From 09491510b2fee4b09fef1cb6bc465fc99601d67f Mon Sep 17 00:00:00 2001 From: Viet Nguyen Duc Date: Wed, 18 Sep 2024 17:49:28 +0000 Subject: [PATCH] Improve Selenium Grid Scaler on base KEDA core 2.15 Signed-off-by: Viet Nguyen Duc --- .github/workflows/release-patch.yml | 134 ++ Makefile | 16 +- pkg/scalers/selenium_grid_scaler.go | 243 +++- pkg/scalers/selenium_grid_scaler_test.go | 1410 +++++++++++++++++++--- tests/scalers/selenium/selenium_test.go | 79 +- 5 files changed, 1620 insertions(+), 262 deletions(-) create mode 100644 .github/workflows/release-patch.yml diff --git a/.github/workflows/release-patch.yml b/.github/workflows/release-patch.yml new file mode 100644 index 00000000000..a2833b6e7aa --- /dev/null +++ b/.github/workflows/release-patch.yml @@ -0,0 +1,134 @@ +name: release-patch +on: + push: + workflow_dispatch: + +jobs: + build: + name: Push Release + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + id-token: write # needed for signing the images with GitHub OIDC Token **not production ready** + + # keda-tools is built from github.com/test-tools/tools/Dockerfile + container: ghcr.io/kedacore/keda-tools:1.22.5 + steps: + - name: Check out code + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + with: + fetch-depth: 1 + + - name: Register workspace path + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - id: go-paths + run: | + echo "mod_cache=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT + echo "build_cache=$(go env GOCACHE)" >> $GITHUB_OUTPUT + + - name: Go modules cache + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + path: ${{ steps.go-paths.outputs.mod_cache }} + key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} + + - name: Go build cache + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + path: ${{ steps.go-paths.outputs.build_cache }} + key: ${{ runner.os }}-go-build-cache-${{ hashFiles('**/go.sum') }} + + - name: Go modules sync + run: go mod tidy -compat=1.22 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + # Username used to log in to a Docker registry. If not set then no login will occur + username: ${{secrets.DOCKER_USERNAME}} + # Password or personal access token used to log in to a Docker registry. If not set then no login will occur + password: ${{secrets.DOCKER_PASSWORD}} + # Server address of Docker registry. If not set then will default to Docker Hub + registry: docker.io + + - name: Get the version + id: get_version + run: echo "VERSION=${GITHUB_REF#refs/heads/release/v}-selenium-grid" >> $GITHUB_OUTPUT + +# - name: Release Deployment YAML file +# run: IMAGE_REPO=${IMAGE_REPO} IMAGE_REGISTRY=${IMAGE_REGISTRY} make release +# env: +# IMAGE_REPO: ${{ vars.DOCKER_NAMESPACE || 'selenium' }} +# IMAGE_REGISTRY: docker.io +# VERSION: ${{ steps.get_version.outputs.VERSION }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Publish KEDA images on GitHub Container Registry + run: IMAGE_REPO=${IMAGE_REPO} IMAGE_REGISTRY=${IMAGE_REGISTRY} make publish-multiarch + env: + IMAGE_REPO: ${{ vars.DOCKER_NAMESPACE || 'selenium' }} + IMAGE_REGISTRY: docker.io + VERSION: ${{ steps.get_version.outputs.VERSION }} + + # https://github.com/sigstore/cosign-installer + - name: Install Cosign + uses: sigstore/cosign-installer@v3 + + - name: Check Cosign install! + run: cosign version + + - name: Sign KEDA images published on GitHub Container Registry + # This step uses the identity token to provision an ephemeral certificate + # against the sigstore community Fulcio instance. + run: IMAGE_REPO=${IMAGE_REPO} IMAGE_REGISTRY=${IMAGE_REGISTRY} make sign-images + env: + IMAGE_REPO: ${{ vars.DOCKER_NAMESPACE || 'selenium' }} + IMAGE_REGISTRY: docker.io + VERSION: ${{ steps.get_version.outputs.VERSION }} + + # Get release information to determine id of the current release + - name: Get Release + id: get-release-info + uses: bruceadams/get-release@v1.3.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +# # Upload deployment YAML file to GitHub release +# - name: Upload Deployment YAML file +# id: upload-deployment-yaml +# uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1 +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# with: +# upload_url: https://uploads.github.com/repos/${{ vars.AUTHORS || 'SeleniumHQ' }}/keda/releases/${{ steps.get-release-info.outputs.id }}/assets?name=keda-${{ steps.get_version.outputs.VERSION }}.yaml +# asset_path: keda-${{ steps.get_version.outputs.VERSION }}.yaml +# asset_name: keda-${{ steps.get_version.outputs.VERSION }}.yaml +# asset_content_type: application/x-yaml +# +# # Upload core deployment YAML file to GitHub release (TO BE DELETED FOR v2.12) +# - name: Upload Deployment YAML file +# id: upload-core-deployment-yaml +# uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1 +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# with: +# upload_url: https://uploads.github.com/repos/${{ vars.AUTHORS || 'SeleniumHQ' }}/keda/releases/${{ steps.get-release-info.outputs.id }}/assets?name=keda-${{ steps.get_version.outputs.VERSION }}-core.yaml +# asset_path: keda-${{ steps.get_version.outputs.VERSION }}-core.yaml +# asset_name: keda-${{ steps.get_version.outputs.VERSION }}-core.yaml +# asset_content_type: application/x-yaml +# +# # Upload CRD deployment YAML file to GitHub release +# - name: Upload Deployment YAML file +# id: upload-crd-deployment-yaml +# uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1 +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# with: +# upload_url: https://uploads.github.com/repos/${{ vars.AUTHORS || 'SeleniumHQ' }}/keda/releases/${{ steps.get-release-info.outputs.id }}/assets?name=keda-${{ steps.get_version.outputs.VERSION }}-crds.yaml +# asset_path: keda-${{ steps.get_version.outputs.VERSION }}-crds.yaml +# asset_name: keda-${{ steps.get_version.outputs.VERSION }}-crds.yaml +# asset_content_type: application/x-yaml diff --git a/Makefile b/Makefile index 4ee8b59f2df..cb15e1500d8 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ GOBIN=$(shell go env GOBIN) endif GO_BUILD_VARS= GO111MODULE=on CGO_ENABLED=$(CGO) GOOS=$(TARGET_OS) GOARCH=$(ARCH) -GO_LDFLAGS="-X=github.com/kedacore/keda/v2/version.GitCommit=$(GIT_COMMIT) -X=github.com/kedacore/keda/v2/version.Version=$(VERSION)" +GO_LDFLAGS="-X=github.com/$(IMAGE_REPO)/keda/v2/version.GitCommit=$(GIT_COMMIT) -X=github.com/$(IMAGE_REPO)/keda/v2/version.Version=$(VERSION)" COSIGN_FLAGS ?= -y -a GIT_HASH=${GIT_COMMIT} -a GIT_VERSION=${VERSION} -a BUILD_DATE=${DATE} @@ -184,7 +184,7 @@ pkg/mock/mock_client/mock_interfaces.go: vendor/sigs.k8s.io/controller-runtime/p mkdir -p pkg/mock/mock_client $(MOCKGEN) sigs.k8s.io/controller-runtime/pkg/client Patch,Reader,Writer,StatusClient,StatusWriter,Client,WithWatch,FieldIndexer > $@ pkg/scalers/liiklus/mocks/mock_liiklus.go: - $(MOCKGEN) -destination=$@ github.com/kedacore/keda/v2/pkg/scalers/liiklus LiiklusServiceClient + $(MOCKGEN) -destination=$@ github.com/$(IMAGE_REPO)/keda/v2/pkg/scalers/liiklus LiiklusServiceClient ################################################## # Build # @@ -233,11 +233,11 @@ publish-multiarch: publish-controller-multiarch publish-adapter-multiarch publis release: manifests kustomize set-version ## Produce new KEDA release in keda-$(VERSION).yaml file. cd config/manager && \ - $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda=${IMAGE_CONTROLLER} + $(KUSTOMIZE) edit set image $(IMAGE_REGISTRY)/$(IMAGE_REPO)/keda=${IMAGE_CONTROLLER} cd config/metrics-server && \ - $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda-metrics-apiserver=${IMAGE_ADAPTER} + $(KUSTOMIZE) edit set image $(IMAGE_REGISTRY)/$(IMAGE_REPO)/keda-metrics-apiserver=${IMAGE_ADAPTER} cd config/webhooks && \ - $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda-admission-webhooks=${IMAGE_WEBHOOKS} + $(KUSTOMIZE) edit set image $(IMAGE_REGISTRY)/$(IMAGE_REPO)/keda-admission-webhooks=${IMAGE_WEBHOOKS} # Need this workaround to mitigate a problem with inserting labels into selectors, # until this issue is solved: https://github.com/kubernetes-sigs/kustomize/issues/1009 @sed -i".out" -e 's@version:[ ].*@version: $(VERSION)@g' config/default/kustomize-config/metadataLabelTransformer.yaml @@ -271,12 +271,12 @@ uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified deploy: install ## Deploy controller to the K8s cluster specified in ~/.kube/config. cd config/manager && \ - $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda=${IMAGE_CONTROLLER} && \ + $(KUSTOMIZE) edit set image $(IMAGE_REGISTRY)/$(IMAGE_REPO)/keda=${IMAGE_CONTROLLER} && \ if [ "$(AZURE_RUN_WORKLOAD_IDENTITY_TESTS)" = true ]; then \ $(KUSTOMIZE) edit add label --force azure.workload.identity/use:true; \ fi cd config/metrics-server && \ - $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda-metrics-apiserver=${IMAGE_ADAPTER} + $(KUSTOMIZE) edit set image $(IMAGE_REGISTRY)/$(IMAGE_REPO)/keda-metrics-apiserver=${IMAGE_ADAPTER} if [ "$(AZURE_RUN_WORKLOAD_IDENTITY_TESTS)" = true ]; then \ cd config/service_account && \ @@ -297,7 +297,7 @@ deploy: install ## Deploy controller to the K8s cluster specified in ~/.kube/con fi cd config/webhooks && \ - $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda-admission-webhooks=${IMAGE_WEBHOOKS} + $(KUSTOMIZE) edit set image $(IMAGE_REGISTRY)/$(IMAGE_REPO)/keda-admission-webhooks=${IMAGE_WEBHOOKS} # Need this workaround to mitigate a problem with inserting labels into selectors, # until this issue is solved: https://github.com/kubernetes-sigs/kustomize/issues/1009 diff --git a/pkg/scalers/selenium_grid_scaler.go b/pkg/scalers/selenium_grid_scaler.go index 4e1f75667ad..842f0f9ca5a 100644 --- a/pkg/scalers/selenium_grid_scaler.go +++ b/pkg/scalers/selenium_grid_scaler.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "math" "net/http" "strings" @@ -30,50 +29,83 @@ type seleniumGridScalerMetadata struct { triggerIndex int URL string `keda:"name=url, order=triggerMetadata;authParams"` + Username string `keda:"name=username, order=triggerMetadata;authParams, optional"` + Password string `keda:"name=password, order=triggerMetadata;authParams, optional"` BrowserName string `keda:"name=browserName, order=triggerMetadata"` SessionBrowserName string `keda:"name=sessionBrowserName, order=triggerMetadata, optional"` ActivationThreshold int64 `keda:"name=activationThreshold, order=triggerMetadata, optional"` BrowserVersion string `keda:"name=browserVersion, order=triggerMetadata, optional, default=latest"` UnsafeSsl bool `keda:"name=unsafeSsl, order=triggerMetadata, optional, default=false"` PlatformName string `keda:"name=platformName, order=triggerMetadata, optional, default=linux"` + NodeMaxSessions int `keda:"name=nodeMaxSessions, order=triggerMetadata, optional, default=1"` TargetValue int64 } -type seleniumResponse struct { - Data data `json:"data"` +type SeleniumResponse struct { + Data Data `json:"data"` } -type data struct { - Grid grid `json:"grid"` - SessionsInfo sessionsInfo `json:"sessionsInfo"` +type Data struct { + Grid Grid `json:"grid"` + NodesInfo NodesInfo `json:"nodesInfo"` + SessionsInfo SessionsInfo `json:"sessionsInfo"` } -type grid struct { - MaxSession int `json:"maxSession"` - NodeCount int `json:"nodeCount"` +type Grid struct { + SessionCount int `json:"sessionCount"` + MaxSession int `json:"maxSession"` + TotalSlots int `json:"totalSlots"` } -type sessionsInfo struct { - SessionQueueRequests []string `json:"sessionQueueRequests"` - Sessions []seleniumSession `json:"sessions"` +type NodesInfo struct { + Nodes Nodes `json:"nodes"` } -type seleniumSession struct { +type SessionsInfo struct { + SessionQueueRequests []string `json:"sessionQueueRequests"` +} + +type Nodes []struct { + ID string `json:"id"` + Status string `json:"status"` + SessionCount int `json:"sessionCount"` + MaxSession int `json:"maxSession"` + SlotCount int `json:"slotCount"` + Stereotypes string `json:"stereotypes"` + Sessions Sessions `json:"sessions"` +} + +type ReservedNodes struct { + ID string `json:"id"` + MaxSession int `json:"maxSession"` + SlotCount int `json:"slotCount"` +} + +type Sessions []struct { ID string `json:"id"` Capabilities string `json:"capabilities"` - NodeID string `json:"nodeId"` + Slot Slot `json:"slot"` } -type capability struct { +type Slot struct { + ID string `json:"id"` + Stereotype string `json:"stereotype"` +} + +type Capability struct { BrowserName string `json:"browserName"` BrowserVersion string `json:"browserVersion"` PlatformName string `json:"platformName"` } +type Stereotypes []struct { + Slots int `json:"slots"` + Stereotype Capability `json:"stereotype"` +} + const ( DefaultBrowserVersion string = "latest" - DefaultPlatformName string = "linux" ) func NewSeleniumGridScaler(config *scalersconfig.ScalerConfig) (Scaler, error) { @@ -117,7 +149,7 @@ func parseSeleniumGridScalerMetadata(config *scalersconfig.ScalerConfig) (*selen return meta, nil } -// No cleanup required for selenium grid scaler +// No cleanup required for Selenium Grid scaler func (s *seleniumGridScaler) Close(context.Context) error { if s.httpClient != nil { s.httpClient.CloseIdleConnections() @@ -152,7 +184,7 @@ func (s *seleniumGridScaler) GetMetricSpecForScaling(context.Context) []v2.Metri func (s *seleniumGridScaler) getSessionsCount(ctx context.Context, logger logr.Logger) (int64, error) { body, err := json.Marshal(map[string]string{ - "query": "{ grid { maxSession, nodeCount }, sessionsInfo { sessionQueueRequests, sessions { id, capabilities, nodeId } } }", + "query": "{ grid { sessionCount, maxSession, totalSlots }, nodesInfo { nodes { id, status, sessionCount, maxSession, slotCount, stereotypes, sessions { id, capabilities, slot { id, stereotype } } } }, sessionsInfo { sessionQueueRequests } }", }) if err != nil { @@ -164,6 +196,10 @@ func (s *seleniumGridScaler) getSessionsCount(ctx context.Context, logger logr.L return -1, err } + if s.metadata.Username != "" && s.metadata.Password != "" { + req.SetBasicAuth(s.metadata.Username, s.metadata.Password) + } + res, err := s.httpClient.Do(req) if err != nil { return -1, err @@ -179,62 +215,165 @@ func (s *seleniumGridScaler) getSessionsCount(ctx context.Context, logger logr.L if err != nil { return -1, err } - v, err := getCountFromSeleniumResponse(b, s.metadata.BrowserName, s.metadata.BrowserVersion, s.metadata.SessionBrowserName, s.metadata.PlatformName, logger) + v, err := getCountFromSeleniumResponse(b, s.metadata.BrowserName, s.metadata.BrowserVersion, s.metadata.SessionBrowserName, s.metadata.PlatformName, s.metadata.NodeMaxSessions, logger) if err != nil { return -1, err } return v, nil } -func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion string, sessionBrowserName string, platformName string, logger logr.Logger) (int64, error) { +func countMatchingSlotsStereotypes(stereotypes Stereotypes, request Capability, browserName string, browserVersion string, sessionBrowserName string, platformName string) int { + var matchingSlots int + for _, stereotype := range stereotypes { + if checkCapabilitiesMatch(stereotype.Stereotype, request, browserName, browserVersion, sessionBrowserName, platformName) { + matchingSlots += stereotype.Slots + } + } + return matchingSlots +} + +func countMatchingSessions(sessions Sessions, request Capability, browserName string, browserVersion string, sessionBrowserName string, platformName string, logger logr.Logger) int { + var matchingSessions int + for _, session := range sessions { + var capability = Capability{} + if err := json.Unmarshal([]byte(session.Capabilities), &capability); err == nil { + if checkCapabilitiesMatch(capability, request, browserName, browserVersion, sessionBrowserName, platformName) { + matchingSessions++ + } + } else { + logger.Error(err, fmt.Sprintf("Error when unmarshaling session capabilities: %s", err)) + } + } + return matchingSessions +} + +func checkCapabilitiesMatch(capability Capability, requestCapability Capability, browserName string, browserVersion string, sessionBrowserName string, platformName string) bool { + // Ensure the logic should be aligned with DefaultSlotMatcher in Selenium Grid - SeleniumHQ/selenium/java/src/org/openqa/selenium/grid/data/DefaultSlotMatcher.java + // A browserName matches when one of the following conditions is met: + // 1. `browserName` in capability matches with `browserName` or `sessionBrowserName` in scaler metadata + // 2. `browserName` in request capability is empty or not provided + var browserNameMatches = strings.EqualFold(capability.BrowserName, browserName) || strings.EqualFold(capability.BrowserName, sessionBrowserName) || + requestCapability.BrowserName == "" + // A browserVersion matches when one of the following conditions is met: + // 1. `browserVersion` in request capability is empty or not provided or `stable` + // 2. `browserVersion` in capability matches with prefix of the scaler metadata `browserVersion` + // 3. `browserVersion` in scaler metadata is `latest` + var browserVersionMatches = requestCapability.BrowserVersion == "" || requestCapability.BrowserVersion == "stable" || + strings.HasPrefix(capability.BrowserVersion, browserVersion) || browserVersion == DefaultBrowserVersion + // A platformName matches when one of the following conditions is met: + // 1. `platformName` in request capability is empty or not provided + // 2. `platformName` in capability is empty or not provided + // 3. `platformName` in capability matches with the scaler metadata `platformName` + // 4. `platformName` in scaler metadata is empty or not provided + var platformNameMatches = requestCapability.PlatformName == "" || capability.PlatformName == "" || + strings.EqualFold(capability.PlatformName, platformName) || platformName == "" + return browserNameMatches && browserVersionMatches && platformNameMatches +} + +func checkNodeReservedSlots(reservedNodes []ReservedNodes, nodeID string, availableSlots int) int { + for _, reservedNode := range reservedNodes { + if strings.EqualFold(reservedNode.ID, nodeID) { + return reservedNode.SlotCount + } + } + return availableSlots +} + +func updateOrAddReservedNode(reservedNodes []ReservedNodes, nodeID string, slotCount int, maxSession int) []ReservedNodes { + for i, reservedNode := range reservedNodes { + if strings.EqualFold(reservedNode.ID, nodeID) { + // Update remaining available slots for the reserved node + reservedNodes[i].SlotCount = slotCount + return reservedNodes + } + } + // Add new reserved node if not found + return append(reservedNodes, ReservedNodes{ID: nodeID, SlotCount: slotCount, MaxSession: maxSession}) +} + +func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion string, sessionBrowserName string, platformName string, nodeMaxSessions int, logger logr.Logger) (int64, error) { + // The returned count of the number of new Nodes will be scaled up var count int64 - var seleniumResponse = seleniumResponse{} + // Track number of available slots of existing Nodes in the Grid can be reserved for the matched requests + var availableSlots int + // Track number of matched requests in the sessions queue will be served by this scaler + var queueSlots int + var seleniumResponse = SeleniumResponse{} if err := json.Unmarshal(b, &seleniumResponse); err != nil { return 0, err } var sessionQueueRequests = seleniumResponse.Data.SessionsInfo.SessionQueueRequests - for _, sessionQueueRequest := range sessionQueueRequests { - var capability = capability{} - if err := json.Unmarshal([]byte(sessionQueueRequest), &capability); err == nil { - if capability.BrowserName == browserName { - var platformNameMatches = capability.PlatformName == "" || strings.EqualFold(capability.PlatformName, platformName) - if strings.HasPrefix(capability.BrowserVersion, browserVersion) && platformNameMatches { - count++ - } else if len(strings.TrimSpace(capability.BrowserVersion)) == 0 && browserVersion == DefaultBrowserVersion && platformNameMatches { - count++ - } + var nodes = seleniumResponse.Data.NodesInfo.Nodes + // Track list of existing Nodes that have available slots for the matched requests + var reservedNodes []ReservedNodes + // Track list of new Nodes will be scaled up with number of available slots following scaler parameter `nodeMaxSessions` + var newRequestNodes []ReservedNodes + for requestIndex, sessionQueueRequest := range sessionQueueRequests { + var isRequestMatched bool + var requestCapability = Capability{} + if err := json.Unmarshal([]byte(sessionQueueRequest), &requestCapability); err == nil { + if checkCapabilitiesMatch(requestCapability, requestCapability, browserName, browserVersion, sessionBrowserName, platformName) { + queueSlots++ + isRequestMatched = true } } else { - logger.Error(err, fmt.Sprintf("Error when unmarshaling session queue requests: %s", err)) + logger.Error(err, fmt.Sprintf("Error when unmarshaling sessionQueueRequest capability: %s", err)) } - } - var sessions = seleniumResponse.Data.SessionsInfo.Sessions - for _, session := range sessions { - var capability = capability{} - if err := json.Unmarshal([]byte(session.Capabilities), &capability); err == nil { - var platformNameMatches = capability.PlatformName == "" || strings.EqualFold(capability.PlatformName, platformName) - if capability.BrowserName == sessionBrowserName { - if strings.HasPrefix(capability.BrowserVersion, browserVersion) && platformNameMatches { - count++ - } else if browserVersion == DefaultBrowserVersion && platformNameMatches { - count++ + // Skip the request if the capability does not match the scaler parameters + if !isRequestMatched { + continue + } + + var isRequestReserved bool + // Check if the matched request can be assigned to available slots of existing Nodes in the Grid + for _, node := range nodes { + // Check if node is UP and has available slots (maxSession > sessionCount) + if strings.EqualFold(node.Status, "UP") && checkNodeReservedSlots(reservedNodes, node.ID, node.MaxSession-node.SessionCount) > 0 { + var stereotypes = Stereotypes{} + var availableSlotsMatch int + if err := json.Unmarshal([]byte(node.Stereotypes), &stereotypes); err == nil { + // Count available slots that match the request capability and scaler metadata + availableSlotsMatch += countMatchingSlotsStereotypes(stereotypes, requestCapability, browserName, browserVersion, sessionBrowserName, platformName) + } else { + logger.Error(err, fmt.Sprintf("Error when unmarshaling node stereotypes: %s", err)) + } + // Count ongoing sessions that match the request capability and scaler metadata + var currentSessionsMatch = countMatchingSessions(node.Sessions, requestCapability, browserName, browserVersion, sessionBrowserName, platformName, 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 + if availableSlotsMatch > currentSessionsMatch { + availableSlots++ + reservedNodes = updateOrAddReservedNode(reservedNodes, node.ID, availableSlotsCanBeReserved-1, node.MaxSession) + isRequestReserved = true + break } } - } else { - logger.Error(err, fmt.Sprintf("Error when unmarshaling sessions info: %s", err)) + } + // 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 + if !isRequestReserved { + for _, newRequestNode := range newRequestNodes { + if newRequestNode.SlotCount > 0 { + newRequestNodes = updateOrAddReservedNode(newRequestNodes, newRequestNode.ID, newRequestNode.SlotCount-1, nodeMaxSessions) + isRequestReserved = true + break + } + } + } + // Check if a new Node should be scaled up to reserve for the matched request + if !isRequestReserved { + newRequestNodes = updateOrAddReservedNode(newRequestNodes, string(rune(requestIndex)), nodeMaxSessions-1, nodeMaxSessions) } } - var gridMaxSession = int64(seleniumResponse.Data.Grid.MaxSession) - var gridNodeCount = int64(seleniumResponse.Data.Grid.NodeCount) - - if gridMaxSession > 0 && gridNodeCount > 0 { - // Get count, convert count to next highest int64 - var floatCount = float64(count) / (float64(gridMaxSession) / float64(gridNodeCount)) - count = int64(math.Ceil(floatCount)) + if queueSlots > availableSlots { + count = int64(len(newRequestNodes)) + } else { + count = 0 } + return count, nil } diff --git a/pkg/scalers/selenium_grid_scaler_test.go b/pkg/scalers/selenium_grid_scaler_test.go index 95e24743c1e..c31c151be31 100644 --- a/pkg/scalers/selenium_grid_scaler_test.go +++ b/pkg/scalers/selenium_grid_scaler_test.go @@ -16,6 +16,7 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { sessionBrowserName string browserVersion string platformName string + nodeMaxSessions int } tests := []struct { name string @@ -29,7 +30,6 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { b: []byte(nil), browserName: "", }, - // want: 0, wantErr: true, }, { @@ -38,52 +38,290 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { b: []byte(""), browserName: "", }, - // want: resource.NewQuantity(0, resource.DecimalSI), wantErr: true, }, { - name: "no active sessions should return count as 0", + name: "no sessionQueueRequests should return count as 0", args: args{ b: []byte(`{ - "data": { - "grid":{ - "maxSession": 0, - "nodeCount": 0 + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 0, + "totalSlots": 0 + }, + "nodesInfo": { + "nodes": [] }, "sessionsInfo": { - "sessionQueueRequests": [], - "sessions": [] + "sessionQueueRequests": [] } + } } - }`), + `), browserName: "", }, want: 0, wantErr: false, }, { - name: "active sessions with no matching browsername should return count as 0", + name: "12 sessionQueueRequests with 4 requests matching browserName chrome should return count as 4", args: args{ b: []byte(`{ - "data": { - "grid":{ - "maxSession": 1, - "nodeCount": 1 + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 0, + "totalSlots": 0 + }, + "nodesInfo": { + "nodes": [] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\n \"browserName\": \"chrome\",\n \"goog:chromeOptions\": {\n \"extensions\": [\n ],\n \"args\": [\n \"disable-features=DownloadBubble,DownloadBubbleV2\"\n ]\n },\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_download_file (ChromeTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_with_frames (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_download_file (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_title_and_maximize_window (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"browserName\": \"chrome\",\n \"goog:chromeOptions\": {\n \"extensions\": [\n ],\n \"args\": [\n \"disable-features=DownloadBubble,DownloadBubbleV2\"\n ]\n },\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_play_video (ChromeTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"browserName\": \"chrome\",\n \"goog:chromeOptions\": {\n \"extensions\": [\n ],\n \"args\": [\n \"disable-features=DownloadBubble,DownloadBubbleV2\"\n ]\n },\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_select_from_a_dropdown (ChromeTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_visit_basic_auth_secured_page (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_select_from_a_dropdown (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"browserName\": \"chrome\",\n \"goog:chromeOptions\": {\n \"extensions\": [\n ],\n \"args\": [\n \"disable-features=DownloadBubble,DownloadBubbleV2\"\n ]\n },\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_title (ChromeTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_title (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_accept_languages (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_play_video (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}" + ] + } + } + } + `), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + }, + want: 4, + wantErr: false, + }, + { + name: "2 sessionQueueRequests and 1 available nodeStereotypes with matching browserName firefox should return count as 1", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 7, + "totalSlots": 7 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\"\n}"], - "sessions": [ + "nodesInfo": { + "nodes": [ + { + "id": "82ee33bd-390e-4dd6-aee2-06b17ecee18e", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-m5n8z-4br6x\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "reserved", + "capabilities": "{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-m5n8z-4br6x\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}", + "slot": { + "id": "83c9d9f5-f79d-4dea-bc9b-ce61bf2bc01c", + "stereotype": "{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-m5n8z-4br6x\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}" + } } - ] + ] + }, + { + "id": "b4d3d31a-3239-4c09-a5f5-3650d4fcef48", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-s2gq6-82lwb\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "reserved", + "capabilities": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-s2gq6-82lwb\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}", + "slot": { + "id": "b03b80c0-95f8-4b9c-ba06-bebd2568ce3d", + "stereotype": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-s2gq6-82lwb\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}" + } + } + ] + }, + { + "id": "f3e67bf7-3c40-42d4-ab10-666b49c88925", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-xh95p-9c2cl\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [] + }, + { + "id": "f1e315fe-5f32-4a73-bb31-b73ed9a728e5", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-j2xbn-lq76c\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "reserved", + "capabilities": "{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-j2xbn-lq76c\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}", + "slot": { + "id": "9d91cd87-b443-4a0c-93e7-eea8c4661207", + "stereotype": "{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-j2xbn-lq76c\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}" + } + } + ] + }, + { + "id": "0ae48415-a230-4bc4-a26c-4fc4ffc3abc1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-xk6mm-2m6jh\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "reserved", + "capabilities": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-xk6mm-2m6jh\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}", + "slot": { + "id": "2c1fc5c4-881a-48fd-9b9e-b4d3ecbc1bd8", + "stereotype": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-xk6mm-2m6jh\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}" + } + } + ] + }, + { + "id": "284fa982-5be0-44a6-b64e-e2e76fe52d1f", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-bvq59-6dh6q\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "reserved", + "capabilities": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-bvq59-6dh6q\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}", + "slot": { + "id": "5f8f9ba0-0f61-473e-b367-b68d9368dc24", + "stereotype": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-bvq59-6dh6q\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}" + } + } + ] + }, + { + "id": "451442d0-3649-4b21-a5a5-32bc847f1765", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-42xbf-zpdd4\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [] + }, + { + "id": "a4d26330-e5be-4630-b4da-9078f2495ece", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-qt9z2-6xx86\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "reserved", + "capabilities": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-qt9z2-6xx86\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}", + "slot": { + "id": "38bd0b09-ffe0-46e9-8983-bd208270c8da", + "stereotype": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-qt9z2-6xx86\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}" + } + } + ] + }, + { + "id": "e81f0038-fc72-4045-9de1-b98143053eae", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-v7nrv-xsfkb\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "reserved", + "capabilities": "{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-v7nrv-xsfkb\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}", + "slot": { + "id": "43b992cc-39bb-4b0f-92b6-99603a543459", + "stereotype": "{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-v7nrv-xsfkb\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}" + } + } + ] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_accept_languages (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_play_video (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}" + ] } + } } - }`), - browserName: "", - sessionBrowserName: "", + `), + browserName: "firefox", + sessionBrowserName: "firefox", + browserVersion: "latest", + platformName: "linux", + }, + want: 1, + wantErr: false, + }, + { + name: "1 sessionQueueRequests and 1 available nodeStereotypes with matching browserName chrome should return count as 0", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 0, + "totalSlots": 0 + }, + "nodesInfo": { + "nodes": [ + { + "id": "f3e67bf7-3c40-42d4-ab10-666b49c88925", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-xh95p-9c2cl\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [] + }, + { + "id": "451442d0-3649-4b21-a5a5-32bc847f1765", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-42xbf-zpdd4\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_accept_languages (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"browserName\": \"chrome\",\n \"goog:chromeOptions\": {\n \"extensions\": [\n ],\n \"args\": [\n \"disable-features=DownloadBubble,DownloadBubbleV2\"\n ]\n },\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_visit_basic_auth_secured_page (ChromeTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}" + ] + } + } + } + `), + browserName: "chrome", + sessionBrowserName: "chrome", browserVersion: "latest", platformName: "linux", }, @@ -91,73 +329,290 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { wantErr: false, }, { - name: "active sessions with matching browsername should return count as 2", + name: "1 sessionQueueRequests Linux and 1 available nodeStereotypes Windows with matching browserName chrome should return count as 1", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 2, + "totalSlots": 2 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Windows 11\"}}]", + "sessions": [] + }, + { + "id": "node-2", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"firefox\", \"browserVersion\": \"130.0\", \"platformName\": \"Windows 11\"}}]", + "sessions": [] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"firefox\", \"browserVersion\": \"130.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}" + ] + } + } + } + `), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + }, + want: 1, + wantErr: false, + }, + { + name: "scaler browserVersion is latest, 2 sessionQueueRequests wihtout browserVersion, 2 available nodeStereotypes with different versions and platforms, should return count as 1", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 0, + "totalSlots": 0 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [] + }, + { + "id": "node-2", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"92.0\", \"platformName\": \"Windows 11\"}}]", + "sessions": [] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}" + ] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + }, + want: 1, + wantErr: false, + }, + { + 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", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 6, + "totalSlots": 6 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 0, + "maxSession": 3, + "slotCount": 3, + "stereotypes": "[{\"slots\": 3, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [] + }, + { + "id": "node-2", + "status": "UP", + "sessionCount": 0, + "maxSession": 3, + "slotCount": 3, + "stereotypes": "[{\"slots\": 3, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"92.0\", \"platformName\": \"Windows 11\"}}]", + "sessions": [] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"Windows 11\"}" + ] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + }, + want: 1, + wantErr: false, + }, + { + name: "queue request with browserName browserVersion and browserVersion but no available nodes should return count as 1", args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 1, "maxSession": 1, - "nodeCount": 1 + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"firefox\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"firefox\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] + } + ] }, "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\"\n}"], - "sessions": [] + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] } } }`), browserName: "chrome", sessionBrowserName: "chrome", - browserVersion: "latest", + browserVersion: "91.0", platformName: "linux", }, - want: 2, + want: 1, wantErr: false, }, { - name: "2 session queue with matching browsername and browserversion should return count as 1", + name: "1 queue request with browserName browserVersion and browserVersion but 2 nodes without available slots should return count as 1", args: args{ b: []byte(`{ "data": { - "grid":{ - "maxSession": 4, - "nodeCount": 2 + "grid": { + "sessionCount": 2, + "maxSession": 2, + "totalSlots": 2 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] + }, + { + "id": "node-2", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-2", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] + } + ] }, "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\"\n}"] + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] } } }`), browserName: "chrome", sessionBrowserName: "chrome", - browserVersion: "latest", + browserVersion: "91.0", platformName: "linux", }, want: 1, wantErr: false, }, { - name: "2 active sessions with matching browsername on 2 nodes and maxSession=4 should return count as 1 (rounded up from 0.75)", + name: "2 session queue with matching browsername and browserversion of 2 available slots should return count as 0", args: args{ b: []byte(`{ "data": { - "grid":{ - "maxSession": 4, - "nodeCount": 1 + "grid": { + "sessionCount": 0, + "maxSession": 2, + "totalSlots": 2 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\"\n}"], - "sessions": [ + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [] }, { - "id": "0f9c5a941aa4d755a54b84be1f6535b2", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983d" + "id": "node-2", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] } } }`), @@ -166,66 +621,162 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "91.0", platformName: "linux", }, - want: 1, + want: 0, wantErr: false, }, { - name: "2 active sessions with matching browsername on 1 node and maxSession=3 should return count as 1 (rounded up from 0.33)", + name: "2 queue requests with browserName browserVersion and platformName matching 2 available slots on 2 different nodes should return count as 0", args: args{ b: []byte(`{ "data": { - "grid":{ - "maxSession": 3, - "nodeCount": 1 + "grid": { + "sessionCount": 2, + "maxSession": 4, + "totalSlots": 4 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\"\n}"], - "sessions": [ + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 2, + "slotCount": 2, + "stereotypes": "[{\"slots\": 2, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] }, { - "id": "0f9c5a941aa4d755a54b84be1f6535b2", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983d" + "id": "node-2", + "status": "UP", + "sessionCount": 1, + "maxSession": 2, + "slotCount": 2, + "stereotypes": "[{\"slots\": 2, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-2", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] } } }`), browserName: "chrome", sessionBrowserName: "chrome", - browserVersion: "latest", + browserVersion: "91.0", platformName: "linux", }, - want: 1, + want: 0, wantErr: false, }, { - name: "2 active sessions with matching browsername on 2 nodes should return count as 5", + name: "1 queue request with browserName browserVersion and platformName matching 1 available slot on node has 3 max sessions should return count as 0", args: args{ b: []byte(`{ "data": { - "grid":{ - "maxSession": 2, - "nodeCount": 2 + "grid": { + "sessionCount": 2, + "maxSession": 3, + "totalSlots": 3 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 2, + "maxSession": 3, + "slotCount": 3, + "stereotypes": "[{\"slots\": 3, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + }, + { + "id": "session-2", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] + } + ] }, "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}"], - "sessions": [ + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "91.0", + platformName: "linux", + }, + want: 0, + wantErr: false, + }, + { + name: "3 queue requests with browserName browserVersion and platformName but 2 running nodes are busy should return count as 3", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 2, + "maxSession": 2, + "totalSlots": 2 + }, + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] }, { - "id": "0f9c5a941aa4d755a54b84be1f6535b2", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983d" + "id": "node-2", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-2", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] } } }`), @@ -234,32 +785,57 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "91.0", platformName: "linux", }, - want: 5, + want: 3, wantErr: false, }, { - name: "2 active sessions with matching browsername on 2 nodes with 3 other versions in queue should return count as 2 with default browserVersion and PlatformName", + name: "3 queue requests with browserName browserVersion and platformName but 2 running nodes are busy with different versions should return count as 3", args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 2, "maxSession": 2, - "nodeCount": 2 + "totalSlots": 2 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}"], - "sessions": [ + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] }, { - "id": "0f9c5a941aa4d755a54b84be1f6535b2", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983d" + "id": "node-2", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-2", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"90.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"92.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"93.0\", \"platformName\": \"linux\"}" + ] } } }`), @@ -268,32 +844,57 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "latest", platformName: "linux", }, - want: 2, + want: 3, wantErr: false, }, { - name: "2 active sessions with matching browsername on 2 nodes should return count as 5 with default browserVersion / PlatformName and incoming sessions do not have versions", + name: "3 queue requests with browserName and platformName but 2 running nodes are busy with different versions should return count as 3", args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 2, "maxSession": 2, - "nodeCount": 2 + "totalSlots": 2 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\"}","{\n \"browserName\": \"chrome\"}","{\n \"browserName\": \"chrome\"}"], - "sessions": [ + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] }, { - "id": "0f9c5a941aa4d755a54b84be1f6535b2", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983d" + "id": "node-2", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-2", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}" + ] } } }`), @@ -302,7 +903,7 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "latest", platformName: "linux", }, - want: 5, + want: 3, wantErr: false, }, { @@ -310,19 +911,34 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 1, "maxSession": 1, - "nodeCount": 1 + "totalSlots": 1 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\"\n}"], - "sessions": [ + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] } } }`), @@ -335,23 +951,163 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { wantErr: false, }, { - name: "1 active msedge session with matching browsername/sessionBrowserName should return count as 3", + name: "1 request without browserName and browserVersion stable can be match any available node should return count as 0", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 1, + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"v128.0\", \"platformName\": \"linux\"}}]", + "sessions": [] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserVersion\": \"stable\", \"platformName\": \"linux\"}" + ] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + }, + want: 0, + wantErr: false, + }, + { + name: "1 request without browserName and browserVersion stable should return count as 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\": \"v128.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"v128.0\", \"platformName\": \"linux\"}" + } + ] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserVersion\": \"stable\", \"platformName\": \"linux\"}" + ] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + }, + want: 1, + wantErr: false, + }, + { + name: "2 queue requests with browserName in string match node stereotype and scaler metadata browserVersion should return count as 1", args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 1, "maxSession": 1, - "nodeCount": 1 + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"msedge\", \"browserVersion\": \"dev\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"msedge\", \"browserVersion\": \"dev\", \"platformName\": \"linux\"}" + } + ] + } + ] }, "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"MicrosoftEdge\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"MicrosoftEdge\",\n \"browserVersion\": \"91.0\"\n}"], - "sessions": [ + "sessionQueueRequests": [ + "{\"browserName\": \"MicrosoftEdge\", \"browserVersion\": \"beta\", \"platformName\": \"linux\"}", + "{\"browserName\": \"MicrosoftEdge\", \"browserVersion\": \"dev\", \"platformName\": \"linux\"}" + ] + } + } + }`), + browserName: "MicrosoftEdge", + sessionBrowserName: "msedge", + browserVersion: "dev", + platformName: "linux", + }, + want: 1, + wantErr: false, + }, + { + name: "2 queue requests with matching browsername/sessionBrowserName but 1 node is busy should return count as 2", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 1, + "maxSession": 1, + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"msedge\",\n \"browserVersion\": \"91.0.4472.114\",\n \"msedge\": {\n \"msedgedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"ms:edgeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"msedge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"msedge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"MicrosoftEdge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"MicrosoftEdge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] } } }`), @@ -360,27 +1116,124 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "91.0", platformName: "linux", }, - want: 3, + want: 2, wantErr: false, }, { - name: "1 active msedge session while asking for 2 chrome sessions should return a count of 2", + name: "2 queue requests with matching browsername/sessionBrowserName and 1 node is is available should return count as 1", args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 0, "maxSession": 1, - "nodeCount": 1 + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"msedge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [] + } + ] }, "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\"\n}"], - "sessions": [ + "sessionQueueRequests": [ + "{\"browserName\": \"MicrosoftEdge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"MicrosoftEdge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] + } + } + }`), + browserName: "MicrosoftEdge", + sessionBrowserName: "msedge", + browserVersion: "91.0", + platformName: "linux", + }, + want: 1, + wantErr: false, + }, { + name: "2 queue requests with platformName and without platformName and node with 1 slot available should return count as 1", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 1, + "maxSession": 2, + "totalSlots": 2 + }, + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"msedge\",\n \"browserVersion\": \"91.0.4472.114\",\n \"msedge\": {\n \"msedgedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"ms:edgeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 2, + "slotCount": 2, + "stereotypes": "[{\"slots\": 2, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"Windows 11\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"Windows 11\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"Windows 11\"}" + ] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "91.0", + platformName: "Windows 11", + }, + want: 1, + wantErr: false, + }, + { + name: "1 active msedge session while asking for 2 chrome sessions should return a count of 2", + 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\": \"msedge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"msedge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}" + ] } } }`), @@ -393,23 +1246,39 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { wantErr: false, }, { - name: "1 active msedge session with maxSessions=3 while asking for 3 chrome sessions should return a count of 1", + name: "3 queue requests browserName chrome platformName linux but 1 node has maxSessions=3 with browserName msedge should return a count of 3", args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 1, "maxSession": 3, - "nodeCount": 1 + "totalSlots": 3 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\"\n}"], - "sessions": [ + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"msedge\",\n \"browserVersion\": \"91.0.4472.114\",\n \"msedge\": {\n \"msedgedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"ms:edgeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 3, + "slotCount": 3, + "stereotypes": "[{\"slots\": 3, \"stereotype\": {\"browserName\": \"msedge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"msedge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}" + ] } } }`), @@ -418,7 +1287,7 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "latest", platformName: "linux", }, - want: 1, + want: 3, wantErr: false, }, { @@ -426,36 +1295,58 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { args: args{ b: []byte(`{ "data": { - "grid":{ - "maxSession": 1, - "nodeCount": 1 + "grid": { + "maxSession": 0, + "nodeCount": 0, + "totalSlots": 0 + }, + "nodesInfo": { + "nodes": [] }, "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\",\n \"platformName\": \"Windows 11\"\n}"], - "sessions": [] + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"Windows 11\"}" + ] } } }`), browserName: "chrome", sessionBrowserName: "chrome", browserVersion: "latest", - platformName: "Windows 11", + platformName: "", }, want: 2, wantErr: false, }, { - name: "sessions requests with matching browsername and platformName should return count as 1", + name: "2 queue requests with 1 matching browsername and platformName and 1 existing slot is available should return count as 0", args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 0, "maxSession": 1, - "nodeCount": 1 + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"Windows 11\"}}]", + "sessions": [] + } + ] }, "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"platformName\": \"linux\"\n}","{\n \"browserName\": \"chrome\",\n \"platformName\": \"Windows 11\"\n}"], - "sessions": [] + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"Windows 11\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}" + ] } } }`), @@ -464,32 +1355,46 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "latest", platformName: "Windows 11", }, - want: 1, + want: 0, wantErr: false, }, { - name: "sessions requests and active sessions with matching browsername and platformName should return count as 2", + name: "2 queue requests with 1 request matching browserName and platformName but 1 existing node is busy should return count as 1", args: args{ b: []byte(`{ "data": { - "grid":{ - "maxSession": 1, - "nodeCount": 1 + "grid": { + "sessionCount": 2, + "maxSession": 2, + "totalSlots": 2 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"platformName\": \"linux\"\n}","{\n \"browserName\": \"chrome\",\n \"platformName\": \"Windows 11\",\n \"browserVersion\": \"91.0\"\n}"], - "sessions": [ + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"Windows 11\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" - }, - { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "82ee33bd-390e-4dd6-aee2-06b17ecee18e", + "status": "UP", + "sessionCount": 2, + "maxSession": 2, + "slotCount": 2, + "stereotypes": "[\n {\n \"slots\": 2,\n \"stereotype\": {\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"se:containerName\": \"my-chrome-name-m5n8z-4br6x\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "0f9c5a941aa4d755a54b84be1f6535b1", + "capabilities": "{\"browserName\": \"chrome\", \"platformName\": \"Windows 11\", \"browserVersion\": \"91.0\"}" + }, + { + "id": "0f9c5a941aa4d755a54b84be1f6535b1", + "capabilities": "{\"browserName\": \"chrome\", \"platformName\": \"linux\", \"browserVersion\": \"91.0\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"Windows 11\"}" + ] } } }`), @@ -498,13 +1403,133 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "91.0", platformName: "Windows 11", }, + want: 1, + wantErr: false, + }, + { + name: "5 queue requests with scaler parameter nodeMaxSessions is 2 should return count as 3", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 0, + "totalSlots": 0 + }, + "nodesInfo": { + "nodes": [] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}" + ] + } + } + } + `), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + nodeMaxSessions: 2, + }, + want: 3, + wantErr: false, + }, + { + name: "5 queue requests with scaler parameter nodeMaxSessions is 3 should return count as 2", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 0, + "totalSlots": 0 + }, + "nodesInfo": { + "nodes": [] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}" + ] + } + } + } + `), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + nodeMaxSessions: 3, + }, + want: 2, + wantErr: false, + }, + { + name: "5 queue requests with request matching browserName and platformName and scaler param nodeMaxSessions is 3 and existing node with 1 available slot should return count as 2", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 2, + "maxSession": 3, + "totalSlots": 3 + }, + "nodesInfo": { + "nodes": [ + { + "id": "82ee33bd-390e-4dd6-aee2-06b17ecee18e", + "status": "UP", + "sessionCount": 2, + "maxSession": 3, + "slotCount": 3, + "stereotypes": "[\n {\n \"slots\": 3,\n \"stereotype\": {\n \"browserName\": \"chrome\",\n \"platformName\": \"linux\",\n \"browserVersion\": \"91.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"se:containerName\": \"my-chrome-name-m5n8z-4br6x\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "0f9c5a941aa4d755a54b84be1f6535b1", + "capabilities": "{\"browserName\": \"chrome\", \"platformName\": \"Linux\", \"browserVersion\": \"91.0\"}" + }, + { + "id": "0f9c5a941aa4d755a54b84be1f6535b1", + "capabilities": "{\"browserName\": \"chrome\", \"platformName\": \"linux\", \"browserVersion\": \"91.0\"}" + } + ] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}" + ] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "91.0", + platformName: "linux", + nodeMaxSessions: 3, + }, want: 2, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := getCountFromSeleniumResponse(tt.args.b, tt.args.browserName, tt.args.browserVersion, tt.args.sessionBrowserName, tt.args.platformName, logr.Discard()) + got, err := getCountFromSeleniumResponse(tt.args.b, tt.args.browserName, tt.args.browserVersion, tt.args.sessionBrowserName, tt.args.platformName, tt.args.nodeMaxSessions, logr.Discard()) if (err != nil) != tt.wantErr { t.Errorf("getCountFromSeleniumResponse() error = %v, wantErr %v", err, tt.wantErr) return @@ -564,6 +1589,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { TargetValue: 1, BrowserVersion: "latest", PlatformName: "linux", + NodeMaxSessions: 1, }, }, { @@ -585,6 +1611,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { TargetValue: 1, BrowserVersion: "latest", PlatformName: "linux", + NodeMaxSessions: 1, }, }, { @@ -592,7 +1619,9 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { args: args{ config: &scalersconfig.ScalerConfig{ AuthParams: map[string]string{ - "url": "http://user:password@selenium-hub:4444/graphql", + "url": "http://selenium-hub:4444/graphql", + "username": "user", + "password": "password", }, TriggerMetadata: map[string]string{ "browserName": "MicrosoftEdge", @@ -602,12 +1631,15 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { }, wantErr: false, want: &seleniumGridScalerMetadata{ - URL: "http://user:password@selenium-hub:4444/graphql", + URL: "http://selenium-hub:4444/graphql", + Username: "user", + Password: "password", BrowserName: "MicrosoftEdge", SessionBrowserName: "msedge", TargetValue: 1, BrowserVersion: "latest", PlatformName: "linux", + NodeMaxSessions: 1, }, }, { @@ -631,6 +1663,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { BrowserVersion: "91.0", UnsafeSsl: false, PlatformName: "linux", + NodeMaxSessions: 1, }, }, { @@ -656,6 +1689,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { BrowserVersion: "91.0", UnsafeSsl: true, PlatformName: "linux", + NodeMaxSessions: 1, }, }, { @@ -696,6 +1730,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { BrowserVersion: "91.0", UnsafeSsl: true, PlatformName: "linux", + NodeMaxSessions: 1, }, }, { @@ -722,6 +1757,39 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { BrowserVersion: "91.0", UnsafeSsl: true, PlatformName: "Windows 11", + NodeMaxSessions: 1, + }, + }, + { + name: "valid url, browsername, unsafeSsl, activationThreshold, nodeMaxSessions and platformName should return metadata", + args: args{ + config: &scalersconfig.ScalerConfig{ + TriggerMetadata: map[string]string{ + "url": "http://selenium-hub:4444/graphql", + "username": "user", + "password": "password", + "browserName": "chrome", + "browserVersion": "91.0", + "unsafeSsl": "true", + "activationThreshold": "10", + "platformName": "Windows 11", + "nodeMaxSessions": "3", + }, + }, + }, + wantErr: false, + want: &seleniumGridScalerMetadata{ + URL: "http://selenium-hub:4444/graphql", + Username: "user", + Password: "password", + BrowserName: "chrome", + SessionBrowserName: "chrome", + TargetValue: 1, + ActivationThreshold: 10, + BrowserVersion: "91.0", + UnsafeSsl: true, + PlatformName: "Windows 11", + NodeMaxSessions: 3, }, }, } diff --git a/tests/scalers/selenium/selenium_test.go b/tests/scalers/selenium/selenium_test.go index 8d4b43cef19..b5751ff2159 100644 --- a/tests/scalers/selenium/selenium_test.go +++ b/tests/scalers/selenium/selenium_test.go @@ -33,6 +33,8 @@ var ( hubHost = fmt.Sprintf("selenium-hub.%s", testNamespace) hubPort = 4444 hubGraphURL = fmt.Sprintf("http://%s:%d/graphql", hubHost, hubPort) + HubBasicAuthUsername = "admin" + HubBasicAuthPassword = "admin" minReplicaCount = 0 maxReplicaCount = 1 ) @@ -46,6 +48,8 @@ type templateData struct { HubHost string HubPort int HubGraphURL string + HubBasicAuthUsername string + HubBasicAuthPassword string WithVersion bool JobName string ScaledObjectName string @@ -63,9 +67,9 @@ metadata: labels: app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest data: SE_EVENT_BUS_HOST: selenium-hub SE_EVENT_BUS_PUBLISH_PORT: "4442" @@ -82,9 +86,9 @@ metadata: name: selenium-chrome-node app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: type: ClusterIP selector: @@ -107,9 +111,9 @@ metadata: app.kubernetes.io/name: selenium-chrome-node app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: replicas: 0 selector: @@ -123,7 +127,7 @@ spec: spec: containers: - name: selenium-chrome-node - image: selenium/node-chrome:4.0.0-rc-1-prerelease-20210618 + image: selenium/node-chrome:latest imagePullPolicy: IfNotPresent envFrom: - configMapRef: @@ -157,6 +161,8 @@ spec: - type: selenium-grid metadata: url: '{{.HubGraphURL}}' + username: '{{.HubBasicAuthUsername}}' + password: '{{.HubBasicAuthPassword}}' browserName: 'chrome' activationThreshold: '1' ` @@ -171,9 +177,9 @@ metadata: name: selenium-firefox-node app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: type: ClusterIP selector: @@ -195,9 +201,9 @@ metadata: app.kubernetes.io/name: selenium-firefox-node app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: replicas: 0 selector: @@ -211,7 +217,7 @@ spec: spec: containers: - name: selenium-firefox-node - image: selenium/node-firefox:4.0.0-rc-1-prerelease-20210618 + image: selenium/node-firefox:latest imagePullPolicy: IfNotPresent envFrom: - configMapRef: @@ -245,6 +251,8 @@ spec: - type: selenium-grid metadata: url: '{{.HubGraphURL}}' + username: '{{.HubBasicAuthUsername}}' + password: '{{.HubBasicAuthPassword}}' browserName: 'firefox' activationThreshold: '1' ` @@ -259,9 +267,9 @@ metadata: name: selenium-edge-node app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: type: ClusterIP selector: @@ -284,9 +292,9 @@ metadata: app.kubernetes.io/name: selenium-edge-node app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: replicas: 0 selector: @@ -300,7 +308,7 @@ spec: spec: containers: - name: selenium-edge-node - image: selenium/node-edge:4.0.0-rc-1-prerelease-20210618 + image: selenium/node-edge:latest imagePullPolicy: IfNotPresent envFrom: - configMapRef: @@ -334,6 +342,8 @@ spec: - type: selenium-grid metadata: url: '{{.HubGraphURL}}' + username: '{{.HubBasicAuthUsername}}' + password: '{{.HubBasicAuthPassword}}' browserName: 'MicrosoftEdge' sessionBrowserName: 'msedge' activationThreshold: '1' @@ -349,9 +359,9 @@ metadata: app: selenium-hub app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: selector: app: selenium-hub @@ -382,9 +392,9 @@ metadata: app.kubernetes.io/name: selenium-hub app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: replicas: 1 selector: @@ -396,8 +406,13 @@ spec: spec: containers: - name: selenium-hub - image: selenium/hub:4.0.0-rc-1-prerelease-20210618 + image: selenium/hub:latest imagePullPolicy: IfNotPresent + env: + - name: SE_ROUTER_USERNAME + value: '{{.HubBasicAuthUsername}}' + - name: SE_ROUTER_PASSWORD + value: '{{.HubBasicAuthPassword}}' ports: - containerPort: 4444 protocol: TCP @@ -527,6 +542,8 @@ func getTemplateData() (templateData, []Template) { HubHost: hubHost, HubPort: hubPort, HubGraphURL: hubGraphURL, + HubBasicAuthUsername: HubBasicAuthUsername, + HubBasicAuthPassword: HubBasicAuthPassword, ScaledObjectName: scaledObjectName, MinReplicaCount: minReplicaCount, MaxReplicaCount: maxReplicaCount,