6
6
"errors"
7
7
"fmt"
8
8
"net/http"
9
+ "net/url"
9
10
"strconv"
10
11
11
12
"github.com/go-logr/logr"
@@ -26,32 +27,40 @@ const (
26
27
)
27
28
28
29
type natsJetStreamScaler struct {
29
- metricType v2.MetricTargetType
30
- stream * streamDetail
31
- metadata natsJetStreamMetadata
32
- httpClient * http.Client
33
- logger logr.Logger
30
+ metricType v2.MetricTargetType
31
+ stream * streamDetail
32
+ metadata natsJetStreamMetadata
33
+ httpClient * http.Client
34
+ logger logr.Logger
35
+ leaderStream * streamDetail
34
36
}
35
37
36
38
type natsJetStreamMetadata struct {
37
- monitoringEndpoint string
38
39
account string
39
40
stream string
40
41
consumer string
42
+ leaderName string
43
+ monitoringURL string
41
44
lagThreshold int64
42
45
activationLagThreshold int64
46
+ clusterSize int
43
47
scalerIndex int
44
48
}
45
49
46
50
type jetStreamEndpointResponse struct {
47
- Accounts []accountDetail `json:"account_details"`
51
+ Accounts []accountDetail `json:"account_details"`
52
+ MetaCluster metaCluster `json:"meta_cluster"`
48
53
}
49
54
50
55
type accountDetail struct {
51
56
Name string `json:"name"`
52
57
Streams []* streamDetail `json:"stream_detail"`
53
58
}
54
59
60
+ type metaCluster struct {
61
+ ClusterSize int `json:"cluster_size"`
62
+ }
63
+
55
64
type streamDetail struct {
56
65
Name string `json:"name"`
57
66
Config streamConfig `json:"config"`
@@ -77,6 +86,11 @@ type consumerDetail struct {
77
86
NumPending int `json:"num_pending"`
78
87
Config consumerConfig `json:"config"`
79
88
DeliveryStatus consumerDeliveryStatus `json:"delivery"`
89
+ Cluster consumerCluster `json:"cluster"`
90
+ }
91
+
92
+ type consumerCluster struct {
93
+ Leader string `json:"leader"`
80
94
}
81
95
82
96
type consumerConfig struct {
@@ -158,61 +172,106 @@ func parseNATSJetStreamMetadata(config *ScalerConfig) (natsJetStreamMetadata, er
158
172
return meta , fmt .Errorf ("useHTTPS parsing error %s" , err .Error ())
159
173
}
160
174
}
161
- meta .monitoringEndpoint = getNATSJetStreamEndpoint (useHTTPS , natsServerEndpoint , meta .account )
175
+ meta .monitoringURL = getNATSJetStreamEndpoint (useHTTPS , natsServerEndpoint , meta .account )
162
176
163
177
return meta , nil
164
178
}
165
179
166
180
func getNATSJetStreamEndpoint (useHTTPS bool , natsServerEndpoint string , account string ) string {
167
- protocol := natsHTTPProtocol
181
+ scheme := natsHTTPProtocol
168
182
if useHTTPS {
169
- protocol = natsHTTPSProtocol
183
+ scheme = natsHTTPSProtocol
170
184
}
185
+ return fmt .Sprintf ("%s://%s/jsz?acc=%s&consumers=true&config=true" , scheme , natsServerEndpoint , account )
186
+ }
171
187
172
- return fmt .Sprintf ("%s://%s/jsz?acc=%s&consumers=true&config=true" , protocol , natsServerEndpoint , account )
188
+ func (s * natsJetStreamScaler ) getNATSJetStreamLeaderEndpoint () (string , error ) {
189
+ jsURL , err := url .Parse (s .metadata .monitoringURL )
190
+ if err != nil {
191
+ s .logger .Error (err , "unable to parse monitoring URL to create leader URL" , "natsServerMonitoringURL" , s .metadata .monitoringURL )
192
+ return "" , err
193
+ }
194
+
195
+ return fmt .Sprintf ("%s://%s.%s%s?%s" , jsURL .Scheme , s .metadata .leaderName , jsURL .Host , jsURL .Path , jsURL .RawQuery ), nil
173
196
}
174
197
175
- func (s * natsJetStreamScaler ) IsActive (ctx context.Context ) ( bool , error ) {
176
- req , err := http .NewRequestWithContext (ctx , http .MethodGet , s . metadata . monitoringEndpoint , nil )
198
+ func (s * natsJetStreamScaler ) getNATSJetstreamMonitoringData (ctx context.Context , natsJetStreamEndpoint string ) error {
199
+ req , err := http .NewRequestWithContext (ctx , http .MethodGet , natsJetStreamEndpoint , nil )
177
200
if err != nil {
178
- return false , err
201
+ return err
179
202
}
180
203
181
204
resp , err := s .httpClient .Do (req )
182
205
if err != nil {
183
- s .logger .Error (err , "unable to access NATS JetStream monitoring endpoint" , "natsServerMonitoringEndpoint" , s . metadata . monitoringEndpoint )
184
- return false , err
206
+ s .logger .Error (err , "unable to access NATS JetStream monitoring endpoint" , "natsServerMonitoringEndpoint" , natsJetStreamEndpoint )
207
+ return err
185
208
}
186
209
187
210
defer resp .Body .Close ()
188
211
var jsAccountResp jetStreamEndpointResponse
189
212
if err = json .NewDecoder (resp .Body ).Decode (& jsAccountResp ); err != nil {
190
- s .logger .Error (err , "unable to decode JetStream account response " )
191
- return false , err
213
+ s .logger .Error (err , "unable to decode NATS JetStream account details " )
214
+ return err
192
215
}
193
216
217
+ s .metadata .clusterSize = jsAccountResp .MetaCluster .ClusterSize
218
+
194
219
// Find and assign the stream that we are looking for.
195
- for _ , account := range jsAccountResp .Accounts {
196
- if account .Name == s .metadata .account {
197
- for _ , stream := range account .Streams {
220
+ for _ , jsAccount := range jsAccountResp .Accounts {
221
+ if jsAccount .Name == s .metadata .account {
222
+ for _ , stream := range jsAccount .Streams {
198
223
if stream .Name == s .metadata .stream {
199
224
s .stream = stream
200
225
}
226
+
227
+ for _ , consumer := range stream .Consumers {
228
+ if consumer .Name == s .metadata .consumer {
229
+ s .metadata .leaderName = consumer .Cluster .Leader
230
+ }
231
+ }
201
232
}
202
233
}
203
234
}
235
+
236
+ return nil
237
+ }
238
+
239
+ func (s * natsJetStreamScaler ) IsActive (ctx context.Context ) (bool , error ) {
240
+ err := s .getNATSJetstreamMonitoringData (ctx , s .metadata .monitoringURL )
241
+ if err != nil {
242
+ return false , err
243
+ }
244
+
245
+ // Query the consumer leader pod, it has the accurate count.
246
+ if s .metadata .clusterSize > 1 {
247
+ monitoringLeaderEndpoint , err := s .getNATSJetStreamLeaderEndpoint ()
248
+ if err != nil {
249
+ return false , err
250
+ }
251
+
252
+ err = s .getNATSJetstreamMonitoringData (ctx , monitoringLeaderEndpoint )
253
+ if err != nil {
254
+ return false , err
255
+ }
256
+ }
257
+
204
258
return s .getMaxMsgLag () > s .metadata .activationLagThreshold , nil
205
259
}
206
260
207
261
func (s * natsJetStreamScaler ) getMaxMsgLag () int64 {
208
262
consumerName := s .metadata .consumer
209
263
210
- for _ , consumer := range s .stream .Consumers {
264
+ stream := s .stream
265
+ if s .leaderStream != nil {
266
+ stream = s .leaderStream
267
+ }
268
+
269
+ for _ , consumer := range stream .Consumers {
211
270
if consumer .Name == consumerName {
212
271
return int64 (consumer .NumPending + consumer .NumAckPending )
213
272
}
214
273
}
215
- return s . stream .State .LastSequence
274
+ return stream .State .LastSequence
216
275
}
217
276
218
277
func (s * natsJetStreamScaler ) GetMetricSpecForScaling (context.Context ) []v2.MetricSpec {
@@ -230,32 +289,21 @@ func (s *natsJetStreamScaler) GetMetricSpecForScaling(context.Context) []v2.Metr
230
289
}
231
290
232
291
func (s * natsJetStreamScaler ) GetMetrics (ctx context.Context , metricName string , metricSelector labels.Selector ) ([]external_metrics.ExternalMetricValue , error ) {
233
- req , err := http .NewRequestWithContext (ctx , http .MethodGet , s .metadata .monitoringEndpoint , nil )
234
- if err != nil {
235
- return nil , err
236
- }
237
-
238
- resp , err := s .httpClient .Do (req )
292
+ err := s .getNATSJetstreamMonitoringData (ctx , s .metadata .monitoringURL )
239
293
if err != nil {
240
- s .logger .Error (err , "unable to access NATS JetStream monitoring endpoint" , "natsServerMonitoringEndpoint" , s .metadata .monitoringEndpoint )
241
294
return []external_metrics.ExternalMetricValue {}, err
242
295
}
243
296
244
- defer resp . Body . Close ()
245
- var jsAccountResp jetStreamEndpointResponse
246
- if err = json . NewDecoder ( resp . Body ). Decode ( & jsAccountResp ); err != nil {
247
- s . logger . Error ( err , "unable to decode JetStream account details" )
248
- return []external_metrics.ExternalMetricValue {}, err
249
- }
297
+ // Query the consumer leader pod, it has the accurate count.
298
+ if s . metadata . clusterSize > 1 {
299
+ monitoringLeaderEndpoint , err := s . getNATSJetStreamLeaderEndpoint ()
300
+ if err != nil {
301
+ return []external_metrics.ExternalMetricValue {}, err
302
+ }
250
303
251
- // Find and assign the stream that we are looking for.
252
- for _ , account := range jsAccountResp .Accounts {
253
- if account .Name == s .metadata .account {
254
- for _ , stream := range account .Streams {
255
- if stream .Name == s .metadata .stream {
256
- s .stream = stream
257
- }
258
- }
304
+ err = s .getNATSJetstreamMonitoringData (ctx , monitoringLeaderEndpoint )
305
+ if err != nil {
306
+ return []external_metrics.ExternalMetricValue {}, err
259
307
}
260
308
}
261
309
0 commit comments