Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add sessionsPerNode and setSessionsFromHub parameters to selenium-grid scaler #6055

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Here is an overview of all new **experimental** features:

### Improvements

- TODO ([#XXX](https://github.com/kedacore/keda/issues/XXX))
- **Selenium Scaler**: introduce new parameters setSessionsFromHub, sessionsPerNode and sessionBrowserVersion. ([#6080](https://github.com/kedacore/keda/issues/6080))

### Fixes

Expand Down
83 changes: 71 additions & 12 deletions pkg/scalers/selenium_grid_scaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@ type seleniumGridScaler struct {
type seleniumGridScalerMetadata struct {
triggerIndex int

URL string `keda:"name=url, order=triggerMetadata;authParams"`
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"`
URL string `keda:"name=url, order=triggerMetadata;authParams"`
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"`
SessionsPerNode int64 `keda:"name=sessionsPerNode, order=triggerMetadata, optional, default=1"`
SetSessionsFromHub bool `keda:"name=setSessionsFromHub, order=triggerMetadata, optional, default=false"`
SessionBrowserVersion string `keda:"name=sessionBrowserVersion, order=triggerMetadata, optional"`

TargetValue int64
}
Expand All @@ -46,6 +49,7 @@ type seleniumResponse struct {

type data struct {
Grid grid `json:"grid"`
NodesInfo nodesInfo `json:"nodesInfo"`
SessionsInfo sessionsInfo `json:"sessionsInfo"`
}

Expand All @@ -71,6 +75,19 @@ type capability struct {
PlatformName string `json:"platformName"`
}

type nodesInfo struct {
Nodes []nodes `json:"nodes"`
}

type nodes struct {
Stereotypes string `json:"stereotypes"`
}

type stereotype struct {
Slots int64 `json:"slots"`
Stereotype capability `json:"stereotype"`
}

const (
DefaultBrowserVersion string = "latest"
DefaultPlatformName string = "linux"
Expand Down Expand Up @@ -114,6 +131,9 @@ func parseSeleniumGridScalerMetadata(config *scalersconfig.ScalerConfig) (*selen
if meta.SessionBrowserName == "" {
meta.SessionBrowserName = meta.BrowserName
}
if meta.SessionBrowserVersion == "" {
meta.SessionBrowserVersion = meta.BrowserVersion
}
return meta, nil
}

Expand Down Expand Up @@ -152,7 +172,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 { maxSession, nodeCount }, nodesInfo { nodes { stereotypes } }, sessionsInfo { sessionQueueRequests, sessions { id, capabilities, nodeId } } }",
})

if err != nil {
Expand All @@ -179,21 +199,26 @@ 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.SessionsPerNode, s.metadata.SetSessionsFromHub, s.metadata.SessionBrowserVersion, 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 getCountFromSeleniumResponse(b []byte, browserName string, browserVersion string, sessionBrowserName string, platformName string, sessionsPerNode int64, setSessionsFromHub bool, sessionBrowserVersion string, logger logr.Logger) (int64, error) {
var count int64
var slots int64
var seleniumResponse = seleniumResponse{}

if err := json.Unmarshal(b, &seleniumResponse); err != nil {
return 0, err
}

if setSessionsFromHub {
slots = getSlotsFromSeleniumResponse(seleniumResponse, browserName, browserVersion, platformName, logger)
}

var sessionQueueRequests = seleniumResponse.Data.SessionsInfo.SessionQueueRequests
for _, sessionQueueRequest := range sessionQueueRequests {
var capability = capability{}
Expand All @@ -217,7 +242,7 @@ func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion s
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 {
if strings.HasPrefix(capability.BrowserVersion, sessionBrowserVersion) && platformNameMatches {
count++
} else if browserVersion == DefaultBrowserVersion && platformNameMatches {
count++
Expand All @@ -231,10 +256,44 @@ func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion s
var gridMaxSession = int64(seleniumResponse.Data.Grid.MaxSession)
var gridNodeCount = int64(seleniumResponse.Data.Grid.NodeCount)

if gridMaxSession > 0 && gridNodeCount > 0 {
if setSessionsFromHub {
if slots == 0 {
slots = sessionsPerNode
}
var floatCount = float64(count) / float64(slots)
count = int64(math.Ceil(floatCount))
} else 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))
}

return count, nil
}

func getSlotsFromSeleniumResponse(seleniumResponse seleniumResponse, browserName string, browserVersion string, platformName string, logger logr.Logger) int64 {
var slots int64

var nodes = seleniumResponse.Data.NodesInfo.Nodes
slots:
for _, node := range nodes {
var stereotypes = []stereotype{}
if err := json.Unmarshal([]byte(node.Stereotypes), &stereotypes); err == nil {
for _, stereotype := range stereotypes {
if stereotype.Stereotype.BrowserName == browserName {
var platformNameMatches = stereotype.Stereotype.PlatformName == "" || strings.EqualFold(stereotype.Stereotype.PlatformName, platformName)
if strings.HasPrefix(stereotype.Stereotype.BrowserVersion, browserVersion) && platformNameMatches {
slots = stereotype.Slots
break slots
} else if len(strings.TrimSpace(stereotype.Stereotype.BrowserVersion)) == 0 && browserVersion == DefaultBrowserVersion && platformNameMatches {
slots = stereotype.Slots
break slots
}
}
}
} else {
logger.Error(err, fmt.Sprintf("Error when unmarshalling stereotypes: %s", err))
}
}
return slots
}
Loading
Loading