diff --git a/.keda/README.md b/.keda/README.md index 0846badd59..ac3ca1e415 100644 --- a/.keda/README.md +++ b/.keda/README.md @@ -49,7 +49,9 @@ You can involve to review and discuss the pull requests to help us early detect [kedacore/keda](https://github.com/kedacore/keda) -- https://github.com/kedacore/keda/pull/6477 +- https://github.com/kedacore/keda/pull/6536 (plan, v2.17.0) + +- https://github.com/kedacore/keda/pull/6477 (plan, v2.17.0) - ~~https://github.com/kedacore/keda/pull/6437 (merged, v2.16.1)~~ @@ -59,9 +61,11 @@ You can involve to review and discuss the pull requests to help us early detect [kedacore/keda-docs](https://github.com/kedacore/keda-docs) -- https://github.com/kedacore/keda-docs/pull/1522 +- https://github.com/kedacore/keda-docs/pull/1533 (plan, v2.17.0) + +- https://github.com/kedacore/keda-docs/pull/1522 (plan, v2.17.0) -- https://github.com/kedacore/keda-docs/pull/1515 +- ~~https://github.com/kedacore/keda-docs/pull/1515 (merged, v2.16.1)~~ - ~~https://github.com/kedacore/keda-docs/pull/1468 (merged, v2.16.0)~~ diff --git a/.keda/scalers/selenium-grid-scaler.md b/.keda/scalers/selenium-grid-scaler.md index f1c28acc36..fc80ac71a9 100644 --- a/.keda/scalers/selenium-grid-scaler.md +++ b/.keda/scalers/selenium-grid-scaler.md @@ -23,7 +23,7 @@ triggers: browserName: '' # Optional. Required to be matched with the request in queue and Node stereotypes (Similarly for `browserVersion` and `platformName`). browserVersion: '' # Optional. platformName: '' # Optional. - unsafeSsl : 'false' # Optional. + unsafeSsl: 'false' # Optional. activationThreshold: 0 # Optional. ``` @@ -37,6 +37,7 @@ triggers: - `activationThreshold` - Target value for activating the scaler. Learn more about activation [here](./../concepts/scaling-deployments.md#activating-and-scaling-thresholds). (Default: `0`, Optional) - `platformName` - Name of the browser platform. Refer to the [Selenium Grid's](https://www.selenium.dev/documentation/en/getting_started_with_webdriver/browsers/) and [WebdriverIO's](https://webdriver.io/docs/options/#capabilities) documentation for more info. (Optional) - `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). +- `capabilities` - Add more custom capabilities for matching specific Nodes. (Optional) **Trigger Authentication** - `username` - Username for basic authentication in GraphQL endpoint instead of embedding in the URL. (Optional) @@ -88,7 +89,7 @@ spec: url: 'http://selenium-hub:4444/graphql' browserName: 'chrome' platformName: 'Linux' - unsafeSsl : 'true' + unsafeSsl: 'true' ``` Noted: @@ -198,7 +199,7 @@ spec: browserName: 'chrome' platformName: 'Linux' browserVersion: '131.0' - unsafeSsl : 'true' + unsafeSsl: 'true' ``` The request to trigger this scaler should be @@ -210,6 +211,69 @@ options.set_capability('browserVersion', '131.0') driver = webdriver.Remote(options=options, command_executor=SELENIUM_GRID_URL) ``` +For an advanced use case, you also can set custom capabilities for matching specific Nodes in the scaler trigger metadata. For example + +```yaml +kind: Deployment +metadata: + name: selenium-node-chrome + labels: + deploymentName: selenium-node-chrome +spec: + replicas: 1 + template: + spec: + containers: + - name: selenium-node-chrome + image: selenium/node-chrome:132.0 + ports: + - containerPort: 5555 + env: + - name: SE_NODE_BROWSER_VERSION + value: '132.0' + - name: SE_NODE_PLATFORM_NAME + value: 'Linux' + # Append custom capabilities to Node stereotype. See: https://github.com/SeleniumHQ/docker-selenium?tab=readme-ov-file#node-configuration-options + - name: SE_NODE_STEREOTYPE_EXTRA + value: "{\"myApp:version\":\"beta\", \"myApp:publish:\":\"public\"}" + +--- + +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + name: selenium-grid-scaledobject-chrome-132 + namespace: keda + labels: + deploymentName: selenium-node-chrome-132 +spec: + maxReplicaCount: 8 + scaleTargetRef: + name: selenium-node-chrome-132 + triggers: + - type: selenium-grid + metadata: + url: 'http://selenium-hub:4444/graphql' + browserName: 'chrome' + platformName: 'Linux' + browserVersion: '132.0' + unsafeSsl: 'true' + # Add custom capabilities for matching specific Nodes in scaler trigger metadata. See: https://github.com/kedacore/keda/pull/6536 + capabilities: "{\"myApp:version\":\"beta\", \"myApp:publish:\":\"public\"}" +``` + +The request to trigger this scaler should be + +```python +options = ChromeOptions() +options.set_capability('platformName', 'Linux') +options.set_capability('browserVersion', '132.0') +# Add custom capabilities for matching specific Nodes in client binding. See: https://www.selenium.dev/documentation/grid/configuration/toml_options/#setting-custom-capabilities-for-matching-specific-nodes +options.set_capability('myApp:version', 'beta') +options.set_capability('myApp:publish', 'public') +driver = webdriver.Remote(options=options, command_executor=SELENIUM_GRID_URL) +``` + Similarly, for Firefox ```yaml @@ -230,7 +294,7 @@ spec: url: 'http://selenium-hub:4444/graphql' browserName: 'firefox' platformName: 'Linux' - unsafeSsl : 'true' + unsafeSsl: 'true' ``` Request to trigger the scaler @@ -262,7 +326,7 @@ spec: browserName: 'MicrosoftEdge' sessionBrowserName: 'msedge' platformName: 'Linux' - unsafeSsl : 'true' + unsafeSsl: 'true' ``` Request to trigger the scaler @@ -294,7 +358,7 @@ spec: browserName: 'chrome' platformName: 'Linux' nodeMaxSessions: 4 - unsafeSsl : 'true' + unsafeSsl: 'true' ``` If you are supporting multiple versions of browser capability in your Selenium Grid, You should create one scaler for every browser version and pass the `browserVersion` in the metadata. @@ -318,7 +382,7 @@ spec: browserName: 'chrome' platformName: 'Linux' browserVersion: '91.0' - unsafeSsl : 'true' + unsafeSsl: 'true' ``` ```yaml @@ -340,7 +404,7 @@ spec: browserName: 'chrome' platformName: 'Linux' browserVersion: '90.0' - unsafeSsl : 'true' + unsafeSsl: 'true' ``` ### Authentication Parameters @@ -396,7 +460,7 @@ spec: metadata: browserName: 'chrome' platformName: 'Linux' - unsafeSsl : 'true' + unsafeSsl: 'true' authenticationRef: name: keda-trigger-auth-selenium-grid-secret ``` diff --git a/.keda/scalers/selenium_grid_scaler.go b/.keda/scalers/selenium_grid_scaler.go index 5867cf4d56..8914dc25b8 100644 --- a/.keda/scalers/selenium_grid_scaler.go +++ b/.keda/scalers/selenium_grid_scaler.go @@ -40,6 +40,7 @@ type seleniumGridScalerMetadata struct { ActivationThreshold int64 `keda:"name=activationThreshold, order=triggerMetadata, optional"` UnsafeSsl bool `keda:"name=unsafeSsl, order=triggerMetadata, default=false"` NodeMaxSessions int64 `keda:"name=nodeMaxSessions, order=triggerMetadata, default=1"` + Capabilities string `keda:"name=capabilities, order=triggerMetadata, optional"` TargetValue int64 } @@ -95,15 +96,38 @@ type Slot struct { Stereotype string `json:"stereotype"` } -type Capability struct { - BrowserName string `json:"browserName,omitempty"` - BrowserVersion string `json:"browserVersion,omitempty"` - PlatformName string `json:"platformName,omitempty"` +type Stereotypes []struct { + Slots int64 `json:"slots"` + Stereotype map[string]interface{} `json:"stereotype"` } -type Stereotypes []struct { - Slots int64 `json:"slots"` - Stereotype Capability `json:"stereotype"` +var ExtensionCapabilitiesPrefixes = []string{"goog:", "moz:", "ms:", "se:"} +var FunctionCapabilitiesPrefixes = []string{"se:downloadsEnabled"} + +// Follow pattern in https://github.com/SeleniumHQ/selenium/blob/trunk/java/src/org/openqa/selenium/grid/data/DefaultSlotMatcher.java +func filterCapabilities(capabilities map[string]interface{}) map[string]interface{} { + filteredCapabilities := make(map[string]interface{}) + + for key, value := range capabilities { + retain := true + for _, excludePrefix := range ExtensionCapabilitiesPrefixes { + if strings.HasPrefix(key, excludePrefix) { + retain = false + break + } + } + for _, prefix := range FunctionCapabilitiesPrefixes { + if strings.HasPrefix(key, prefix) { + retain = true + break + } + } + if retain { + filteredCapabilities[key] = value + } + } + + return filteredCapabilities } func NewSeleniumGridScaler(config *scalersconfig.ScalerConfig) (Scaler, error) { @@ -207,38 +231,61 @@ func (s *seleniumGridScaler) getSessionsQueueLength(ctx context.Context, logger } if res.StatusCode != http.StatusOK { - msg := fmt.Sprintf("selenium grid returned %d", res.StatusCode) + msg := fmt.Sprintf("Selenium Grid returned response status code: %d", res.StatusCode) + logger.Error(errors.New(msg), msg) return -1, -1, errors.New(msg) } defer res.Body.Close() b, err := io.ReadAll(res.Body) if err != nil { + logger.Error(err, fmt.Sprintf("Error when reading Selenium Grid response body: %s", err)) return -1, -1, err } - newRequestNodes, onGoingSession, err := getCountFromSeleniumResponse(b, s.metadata.BrowserName, s.metadata.BrowserVersion, s.metadata.SessionBrowserName, s.metadata.PlatformName, s.metadata.NodeMaxSessions, logger) + newRequestNodes, onGoingSession, err := getCountFromSeleniumResponse(b, s.metadata.BrowserName, s.metadata.BrowserVersion, s.metadata.SessionBrowserName, s.metadata.PlatformName, s.metadata.NodeMaxSessions, s.metadata.Capabilities, logger) if err != nil { + logger.Error(err, fmt.Sprintf("Error when getting count from Selenium Grid response: %s", err)) return -1, -1, err } return newRequestNodes, onGoingSession, nil } -func countMatchingSlotsStereotypes(stereotypes Stereotypes, browserName string, browserVersion string, sessionBrowserName string, platformName string) int64 { +func getCapability(capability map[string]interface{}, key string) string { + value, ok := capability[key] + if ok { + return value.(string) + } + return "" +} + +func getBrowserName(capability map[string]interface{}) string { + return getCapability(capability, "browserName") +} + +func getBrowserVersion(capability map[string]interface{}) string { + return getCapability(capability, "browserVersion") +} + +func getPlatformName(capability map[string]interface{}) string { + return getCapability(capability, "platformName") +} + +func countMatchingSlotsStereotypes(stereotypes Stereotypes, browserName string, browserVersion string, sessionBrowserName string, platformName string, capabilities map[string]interface{}) int64 { var matchingSlots int64 for _, stereotype := range stereotypes { - if checkStereotypeCapabilitiesMatch(stereotype.Stereotype, browserName, browserVersion, sessionBrowserName, platformName) { + if checkStereotypeCapabilitiesMatch(stereotype.Stereotype, browserName, browserVersion, sessionBrowserName, platformName, capabilities) { matchingSlots += stereotype.Slots } } return matchingSlots } -func countMatchingSessions(sessions Sessions, browserName string, browserVersion string, sessionBrowserName string, platformName string, logger logr.Logger) int64 { +func countMatchingSessions(sessions Sessions, browserName string, browserVersion string, sessionBrowserName string, platformName string, capabilities map[string]interface{}, logger logr.Logger) int64 { var matchingSessions int64 for _, session := range sessions { - var capability = Capability{} + var capability map[string]interface{} if err := json.Unmarshal([]byte(session.Slot.Stereotype), &capability); err == nil { - if checkStereotypeCapabilitiesMatch(capability, browserName, browserVersion, sessionBrowserName, platformName) { + if checkStereotypeCapabilitiesMatch(capability, browserName, browserVersion, sessionBrowserName, platformName, capabilities) { matchingSessions++ } } else { @@ -248,39 +295,58 @@ func countMatchingSessions(sessions Sessions, browserName string, browserVersion return matchingSessions } +func extensionCapabilitiesMatch(stereotype map[string]interface{}, capabilities map[string]interface{}) bool { + capabilities = filterCapabilities(capabilities) + if len(capabilities) == 0 { + return true + } + for key, value := range capabilities { + if stereotypeValue, ok := stereotype[key]; !ok || stereotypeValue != value { + return false + } + } + return true +} + // This function checks if the request capabilities match the scaler metadata -func checkRequestCapabilitiesMatch(request Capability, browserName string, browserVersion string, _ string, platformName string) bool { +func checkRequestCapabilitiesMatch(request map[string]interface{}, browserName string, browserVersion string, _ string, platformName string, capabilities map[string]interface{}) bool { // Check if browserName matches - browserNameMatch := (request.BrowserName == "" && browserName == "") || - strings.EqualFold(browserName, request.BrowserName) + _browserName := getBrowserName(request) + browserNameMatch := (_browserName == "" && browserName == "") || + strings.EqualFold(browserName, _browserName) // Check if browserVersion matches - browserVersionMatch := (request.BrowserVersion == "" && browserVersion == "") || - (request.BrowserVersion != "" && strings.HasPrefix(browserVersion, request.BrowserVersion)) + _browserVersion := getBrowserVersion(request) + browserVersionMatch := (_browserVersion == "" && browserVersion == "") || + (_browserVersion != "" && strings.HasPrefix(browserVersion, _browserVersion)) // Check if platformName matches - platformNameMatch := (request.PlatformName == "" || strings.EqualFold("any", request.PlatformName) || strings.EqualFold(platformName, request.PlatformName)) && - (platformName == "" || platformName == "any" || strings.EqualFold(platformName, request.PlatformName)) + _platformName := getPlatformName(request) + platformNameMatch := (_platformName == "" || strings.EqualFold("any", _platformName) || strings.EqualFold(platformName, _platformName)) && + (platformName == "" || platformName == "any" || strings.EqualFold(platformName, _platformName)) - return browserNameMatch && browserVersionMatch && platformNameMatch + return browserNameMatch && browserVersionMatch && platformNameMatch && extensionCapabilitiesMatch(request, capabilities) } // This function checks if Node stereotypes or ongoing sessions match the scaler metadata -func checkStereotypeCapabilitiesMatch(capability Capability, browserName string, browserVersion string, sessionBrowserName string, platformName string) bool { +func checkStereotypeCapabilitiesMatch(capability map[string]interface{}, browserName string, browserVersion string, sessionBrowserName string, platformName string, capabilities map[string]interface{}) bool { // Check if browserName matches - browserNameMatch := (capability.BrowserName == "" && browserName == "") || - strings.EqualFold(browserName, capability.BrowserName) || - strings.EqualFold(sessionBrowserName, capability.BrowserName) + _browserName := getBrowserName(capability) + browserNameMatch := (_browserName == "" && browserName == "") || + strings.EqualFold(browserName, _browserName) || + strings.EqualFold(sessionBrowserName, _browserName) // Check if browserVersion matches - browserVersionMatch := (capability.BrowserVersion == "" && browserVersion == "") || - (capability.BrowserVersion != "" && strings.HasPrefix(browserVersion, capability.BrowserVersion)) + _browserVersion := getBrowserVersion(capability) + browserVersionMatch := (_browserVersion == "" && browserVersion == "") || + (_browserVersion != "" && strings.HasPrefix(browserVersion, _browserVersion)) // Check if platformName matches - platformNameMatch := (capability.PlatformName == "" || strings.EqualFold("any", capability.PlatformName) || strings.EqualFold(platformName, capability.PlatformName)) && - (platformName == "" || platformName == "any" || strings.EqualFold(platformName, capability.PlatformName)) + _platformVersion := getPlatformName(capability) + platformNameMatch := (_platformVersion == "" || strings.EqualFold("any", _platformVersion) || strings.EqualFold(platformName, _platformVersion)) && + (platformName == "" || platformName == "any" || strings.EqualFold(platformName, _platformVersion)) - return browserNameMatch && browserVersionMatch && platformNameMatch + return browserNameMatch && browserVersionMatch && platformNameMatch && extensionCapabilitiesMatch(capability, capabilities) } func checkNodeReservedSlots(reservedNodes []ReservedNodes, nodeID string, availableSlots int64) int64 { @@ -304,7 +370,7 @@ func updateOrAddReservedNode(reservedNodes []ReservedNodes, nodeID string, slotC return append(reservedNodes, ReservedNodes{ID: nodeID, SlotCount: slotCount, MaxSession: maxSession}) } -func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion string, sessionBrowserName string, platformName string, nodeMaxSessions int64, logger logr.Logger) (int64, int64, error) { +func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion string, sessionBrowserName string, platformName string, nodeMaxSessions int64, _capabilities string, logger logr.Logger) (int64, int64, error) { // Track number of available slots of existing Nodes in the Grid can be reserved for the matched requests var availableSlots int64 // Track number of matched requests in the sessions queue will be served by this scaler @@ -314,6 +380,13 @@ func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion s if err := json.Unmarshal(b, &seleniumResponse); err != nil { return 0, 0, err } + capabilities := map[string]interface{}{} + if _capabilities != "" { + if err := json.Unmarshal([]byte(_capabilities), &capabilities); err != nil { + logger.Error(err, fmt.Sprintf("Error when unmarshaling trigger metadata 'capabilities': %s", err)) + return 0, 0, err + } + } var sessionQueueRequests = seleniumResponse.Data.SessionsInfo.SessionQueueRequests var nodes = seleniumResponse.Data.NodesInfo.Nodes @@ -324,9 +397,9 @@ func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion s var onGoingSessions int64 for requestIndex, sessionQueueRequest := range sessionQueueRequests { var isRequestMatched bool - var requestCapability = Capability{} + var requestCapability map[string]interface{} if err := json.Unmarshal([]byte(sessionQueueRequest), &requestCapability); err == nil { - if checkRequestCapabilitiesMatch(requestCapability, browserName, browserVersion, sessionBrowserName, platformName) { + if checkRequestCapabilitiesMatch(requestCapability, browserName, browserVersion, sessionBrowserName, platformName, capabilities) { queueSlots++ isRequestMatched = true } @@ -343,7 +416,7 @@ func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion s var availableSlotsMatch int64 if err := json.Unmarshal([]byte(node.Stereotypes), &stereotypes); err == nil { // Count available slots that match the request capability and scaler metadata - availableSlotsMatch += countMatchingSlotsStereotypes(stereotypes, browserName, browserVersion, sessionBrowserName, platformName) + availableSlotsMatch += countMatchingSlotsStereotypes(stereotypes, browserName, browserVersion, sessionBrowserName, platformName, capabilities) } else { logger.Error(err, fmt.Sprintf("Error when unmarshaling node stereotypes: %s", err)) } @@ -351,7 +424,7 @@ func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion s continue } // Count ongoing sessions that match the request capability and scaler metadata - var currentSessionsMatch = countMatchingSessions(node.Sessions, browserName, browserVersion, sessionBrowserName, platformName, logger) + var currentSessionsMatch = countMatchingSessions(node.Sessions, browserName, browserVersion, sessionBrowserName, platformName, capabilities, logger) // Count remaining available slots can be reserved for this request var availableSlotsCanBeReserved = checkNodeReservedSlots(reservedNodes, node.ID, node.MaxSession-node.SessionCount) // Reserve one available slot for the request if available slots match is greater than current sessions match @@ -381,7 +454,7 @@ func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion s // Count ongoing sessions across all nodes that match the scaler metadata for _, node := range nodes { - onGoingSessions += countMatchingSessions(node.Sessions, browserName, browserVersion, sessionBrowserName, platformName, logger) + onGoingSessions += countMatchingSessions(node.Sessions, browserName, browserVersion, sessionBrowserName, platformName, capabilities, logger) } return int64(len(newRequestNodes)), onGoingSessions, nil diff --git a/.keda/scalers/selenium_grid_scaler_test.go b/.keda/scalers/selenium_grid_scaler_test.go index c556c35091..add23d6f99 100644 --- a/.keda/scalers/selenium_grid_scaler_test.go +++ b/.keda/scalers/selenium_grid_scaler_test.go @@ -17,6 +17,7 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion string platformName string nodeMaxSessions int64 + capabilities string } tests := []struct { name string @@ -1906,6 +1907,112 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { wantOnGoingSessions: 0, wantErr: false, }, + { + name: "4_sessions_requests_with_matching_browserName_and_platformName_when_set_nodeMaxSessions_2_and_3_requests_match_should_return_count_as_1_and_1_ongoing_session", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 1, + "maxSession": 2, + "totalSlots": 2 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 2, + "slotCount": 2, + "stereotypes": "[{\"slots\": 2, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"\", \"platformName\": \"linux\", \"se:downloadsEnabled\": true}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"\", \"platformName\": \"linux\", \"se:downloadsEnabled\": true}", + "slot": { + "id": "9ce1edba-72fb-465e-b311-ee473d8d7b64", + "stereotype": "{\"browserName\": \"chrome\", \"browserVersion\": \"\", \"platformName\": \"linux\", \"se:downloadsEnabled\": true}" + } + } + ] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\n \"browserName\": \"chrome\",\n \"platformName\": \"linux\"\n}", + "{\n \"browserName\": \"chrome\",\n \"se:downloadsEnabled\": true,\n \"platformName\": \"linux\"\n}", + "{\n \"browserName\": \"chrome\",\n \"se:downloadsEnabled\": true,\n \"platformName\": \"linux\"\n}", + "{\n \"browserName\": \"chrome\",\n \"se:downloadsEnabled\": true,\n \"platformName\": \"linux\"\n}", + "{\n \"browserName\": \"chrome\",\n \"platformName\": \"Windows 11\"\n}"] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "", + platformName: "linux", + nodeMaxSessions: 2, + capabilities: "{\"se:downloadsEnabled\": true}", + }, + wantNewRequestNodes: 1, + wantOnGoingSessions: 1, + wantErr: false, + }, + { + name: "4_sessions_requests_with_matching_browserName_and_platformName_when_set_extra_capabilities_and_2_requests_match_should_return_count_as_2_and_ongoing_1", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 1, + "maxSession": 1, + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"\", \"platformName\": \"linux\", \"myApp:version\": \"beta\", \"myApp:scope\": \"internal\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"\", \"platformName\": \"linux\", \"myApp:version\": \"beta\", \"myApp:scope\": \"internal\"}", + "slot": { + "id": "9ce1edba-72fb-465e-b311-ee473d8d7b64", + "stereotype": "{\"browserName\": \"chrome\", \"browserVersion\": \"\", \"platformName\": \"linux\", \"myApp:version\": \"beta\", \"myApp:scope\": \"internal\"}" + } + } + ] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\n \"browserName\": \"chrome\",\n \"platformName\": \"linux\"\n}", + "{\n \"browserName\": \"chrome\",\n \"myApp:version\": \"beta\",\n \"platformName\": \"linux\"\n}", + "{\n \"browserName\": \"chrome\",\n \"myApp:version\": \"beta\",\n \"myApp:scope\": \"internal\",\n \"platformName\": \"linux\"\n}", + "{\n \"browserName\": \"chrome\",\n \"myApp:version\": \"beta\",\n \"myApp:scope\": \"internal\",\n \"platformName\": \"linux\"\n}", + "{\n \"browserName\": \"chrome\",\n \"platformName\": \"Windows 11\"\n}"] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "", + platformName: "linux", + nodeMaxSessions: 1, + capabilities: "{\"myApp:version\": \"beta\", \"myApp:scope\": \"internal\"}", + }, + wantNewRequestNodes: 2, + wantOnGoingSessions: 1, + wantErr: false, + }, { name: "Given_2_requests_include_1_without_browserVersion_When_scaler_metadata_explicit_name_version_platform_Then_scaler_should_scale_up_for_1_request_has_browserVersion_and_return_0_ongoing_sessions", args: args{ @@ -2865,7 +2972,7 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - newRequestNodes, onGoingSessions, err := getCountFromSeleniumResponse(tt.args.b, tt.args.browserName, tt.args.browserVersion, tt.args.sessionBrowserName, tt.args.platformName, tt.args.nodeMaxSessions, logr.Discard()) + newRequestNodes, onGoingSessions, err := getCountFromSeleniumResponse(tt.args.b, tt.args.browserName, tt.args.browserVersion, tt.args.sessionBrowserName, tt.args.platformName, tt.args.nodeMaxSessions, tt.args.capabilities, logr.Discard()) if (err != nil) != tt.wantErr { t.Errorf("getCountFromSeleniumResponse() error = %v, wantErr %v", err, tt.wantErr) return @@ -2926,6 +3033,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { BrowserVersion: "", PlatformName: "", NodeMaxSessions: 1, + Capabilities: "", }, }, { @@ -2948,6 +3056,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { BrowserVersion: "", PlatformName: "", NodeMaxSessions: 1, + Capabilities: "", }, }, { @@ -2969,6 +3078,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { BrowserVersion: "", PlatformName: "", NodeMaxSessions: 1, + Capabilities: "", }, }, { @@ -2997,6 +3107,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { BrowserVersion: "", PlatformName: "", NodeMaxSessions: 1, + Capabilities: "", }, }, { @@ -3025,6 +3136,37 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { Username: "username", Password: "password", NodeMaxSessions: 1, + Capabilities: "", + }, + }, + { + name: "valid capabilities should return metadata", + args: args{ + config: &scalersconfig.ScalerConfig{ + AuthParams: map[string]string{ + "username": "username", + "password": "password", + }, + TriggerMetadata: map[string]string{ + "url": "http://selenium-hub:4444/graphql", + "browserName": "MicrosoftEdge", + "sessionBrowserName": "msedge", + "capabilities": "{\"se:downloadsEnabled\": true}", + }, + }, + }, + wantErr: false, + want: &seleniumGridScalerMetadata{ + URL: "http://selenium-hub:4444/graphql", + BrowserName: "MicrosoftEdge", + SessionBrowserName: "msedge", + TargetValue: 1, + BrowserVersion: "", + PlatformName: "", + Username: "username", + Password: "password", + NodeMaxSessions: 1, + Capabilities: "{\"se:downloadsEnabled\": true}", }, }, { @@ -3049,6 +3191,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { UnsafeSsl: false, PlatformName: "", NodeMaxSessions: 1, + Capabilities: "", }, }, { @@ -3075,6 +3218,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { UnsafeSsl: true, PlatformName: "", NodeMaxSessions: 1, + Capabilities: "", }, }, { @@ -3116,6 +3260,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { UnsafeSsl: true, PlatformName: "", NodeMaxSessions: 1, + Capabilities: "", }, }, { @@ -3143,6 +3288,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { UnsafeSsl: true, PlatformName: "Windows 11", NodeMaxSessions: 1, + Capabilities: "", }, }, { @@ -3177,6 +3323,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { UnsafeSsl: true, PlatformName: "Windows 11", NodeMaxSessions: 3, + Capabilities: "", }, }, { @@ -3212,6 +3359,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { UnsafeSsl: true, PlatformName: "Windows 11", NodeMaxSessions: 3, + Capabilities: "", }, }, { @@ -3247,6 +3395,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { UnsafeSsl: true, PlatformName: "Windows 11", NodeMaxSessions: 3, + Capabilities: "", }, }, { @@ -3281,6 +3430,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { UnsafeSsl: true, PlatformName: "Windows 11", NodeMaxSessions: 3, + Capabilities: "", }, }, } diff --git a/Makefile b/Makefile index 0b22b97c86..26f55ba1dd 100644 --- a/Makefile +++ b/Makefile @@ -28,13 +28,13 @@ CURRENT_PLATFORM := $(shell if [ `arch` = "aarch64" ] || [ `arch` = "arm64" ]; t PLATFORMS := $(or $(PLATFORMS),$(shell echo $$PLATFORMS),$(CURRENT_PLATFORM)) SEL_PASSWD := $(or $(SEL_PASSWD),$(SEL_PASSWD),secret) CHROMIUM_VERSION := $(or $(CHROMIUM_VERSION),$(CHROMIUM_VERSION),latest) -FIREFOX_DOWNLOAD_URL := $(or $(FIREFOX_DOWNLOAD_URL),$(FIREFOX_DOWNLOAD_URL),https://download-installer.cdn.mozilla.net/pub/firefox/nightly/2024/11/2024-11-25-09-40-45-mozilla-central/firefox-134.0a1.en-US.linux-aarch64.deb) +FIREFOX_DOWNLOAD_URL := $(or $(FIREFOX_DOWNLOAD_URL),$(FIREFOX_DOWNLOAD_URL),https://download-installer.cdn.mozilla.net/pub/firefox/nightly/2025/01/2025-01-06-09-47-46-mozilla-central/firefox-135.0a1.en-US.linux-aarch64.deb) SBOM_OUTPUT := $(or $(SBOM_OUTPUT),$(SBOM_OUTPUT),package_versions.txt) KEDA_TAG_PREV_VERSION := $(or $(KEDA_TAG_PREV_VERSION),$(KEDA_TAG_PREV_VERSION),2.16.1-selenium-grid) KEDA_CORE_VERSION := $(or $(KEDA_CORE_VERSION),$(KEDA_CORE_VERSION),2.16.1) KEDA_TAG_VERSION := $(or $(KEDA_TAG_VERSION),$(KEDA_TAG_VERSION),2.16.1-selenium-grid) KEDA_BASED_NAME := $(or $(KEDA_BASED_NAME),$(KEDA_BASED_NAME),ndviet) -KEDA_BASED_TAG := $(or $(KEDA_BASED_TAG),$(KEDA_BASED_TAG),2.16.1-selenium-grid-20250108) +KEDA_BASED_TAG := $(or $(KEDA_BASED_TAG),$(KEDA_BASED_TAG),2.16.1-selenium-grid-20250210) TEST_PATCHED_KEDA := $(or $(TEST_PATCHED_KEDA),$(TEST_PATCHED_KEDA),true) all: hub \ @@ -282,7 +282,7 @@ fetch_grid_scaler_resources: && cd ./.keda/scalers \ && 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 \ && 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 \ - && curl -L https://raw.githubusercontent.com/$(KEDA_BASED_NAME)/keda-docs/main/content/docs/2.16/scalers/selenium-grid-scaler.md -o selenium-grid-scaler.md + && 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 fetch_grid_scaler_images: docker pull --platform linux/amd64 --platform linux/arm64 $(KEDA_BASED_NAME)/keda:$(KEDA_BASED_TAG) diff --git a/tests/build-backward-compatible/cdp-matrix.yml b/tests/build-backward-compatible/cdp-matrix.yml index 5b52efb9f7..46bd716490 100644 --- a/tests/build-backward-compatible/cdp-matrix.yml +++ b/tests/build-backward-compatible/cdp-matrix.yml @@ -13,8 +13,8 @@ matrix: FIREFOX_DOWNLOAD_URL: 'https://download-installer.cdn.mozilla.net/pub/firefox/nightly/2024/11/2024-11-25-09-40-45-mozilla-central/firefox-134.0a1.en-US.linux-aarch64.deb' FIREFOX_PLATFORMS: 'linux/amd64,linux/arm64' 133: - EDGE_VERSION: - CHROME_VERSION: + EDGE_VERSION: 'microsoft-edge-stable=132.0.2957.140-1' + CHROME_VERSION: 'google-chrome-stable=132.0.6834.159-1' FIREFOX_VERSION: '133.0.3' FIREFOX_DOWNLOAD_URL: 'https://download-installer.cdn.mozilla.net/pub/firefox/nightly/2024/10/2024-10-28-09-56-35-mozilla-central/firefox-133.0a1.en-US.linux-aarch64.deb' FIREFOX_PLATFORMS: 'linux/amd64,linux/arm64'