Skip to content

Commit 77bd816

Browse files
authored
[KEDA] Selenium Grid: Add trigger param overProvisionRatio for ability to scale more than queue request (#2907)
Signed-off-by: Viet Nguyen Duc <[email protected]>
1 parent ad0d7bd commit 77bd816

File tree

11 files changed

+156
-46
lines changed

11 files changed

+156
-46
lines changed

.keda/README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ The stable implementation will be merged to the upstream KEDA repository frequen
1313
Replace the image registry and tag of these KEDA components with the patched image tag:
1414

1515
```bash
16-
docker pull selenium/keda:2.17.2-selenium-grid-20250717
17-
docker pull selenium/keda-metrics-apiserver:2.17.2-selenium-grid-20250717
18-
docker pull selenium/keda-admission-webhooks:2.17.2-selenium-grid-20250717
16+
docker pull selenium/keda:2.17.2-selenium-grid-20250721
17+
docker pull selenium/keda-metrics-apiserver:2.17.2-selenium-grid-20250721
18+
docker pull selenium/keda-admission-webhooks:2.17.2-selenium-grid-20250721
1919
```
2020

2121
Besides that, you also can use image tag `latest` or `nightly`.
@@ -27,15 +27,15 @@ If you are deploying KEDA core using their official Helm [chart](https://github.
2727
keda:
2828
registry: selenium
2929
repository: keda
30-
tag: "2.17.2-selenium-grid-20250717"
30+
tag: "2.17.2-selenium-grid-20250721"
3131
metricsApiServer:
3232
registry: selenium
3333
repository: keda-metrics-apiserver
34-
tag: "2.17.2-selenium-grid-20250717"
34+
tag: "2.17.2-selenium-grid-20250721"
3535
webhooks:
3636
registry: selenium
3737
repository: keda-admission-webhooks
38-
tag: "2.17.2-selenium-grid-20250717"
38+
tag: "2.17.2-selenium-grid-20250721"
3939
```
4040
4141
If you are deployment Selenium Grid chart with `autoscaling.enabled` is `true` (implies installing KEDA sub-chart), KEDA images registry and tag already set in the `values.yaml`. Refer to list [configuration](../charts/selenium-grid/CONFIGURATION.md).
@@ -49,6 +49,8 @@ You can involve to review and discuss the pull requests to help us early detect
4949

5050
[kedacore/keda](https://github.com/kedacore/keda)
5151

52+
- https://github.com/kedacore/keda/pull/6920 (planned, v2.18.0)
53+
5254
- ~~https://github.com/kedacore/keda/pull/6772 (merged, v2.17.1)~~
5355

5456
- ~~https://github.com/kedacore/keda/pull/6684 (merged, v2.17.0)~~

.keda/scalers/selenium-grid-scaler.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ triggers:
2828
nodeMaxSessions: 1 # Optional.
2929
enableManagedDownloads: true # Optional.
3030
capabilities: '' # Optional.
31+
overProvisionRatio: '' # Optional.
3132
```
3233
3334
**Parameter list:**
@@ -42,13 +43,16 @@ triggers:
4243
- `nodeMaxSessions` - Number of maximum sessions that can run in parallel on a Node. Update this parameter align with node config `--max-sessions` (`SE_NODE_MAX_SESSIONS`) to have the correct scaling behavior. (Default: `1`, Optional).
4344
- `enableManagedDownloads`- Set this for Node enabled to auto manage files downloaded for a given session on the Node. When the client requests enabling this feature, it can only be assigned to the Node that also enabled it. Otherwise, the request will wait until it timed out. (Default: `true`, Optional).
4445
- `capabilities` - Add more custom capabilities for matching specific Nodes. It should be in JSON string, see [example](https://www.selenium.dev/documentation/grid/configuration/toml_options/#setting-custom-capabilities-for-matching-specific-nodes) (Optional)
46+
- `overProvisionRatio` - The number of overprovisioning ratio to scale more than the actual number of requests. For example, if there are 20 requests for the browser instead of scaling to 20 Nodes, it is able to scale 20% more than the requested, in this case is 24, in this case input value is `0.2` (Optional)
4547

4648
**Trigger Authentication**
4749
- `username` - Username for basic authentication in GraphQL endpoint instead of embedding in the URL. (Optional)
4850
- `password` - Password for basic authentication in GraphQL endpoint instead of embedding in the URL. (Optional)
4951
- `authType` - Type of authentication to be used. This can be set to `Bearer` or `OAuth2` in case Selenium Grid behind an Ingress proxy with other authentication types. (Optional)
5052
- `accessToken` - Access token. This is required when `authType` is set a value. (Optional)
5153

54+
Noted that trigger authentication parameters are able to set in either trigger metadata or trigger authentication. However, if both are set, the trigger authentication will take precedence.
55+
5256
### Example
5357

5458
---

.keda/scalers/selenium_grid_scaler.go

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,22 @@ type seleniumGridScaler struct {
2828
type seleniumGridScalerMetadata struct {
2929
triggerIndex int
3030

31-
URL string `keda:"name=url, order=authParams;triggerMetadata"`
32-
AuthType string `keda:"name=authType, order=authParams;resolvedEnv, optional"`
33-
Username string `keda:"name=username, order=authParams;resolvedEnv, optional"`
34-
Password string `keda:"name=password, order=authParams;resolvedEnv, optional"`
35-
AccessToken string `keda:"name=accessToken, order=authParams;resolvedEnv, optional"`
36-
BrowserName string `keda:"name=browserName, order=triggerMetadata, optional"`
37-
SessionBrowserName string `keda:"name=sessionBrowserName, order=triggerMetadata, optional"`
38-
BrowserVersion string `keda:"name=browserVersion, order=triggerMetadata, optional"`
39-
PlatformName string `keda:"name=platformName, order=triggerMetadata, optional"`
40-
ActivationThreshold int64 `keda:"name=activationThreshold, order=triggerMetadata, optional"`
41-
UnsafeSsl bool `keda:"name=unsafeSsl, order=triggerMetadata, default=false"`
42-
NodeMaxSessions int64 `keda:"name=nodeMaxSessions, order=triggerMetadata, default=1"`
43-
EnableManagedDownloads bool `keda:"name=enableManagedDownloads, order=triggerMetadata, default=true"`
44-
Capabilities string `keda:"name=capabilities, order=triggerMetadata, optional"`
45-
46-
TargetValue int64
31+
URL string `keda:"name=url, order=authParams;triggerMetadata;resolvedEnv"`
32+
AuthType string `keda:"name=authType, order=authParams;triggerMetadata;resolvedEnv, optional"`
33+
Username string `keda:"name=username, order=authParams;triggerMetadata;resolvedEnv, optional"`
34+
Password string `keda:"name=password, order=authParams;triggerMetadata;resolvedEnv, optional"`
35+
AccessToken string `keda:"name=accessToken, order=authParams;triggerMetadata;resolvedEnv, optional"`
36+
BrowserName string `keda:"name=browserName, order=triggerMetadata, optional"`
37+
SessionBrowserName string `keda:"name=sessionBrowserName, order=triggerMetadata, optional"`
38+
BrowserVersion string `keda:"name=browserVersion, order=triggerMetadata, optional"`
39+
PlatformName string `keda:"name=platformName, order=triggerMetadata, optional"`
40+
NodeMaxSessions int64 `keda:"name=nodeMaxSessions, order=triggerMetadata, default=1"`
41+
EnableManagedDownloads bool `keda:"name=enableManagedDownloads, order=triggerMetadata, default=true"`
42+
Capabilities string `keda:"name=capabilities, order=triggerMetadata, optional"`
43+
OverProvisionRatio float64 `keda:"name=overProvisionRatio, order=triggerMetadata, optional"`
44+
UnsafeSsl bool `keda:"name=unsafeSsl, order=triggerMetadata, optional"`
45+
TargetValue int64 `keda:"name=targetValue, order=triggerMetadata, default=1"`
46+
ActivationThreshold float64 `keda:"name=activationThreshold, order=triggerMetadata, optional"`
4747
}
4848

4949
type Platform struct {
@@ -173,12 +173,10 @@ func parseCapabilitiesToMap(_capabilities string) (map[string]interface{}, error
173173
}
174174

175175
func parseSeleniumGridScalerMetadata(config *scalersconfig.ScalerConfig) (*seleniumGridScalerMetadata, error) {
176-
meta := &seleniumGridScalerMetadata{
177-
TargetValue: 1,
178-
}
176+
meta := &seleniumGridScalerMetadata{}
179177

180178
if err := config.TypedConfig(meta); err != nil {
181-
return nil, fmt.Errorf("error parsing prometheus metadata: %w", err)
179+
return nil, fmt.Errorf("error parsing Selenium Grid GraphQL response: %w", err)
182180
}
183181

184182
meta.triggerIndex = config.TriggerIndex
@@ -204,9 +202,19 @@ func (s *seleniumGridScaler) GetMetricsAndActivity(ctx context.Context, metricNa
204202
return []external_metrics.ExternalMetricValue{}, false, fmt.Errorf("error requesting selenium grid endpoint: %w", err)
205203
}
206204

207-
metric := GenerateMetricInMili(metricName, float64(newRequestNodes+onGoingSessions))
205+
scaledCount := getScaledCount(newRequestNodes, onGoingSessions, s.metadata.OverProvisionRatio)
206+
metric := GenerateMetricInMili(metricName, scaledCount)
207+
208+
return []external_metrics.ExternalMetricValue{metric}, scaledCount > s.metadata.ActivationThreshold, nil
209+
}
208210

209-
return []external_metrics.ExternalMetricValue{metric}, (newRequestNodes + onGoingSessions) > s.metadata.ActivationThreshold, nil
211+
func getScaledCount(newRequestNodes int64, onGoingSession int64, overProvisionRatio float64) float64 {
212+
scaledCount := float64(newRequestNodes + onGoingSession)
213+
if overProvisionRatio > 0 {
214+
// Apply over-provision ratio to the scaled count
215+
scaledCount += scaledCount * overProvisionRatio
216+
}
217+
return scaledCount
210218
}
211219

212220
func buildSeleniumGridMetricName(meta *seleniumGridScalerMetadata) string {

.keda/scalers/selenium_grid_scaler_test.go

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ func Test_getCountFromSeleniumResponse(t *testing.T) {
1919
nodeMaxSessions int64
2020
enableManagedDownloads bool
2121
capabilities string
22+
overProvisionRatio float64
2223
}
2324
tests := []struct {
2425
name string
2526
args args
2627
wantNewRequestNodes int64
2728
wantOnGoingSessions int64
29+
wantOverProvisioned float64
2830
wantErr bool
2931
}{
3032
{
@@ -437,6 +439,57 @@ func Test_getCountFromSeleniumResponse(t *testing.T) {
437439
wantOnGoingSessions: 0,
438440
wantErr: false,
439441
},
442+
{
443+
name: "Scaled_number_when_set_param_over_provisioned",
444+
args: args{
445+
b: []byte(`{
446+
"data": {
447+
"grid": {
448+
"sessionCount": 0,
449+
"maxSession": 0,
450+
"totalSlots": 0
451+
},
452+
"nodesInfo": {
453+
"nodes": [
454+
{
455+
"id": "node-1",
456+
"status": "UP",
457+
"sessionCount": 0,
458+
"maxSession": 1,
459+
"slotCount": 1,
460+
"stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"\", \"platformName\": \"linux\"}}]",
461+
"sessions": []
462+
},
463+
{
464+
"id": "node-2",
465+
"status": "UP",
466+
"sessionCount": 0,
467+
"maxSession": 1,
468+
"slotCount": 1,
469+
"stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"\", \"platformName\": \"Windows 11\"}}]",
470+
"sessions": []
471+
}
472+
]
473+
},
474+
"sessionsInfo": {
475+
"sessionQueueRequests": [
476+
"{\"browserName\": \"chrome\", \"platformName\": \"linux\"}",
477+
"{\"browserName\": \"chrome\", \"platformName\": \"linux\"}"
478+
]
479+
}
480+
}
481+
}`),
482+
browserName: "chrome",
483+
sessionBrowserName: "chrome",
484+
browserVersion: "",
485+
platformName: "linux",
486+
overProvisionRatio: 1.2,
487+
},
488+
wantNewRequestNodes: 1,
489+
wantOnGoingSessions: 0,
490+
wantOverProvisioned: 2.2,
491+
wantErr: false,
492+
},
440493
{
441494
name: "scaler_browserVersion_is_latest,_5_sessionQueueRequests_wihtout_browserVersion_also_1_different_platformName,_1_available_nodeStereotypes_with_3_slots_Linux_and_1_node_Windows,_should_return_count_as_1",
442495
args: args{
@@ -1048,7 +1101,7 @@ func Test_getCountFromSeleniumResponse(t *testing.T) {
10481101
wantErr: false,
10491102
},
10501103
{
1051-
name: "1 queue request without platformName and scaler metadata without platfromName should return 1 new node and 1 ongoing session",
1104+
name: "1 queue request without platformName and scaler metadata without platformName should return 1 new node and 1 ongoing session",
10521105
args: args{
10531106
b: []byte(`{
10541107
"data": {
@@ -3154,13 +3207,17 @@ func Test_getCountFromSeleniumResponse(t *testing.T) {
31543207
for _, tt := range tests {
31553208
t.Run(tt.name, func(t *testing.T) {
31563209
newRequestNodes, onGoingSessions, err := getCountFromSeleniumResponse(tt.args.b, tt.args.browserName, tt.args.browserVersion, tt.args.sessionBrowserName, tt.args.platformName, tt.args.nodeMaxSessions, tt.args.enableManagedDownloads, tt.args.capabilities, logr.Discard())
3210+
scaledCount := getScaledCount(newRequestNodes, onGoingSessions, tt.args.overProvisionRatio)
31573211
if (err != nil) != tt.wantErr {
31583212
t.Errorf("getCountFromSeleniumResponse() error = %v, wantErr %v", err, tt.wantErr)
31593213
return
31603214
}
31613215
if !reflect.DeepEqual(newRequestNodes, tt.wantNewRequestNodes) || !reflect.DeepEqual(onGoingSessions, tt.wantOnGoingSessions) {
31623216
t.Errorf("getCountFromSeleniumResponse() = [%v, %v], want [%v, %v]", newRequestNodes, onGoingSessions, tt.wantNewRequestNodes, tt.wantOnGoingSessions)
31633217
}
3218+
if tt.args.overProvisionRatio > 0 && !reflect.DeepEqual(scaledCount, tt.wantOverProvisioned) {
3219+
t.Errorf("getCountFromSeleniumResponse() = %v, want over-provisioned %v", scaledCount, tt.wantOverProvisioned)
3220+
}
31643221
})
31653222
}
31663223
}

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ KEDA_TAG_PREV_VERSION := $(or $(KEDA_TAG_PREV_VERSION),$(KEDA_TAG_PREV_VERSION),
3434
KEDA_CORE_VERSION := $(or $(KEDA_CORE_VERSION),$(KEDA_CORE_VERSION),2.17.2)
3535
KEDA_TAG_VERSION := $(or $(KEDA_TAG_VERSION),$(KEDA_TAG_VERSION),2.17.2-selenium-grid)
3636
KEDA_BASED_NAME := $(or $(KEDA_BASED_NAME),$(KEDA_BASED_NAME),ndviet)
37-
KEDA_BASED_TAG := $(or $(KEDA_BASED_TAG),$(KEDA_BASED_TAG),2.17.2-selenium-grid-20250705)
38-
TEST_PATCHED_KEDA := $(or $(TEST_PATCHED_KEDA),$(TEST_PATCHED_KEDA),false)
37+
KEDA_BASED_TAG := $(or $(KEDA_BASED_TAG),$(KEDA_BASED_TAG),2.17.2-selenium-grid-20250721)
38+
TEST_PATCHED_KEDA := $(or $(TEST_PATCHED_KEDA),$(TEST_PATCHED_KEDA),true)
3939

4040
all: hub \
4141
distributor \
@@ -284,7 +284,7 @@ fetch_grid_scaler_resources:
284284
&& cd ./.keda/scalers \
285285
&& curl -L https://raw.githubusercontent.com/$(KEDA_BASED_NAME)/keda/v$(KEDA_BASED_TAG)/pkg/scalers/selenium_grid_scaler.go -o selenium_grid_scaler.go \
286286
&& curl -L https://raw.githubusercontent.com/$(KEDA_BASED_NAME)/keda/v$(KEDA_BASED_TAG)/pkg/scalers/selenium_grid_scaler_test.go -o selenium_grid_scaler_test.go \
287-
&& curl -L https://raw.githubusercontent.com/$(KEDA_BASED_NAME)/keda-docs/main/content/docs/2.17/scalers/selenium-grid-scaler.md -o selenium-grid-scaler.md
287+
&& curl -L https://raw.githubusercontent.com/$(KEDA_BASED_NAME)/keda-docs/main/content/docs/2.18/scalers/selenium-grid-scaler.md -o selenium-grid-scaler.md
288288

289289
fetch_grid_scaler_images:
290290
docker pull --platform linux/amd64 --platform linux/arm64 $(KEDA_BASED_NAME)/keda:$(KEDA_BASED_TAG)

0 commit comments

Comments
 (0)