[grid] Enhance max-sessions in case specify driver configuration in Node
Signed-off-by: Viet Nguyen Duc <nguyenducviet4496@gmail.com>
diff --git a/java/src/org/openqa/selenium/grid/node/config/NodeOptions.java b/java/src/org/openqa/selenium/grid/node/config/NodeOptions.java
index 5afc047..d2a290a 100644
--- a/java/src/org/openqa/selenium/grid/node/config/NodeOptions.java
+++ b/java/src/org/openqa/selenium/grid/node/config/NodeOptions.java
@@ -454,6 +454,10 @@ private void addDriverConfigs(
throw new ConfigException("No driver configs were found!");
}
+ // Handle session distribution for detect-drivers = false scenario
+ Map<String, Integer> sessionDistribution =
+ calculateDriverConfigSessionDistribution(configList, maxSessions);
+
List<DriverService.Builder<?, ?>> builderList = new ArrayList<>();
ServiceLoader.load(DriverService.Builder.class).forEach(builderList::add);
@@ -506,9 +510,8 @@ private void addDriverConfigs(
new ConfigException(
"Unable to find matching driver for %s", stereotype));
- int driverMaxSessions =
- Integer.parseInt(
- thisConfig.getOrDefault("max-sessions", String.valueOf(maxSessions)));
+ // Use calculated session distribution
+ int driverMaxSessions = sessionDistribution.get(configName);
Require.positive("Driver max sessions", driverMaxSessions);
WebDriverInfo driverInfoConfig =
@@ -521,8 +524,7 @@ private void addDriverConfigs(
builder -> {
ImmutableCapabilities immutable =
new ImmutableCapabilities(stereotype);
- int maxDriverSessions = getDriverMaxSessions(info, driverMaxSessions);
- for (int i = 0; i < maxDriverSessions; i++) {
+ for (int i = 0; i < driverMaxSessions; i++) {
driverConfigs.putAll(
driverInfoConfig, factoryFactory.apply(immutable));
}
@@ -792,6 +794,75 @@ private void report(Map.Entry<WebDriverInfo, Collection<SessionFactory>> entry)
entry.getValue().size()));
}
+ private Map<String, Integer> calculateDriverConfigSessionDistribution(
+ List<Map<String, String>> configList, int nodeMaxSessions) {
+ Map<String, Integer> sessionDistribution = new HashMap<>();
+ boolean overrideMaxSessions =
+ config.getBool(NODE_SECTION, "override-max-sessions").orElse(OVERRIDE_MAX_SESSIONS);
+
+ // S2.1: No max-sessions given, distribute CPU cores among driver configurations
+ if (!config.getInt(NODE_SECTION, "max-sessions").isPresent()) {
+ int availableCores = DEFAULT_MAX_SESSIONS;
+ int numDrivers = configList.size();
+
+ // Distribute cores evenly, with remainder going to first drivers
+ int baseSessions = availableCores / numDrivers;
+ int remainder = availableCores % numDrivers;
+
+ for (int i = 0; i < configList.size(); i++) {
+ Map<String, String> driverConfig = configList.get(i);
+ String displayName = driverConfig.get("display-name");
+
+ // Check if driver has specific max-sessions (S2.3)
+ int driverSessions;
+ if (driverConfig.containsKey("max-sessions")) {
+ int specificMaxSessions = Integer.parseInt(driverConfig.get("max-sessions"));
+ if (overrideMaxSessions) {
+ driverSessions = specificMaxSessions;
+ } else {
+ // Respect CPU core distribution even with specific max-sessions
+ driverSessions = Math.min(specificMaxSessions, baseSessions + (i < remainder ? 1 : 0));
+ }
+ } else {
+ driverSessions = baseSessions + (i < remainder ? 1 : 0);
+ }
+
+ sessionDistribution.put(displayName, driverSessions);
+ }
+ } else {
+ // S2.2: Given max-sessions under [node] section
+ int configuredMaxSessions = nodeMaxSessions;
+
+ for (Map<String, String> driverConfig : configList) {
+ String displayName = driverConfig.get("display-name");
+
+ // Check if driver has specific max-sessions (S2.3)
+ int driverSessions;
+ if (driverConfig.containsKey("max-sessions")) {
+ int specificMaxSessions = Integer.parseInt(driverConfig.get("max-sessions"));
+ if (overrideMaxSessions) {
+ // S2.4: Unlimited configure max-sessions with override-max-sessions true
+ driverSessions = specificMaxSessions;
+ } else {
+ // S2.3: Specific driver max-sessions takes precedence but controlled by CPU cores
+ driverSessions = Math.min(specificMaxSessions, configuredMaxSessions);
+ }
+ } else {
+ // Use node max-sessions, but respect CPU limits if override is false
+ if (overrideMaxSessions) {
+ driverSessions = configuredMaxSessions;
+ } else {
+ driverSessions = Math.min(configuredMaxSessions, DEFAULT_MAX_SESSIONS);
+ }
+ }
+
+ sessionDistribution.put(displayName, driverSessions);
+ }
+ }
+
+ return sessionDistribution;
+ }
+
private String unquote(String input) {
int len = input.length();
if ((input.charAt(0) == '"') && (input.charAt(len - 1) == '"')) {
diff --git a/java/test/org/openqa/selenium/grid/node/config/NodeOptionsDetectDriversFalseTest.java b/java/test/org/openqa/selenium/grid/node/config/NodeOptionsDetectDriversFalseTest.java
new file mode 100644
index 0000000..7afa8d4
--- /dev/null
+++ b/java/test/org/openqa/selenium/grid/node/config/NodeOptionsDetectDriversFalseTest.java
@@ -0,0 +1,105 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+
+package org.openqa.selenium.grid.node.config;
+
+import static java.util.Collections.singleton;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.Capabilities;
+import org.openqa.selenium.grid.config.Config;
+import org.openqa.selenium.grid.config.TomlConfig;
+import org.openqa.selenium.grid.node.SessionFactory;
+
+class NodeOptionsDetectDriversFalseTest {
+
+ @Test
+ void testS24_UnlimitedMaxSessionsWithOverride() {
+ String[] rawConfig =
+ new String[] {
+ "[node]",
+ "max-sessions = 4",
+ "override-max-sessions = true",
+ "detect-drivers = false",
+ "",
+ "[[node.driver-configuration]]",
+ "display-name = \"chrome\"",
+ "stereotype = '{\"browserName\":\"chrome\"}'",
+ "",
+ "[[node.driver-configuration]]",
+ "display-name = \"MicrosoftEdge\"",
+ "stereotype = '{\"browserName\":\"MicrosoftEdge\"}'",
+ "max-sessions = 3",
+ "",
+ "[[node.driver-configuration]]",
+ "display-name = \"firefox\"",
+ "stereotype = '{\"browserName\":\"firefox\"}'",
+ "max-sessions = 5"
+ };
+
+ Config config = new TomlConfig(new StringReader(String.join("\n", rawConfig)));
+ List<Capabilities> reported = new ArrayList<>();
+
+ new NodeOptions(config)
+ .getSessionFactories(
+ caps -> {
+ reported.add(caps);
+ return singleton(HelperFactory.create(config, caps));
+ });
+
+ long chromeCount =
+ reported.stream().filter(caps -> "chrome".equals(caps.getBrowserName())).count();
+ long edgeCount =
+ reported.stream().filter(caps -> "MicrosoftEdge".equals(caps.getBrowserName())).count();
+ long firefoxCount =
+ reported.stream().filter(caps -> "firefox".equals(caps.getBrowserName())).count();
+
+ assertThat(chromeCount).isEqualTo(4);
+ assertThat(edgeCount).isEqualTo(3);
+ assertThat(firefoxCount).isEqualTo(5);
+ assertThat(reported.size()).isEqualTo(12);
+ }
+
+ public static class HelperFactory {
+ public static SessionFactory create(Config config, Capabilities caps) {
+ return new SessionFactory() {
+ @Override
+ public Capabilities getStereotype() {
+ return caps;
+ }
+
+ @Override
+ public org.openqa.selenium.internal.Either<
+ org.openqa.selenium.WebDriverException, org.openqa.selenium.grid.node.ActiveSession>
+ apply(org.openqa.selenium.grid.data.CreateSessionRequest createSessionRequest) {
+ return org.openqa.selenium.internal.Either.left(
+ new org.openqa.selenium.SessionNotCreatedException("HelperFactory for testing"));
+ }
+
+ @Override
+ public boolean test(Capabilities capabilities) {
+ return true;
+ }
+ };
+ }
+ }
+}