Skip to content

Commit ec46d55

Browse files
VietND96JorTurFer
authored andcommitted
fix: Selenium Grid scaler exposes sum of pending and ongoing sessions to KDEA (kedacore#6368)
1 parent f88f571 commit ec46d55

File tree

3 files changed

+141
-124
lines changed

3 files changed

+141
-124
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ Here is an overview of all new **experimental** features:
7474

7575
- **General**: Centralize and improve automaxprocs configuration with proper structured logging ([#5970](https://github.com/kedacore/keda/issues/5970))
7676
- **General**: Paused ScaledObject count is reported correctly after operator restart ([#6321](https://github.com/kedacore/keda/issues/6321))
77+
- **General**: ScaledJobs ready status set to true when recoverred problem ([#6329](https://github.com/kedacore/keda/pull/6329))
78+
- **Selenium Grid Scaler**: Exposes sum of pending and ongoing sessions to KDEA ([#6368](https://github.com/kedacore/keda/pull/6368))
7779

7880
### Deprecations
7981

pkg/scalers/selenium_grid_scaler.go

+47-48
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ type seleniumGridScalerMetadata struct {
3636
BrowserName string `keda:"name=browserName, order=triggerMetadata"`
3737
SessionBrowserName string `keda:"name=sessionBrowserName, order=triggerMetadata, optional"`
3838
ActivationThreshold int64 `keda:"name=activationThreshold, order=triggerMetadata, optional"`
39-
BrowserVersion string `keda:"name=browserVersion, order=triggerMetadata, optional, default=latest"`
40-
UnsafeSsl bool `keda:"name=unsafeSsl, order=triggerMetadata, optional, default=false"`
41-
PlatformName string `keda:"name=platformName, order=triggerMetadata, optional, default=linux"`
42-
NodeMaxSessions int `keda:"name=nodeMaxSessions, order=triggerMetadata, optional, default=1"`
39+
BrowserVersion string `keda:"name=browserVersion, order=triggerMetadata, default=latest"`
40+
UnsafeSsl bool `keda:"name=unsafeSsl, order=triggerMetadata, default=false"`
41+
PlatformName string `keda:"name=platformName, order=triggerMetadata, default=linux"`
42+
NodeMaxSessions int64 `keda:"name=nodeMaxSessions, order=triggerMetadata, default=1"`
4343

4444
TargetValue int64
4545
}
@@ -55,9 +55,9 @@ type Data struct {
5555
}
5656

5757
type Grid struct {
58-
SessionCount int `json:"sessionCount"`
59-
MaxSession int `json:"maxSession"`
60-
TotalSlots int `json:"totalSlots"`
58+
SessionCount int64 `json:"sessionCount"`
59+
MaxSession int64 `json:"maxSession"`
60+
TotalSlots int64 `json:"totalSlots"`
6161
}
6262

6363
type NodesInfo struct {
@@ -71,17 +71,17 @@ type SessionsInfo struct {
7171
type Nodes []struct {
7272
ID string `json:"id"`
7373
Status string `json:"status"`
74-
SessionCount int `json:"sessionCount"`
75-
MaxSession int `json:"maxSession"`
76-
SlotCount int `json:"slotCount"`
74+
SessionCount int64 `json:"sessionCount"`
75+
MaxSession int64 `json:"maxSession"`
76+
SlotCount int64 `json:"slotCount"`
7777
Stereotypes string `json:"stereotypes"`
7878
Sessions Sessions `json:"sessions"`
7979
}
8080

8181
type ReservedNodes struct {
8282
ID string `json:"id"`
83-
MaxSession int `json:"maxSession"`
84-
SlotCount int `json:"slotCount"`
83+
MaxSession int64 `json:"maxSession"`
84+
SlotCount int64 `json:"slotCount"`
8585
}
8686

8787
type Sessions []struct {
@@ -102,7 +102,7 @@ type Capability struct {
102102
}
103103

104104
type Stereotypes []struct {
105-
Slots int `json:"slots"`
105+
Slots int64 `json:"slots"`
106106
Stereotype Capability `json:"stereotype"`
107107
}
108108

@@ -148,6 +148,7 @@ func parseSeleniumGridScalerMetadata(config *scalersconfig.ScalerConfig) (*selen
148148
if meta.SessionBrowserName == "" {
149149
meta.SessionBrowserName = meta.BrowserName
150150
}
151+
151152
return meta, nil
152153
}
153154

@@ -160,18 +161,18 @@ func (s *seleniumGridScaler) Close(context.Context) error {
160161
}
161162

162163
func (s *seleniumGridScaler) GetMetricsAndActivity(ctx context.Context, metricName string) ([]external_metrics.ExternalMetricValue, bool, error) {
163-
sessions, err := s.getSessionsCount(ctx, s.logger)
164+
newRequestNodes, onGoingSessions, err := s.getSessionsQueueLength(ctx, s.logger)
164165
if err != nil {
165166
return []external_metrics.ExternalMetricValue{}, false, fmt.Errorf("error requesting selenium grid endpoint: %w", err)
166167
}
167168

168-
metric := GenerateMetricInMili(metricName, float64(sessions))
169+
metric := GenerateMetricInMili(metricName, float64(newRequestNodes+onGoingSessions))
169170

170-
return []external_metrics.ExternalMetricValue{metric}, sessions > s.metadata.ActivationThreshold, nil
171+
return []external_metrics.ExternalMetricValue{metric}, (newRequestNodes + onGoingSessions) > s.metadata.ActivationThreshold, nil
171172
}
172173

173174
func (s *seleniumGridScaler) GetMetricSpecForScaling(context.Context) []v2.MetricSpec {
174-
metricName := kedautil.NormalizeString(fmt.Sprintf("seleniumgrid-%s", s.metadata.BrowserName))
175+
metricName := kedautil.NormalizeString(fmt.Sprintf("selenium-grid-%s-%s-%s", s.metadata.BrowserName, s.metadata.BrowserVersion, s.metadata.PlatformName))
175176
externalMetric := &v2.ExternalMetricSource{
176177
Metric: v2.MetricIdentifier{
177178
Name: GenerateMetricNameWithIndex(s.metadata.triggerIndex, metricName),
@@ -184,18 +185,18 @@ func (s *seleniumGridScaler) GetMetricSpecForScaling(context.Context) []v2.Metri
184185
return []v2.MetricSpec{metricSpec}
185186
}
186187

187-
func (s *seleniumGridScaler) getSessionsCount(ctx context.Context, logger logr.Logger) (int64, error) {
188+
func (s *seleniumGridScaler) getSessionsQueueLength(ctx context.Context, logger logr.Logger) (int64, int64, error) {
188189
body, err := json.Marshal(map[string]string{
189190
"query": "{ grid { sessionCount, maxSession, totalSlots }, nodesInfo { nodes { id, status, sessionCount, maxSession, slotCount, stereotypes, sessions { id, capabilities, slot { id, stereotype } } } }, sessionsInfo { sessionQueueRequests } }",
190191
})
191192

192193
if err != nil {
193-
return -1, err
194+
return -1, -1, err
194195
}
195196

196197
req, err := http.NewRequestWithContext(ctx, "POST", s.metadata.URL, bytes.NewBuffer(body))
197198
if err != nil {
198-
return -1, err
199+
return -1, -1, err
199200
}
200201

201202
if (s.metadata.AuthType == "" || strings.EqualFold(s.metadata.AuthType, "Basic")) && s.metadata.Username != "" && s.metadata.Password != "" {
@@ -206,28 +207,28 @@ func (s *seleniumGridScaler) getSessionsCount(ctx context.Context, logger logr.L
206207

207208
res, err := s.httpClient.Do(req)
208209
if err != nil {
209-
return -1, err
210+
return -1, -1, err
210211
}
211212

212213
if res.StatusCode != http.StatusOK {
213214
msg := fmt.Sprintf("selenium grid returned %d", res.StatusCode)
214-
return -1, errors.New(msg)
215+
return -1, -1, errors.New(msg)
215216
}
216217

217218
defer res.Body.Close()
218219
b, err := io.ReadAll(res.Body)
219220
if err != nil {
220-
return -1, err
221+
return -1, -1, err
221222
}
222-
v, err := getCountFromSeleniumResponse(b, s.metadata.BrowserName, s.metadata.BrowserVersion, s.metadata.SessionBrowserName, s.metadata.PlatformName, s.metadata.NodeMaxSessions, logger)
223+
newRequestNodes, onGoingSession, err := getCountFromSeleniumResponse(b, s.metadata.BrowserName, s.metadata.BrowserVersion, s.metadata.SessionBrowserName, s.metadata.PlatformName, s.metadata.NodeMaxSessions, logger)
223224
if err != nil {
224-
return -1, err
225+
return -1, -1, err
225226
}
226-
return v, nil
227+
return newRequestNodes, onGoingSession, nil
227228
}
228229

229-
func countMatchingSlotsStereotypes(stereotypes Stereotypes, request Capability, browserName string, browserVersion string, sessionBrowserName string, platformName string) int {
230-
var matchingSlots int
230+
func countMatchingSlotsStereotypes(stereotypes Stereotypes, request Capability, browserName string, browserVersion string, sessionBrowserName string, platformName string) int64 {
231+
var matchingSlots int64
231232
for _, stereotype := range stereotypes {
232233
if checkCapabilitiesMatch(stereotype.Stereotype, request, browserName, browserVersion, sessionBrowserName, platformName) {
233234
matchingSlots += stereotype.Slots
@@ -236,8 +237,8 @@ func countMatchingSlotsStereotypes(stereotypes Stereotypes, request Capability,
236237
return matchingSlots
237238
}
238239

239-
func countMatchingSessions(sessions Sessions, request Capability, browserName string, browserVersion string, sessionBrowserName string, platformName string, logger logr.Logger) int {
240-
var matchingSessions int
240+
func countMatchingSessions(sessions Sessions, request Capability, browserName string, browserVersion string, sessionBrowserName string, platformName string, logger logr.Logger) int64 {
241+
var matchingSessions int64
241242
for _, session := range sessions {
242243
var capability = Capability{}
243244
if err := json.Unmarshal([]byte(session.Capabilities), &capability); err == nil {
@@ -274,7 +275,7 @@ func checkCapabilitiesMatch(capability Capability, requestCapability Capability,
274275
return browserNameMatches && browserVersionMatches && platformNameMatches
275276
}
276277

277-
func checkNodeReservedSlots(reservedNodes []ReservedNodes, nodeID string, availableSlots int) int {
278+
func checkNodeReservedSlots(reservedNodes []ReservedNodes, nodeID string, availableSlots int64) int64 {
278279
for _, reservedNode := range reservedNodes {
279280
if strings.EqualFold(reservedNode.ID, nodeID) {
280281
return reservedNode.SlotCount
@@ -283,7 +284,7 @@ func checkNodeReservedSlots(reservedNodes []ReservedNodes, nodeID string, availa
283284
return availableSlots
284285
}
285286

286-
func updateOrAddReservedNode(reservedNodes []ReservedNodes, nodeID string, slotCount int, maxSession int) []ReservedNodes {
287+
func updateOrAddReservedNode(reservedNodes []ReservedNodes, nodeID string, slotCount int64, maxSession int64) []ReservedNodes {
287288
for i, reservedNode := range reservedNodes {
288289
if strings.EqualFold(reservedNode.ID, nodeID) {
289290
// Update remaining available slots for the reserved node
@@ -295,17 +296,15 @@ func updateOrAddReservedNode(reservedNodes []ReservedNodes, nodeID string, slotC
295296
return append(reservedNodes, ReservedNodes{ID: nodeID, SlotCount: slotCount, MaxSession: maxSession})
296297
}
297298

298-
func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion string, sessionBrowserName string, platformName string, nodeMaxSessions int, logger logr.Logger) (int64, error) {
299-
// The returned count of the number of new Nodes will be scaled up
300-
var count int64
299+
func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion string, sessionBrowserName string, platformName string, nodeMaxSessions int64, logger logr.Logger) (int64, int64, error) {
301300
// Track number of available slots of existing Nodes in the Grid can be reserved for the matched requests
302-
var availableSlots int
301+
var availableSlots int64
303302
// Track number of matched requests in the sessions queue will be served by this scaler
304-
var queueSlots int
303+
var queueSlots int64
305304

306305
var seleniumResponse = SeleniumResponse{}
307306
if err := json.Unmarshal(b, &seleniumResponse); err != nil {
308-
return 0, err
307+
return 0, 0, err
309308
}
310309

311310
var sessionQueueRequests = seleniumResponse.Data.SessionsInfo.SessionQueueRequests
@@ -314,6 +313,7 @@ func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion s
314313
var reservedNodes []ReservedNodes
315314
// Track list of new Nodes will be scaled up with number of available slots following scaler parameter `nodeMaxSessions`
316315
var newRequestNodes []ReservedNodes
316+
var onGoingSessions int64
317317
for requestIndex, sessionQueueRequest := range sessionQueueRequests {
318318
var isRequestMatched bool
319319
var requestCapability = Capability{}
@@ -332,20 +332,22 @@ func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion s
332332
}
333333

334334
var isRequestReserved bool
335+
var sumOfCurrentSessionsMatch int64
335336
// Check if the matched request can be assigned to available slots of existing Nodes in the Grid
336337
for _, node := range nodes {
338+
// Count ongoing sessions that match the request capability and scaler metadata
339+
var currentSessionsMatch = countMatchingSessions(node.Sessions, requestCapability, browserName, browserVersion, sessionBrowserName, platformName, logger)
340+
sumOfCurrentSessionsMatch += currentSessionsMatch
337341
// Check if node is UP and has available slots (maxSession > sessionCount)
338342
if strings.EqualFold(node.Status, "UP") && checkNodeReservedSlots(reservedNodes, node.ID, node.MaxSession-node.SessionCount) > 0 {
339343
var stereotypes = Stereotypes{}
340-
var availableSlotsMatch int
344+
var availableSlotsMatch int64
341345
if err := json.Unmarshal([]byte(node.Stereotypes), &stereotypes); err == nil {
342346
// Count available slots that match the request capability and scaler metadata
343347
availableSlotsMatch += countMatchingSlotsStereotypes(stereotypes, requestCapability, browserName, browserVersion, sessionBrowserName, platformName)
344348
} else {
345349
logger.Error(err, fmt.Sprintf("Error when unmarshaling node stereotypes: %s", err))
346350
}
347-
// Count ongoing sessions that match the request capability and scaler metadata
348-
var currentSessionsMatch = countMatchingSessions(node.Sessions, requestCapability, browserName, browserVersion, sessionBrowserName, platformName, logger)
349351
// Count remaining available slots can be reserved for this request
350352
var availableSlotsCanBeReserved = checkNodeReservedSlots(reservedNodes, node.ID, node.MaxSession-node.SessionCount)
351353
// Reserve one available slot for the request if available slots match is greater than current sessions match
@@ -357,6 +359,9 @@ func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion s
357359
}
358360
}
359361
}
362+
if sumOfCurrentSessionsMatch > onGoingSessions {
363+
onGoingSessions = sumOfCurrentSessionsMatch
364+
}
360365
// Check if the matched request can be assigned to available slots of new Nodes will be scaled up, since the scaler parameter `nodeMaxSessions` can be greater than 1
361366
if !isRequestReserved {
362367
for _, newRequestNode := range newRequestNodes {
@@ -373,11 +378,5 @@ func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion s
373378
}
374379
}
375380

376-
if queueSlots > availableSlots {
377-
count = int64(len(newRequestNodes))
378-
} else {
379-
count = 0
380-
}
381-
382-
return count, nil
381+
return int64(len(newRequestNodes)), onGoingSessions, nil
383382
}

0 commit comments

Comments
 (0)