From 5f617fdebc48b302e1be3953a6a4ee9e09a643a2 Mon Sep 17 00:00:00 2001 From: Viet Nguyen Duc Date: Thu, 20 Nov 2025 20:57:38 +0700 Subject: [PATCH 1/2] [grid] Config grouping labels for containers in Dynamic Grid Signed-off-by: Viet Nguyen Duc --- .../grid/node/docker/DockerFlags.java | 11 ++++++++ .../grid/node/docker/DockerOptions.java | 26 +++++++++++++++---- .../node/docker/DockerSessionFactory.java | 14 ++++------ 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/java/src/org/openqa/selenium/grid/node/docker/DockerFlags.java b/java/src/org/openqa/selenium/grid/node/docker/DockerFlags.java index 78b685c2c2dfd..04b44008b42cf 100644 --- a/java/src/org/openqa/selenium/grid/node/docker/DockerFlags.java +++ b/java/src/org/openqa/selenium/grid/node/docker/DockerFlags.java @@ -115,6 +115,17 @@ public class DockerFlags implements HasRoles { example = "[\"/dev/kvm:/dev/kvm\"]") private List devices; + @Parameter( + names = {"--docker-grouping-labels"}, + description = + "Users to specify custom labels for grouping dynamic containers. This will make the" + + " system more flexible for different platforms and use cases") + @ConfigValue( + section = DockerOptions.DOCKER_SECTION, + name = "grouping-labels", + example = "[\"azure.container.group\", \"aws.ecs.cluster\"]") + private List groupingLabels; + @Parameter( names = {"--docker-video-image"}, description = "Docker image to be used when video recording is enabled") diff --git a/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java b/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java index 78f1bc3841271..be25596727bc7 100644 --- a/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java +++ b/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java @@ -174,7 +174,7 @@ public Map> getDockerSessionFactories( DockerAssetsPath assetsPath = getAssetsPath(info); String networkName = getDockerNetworkName(info); Map hostConfig = getDockerHostConfig(info); - Map composeLabels = getComposeLabels(info); + Map groupingLabels = getGroupingLabels(info); loadImages(docker, kinds.keySet().toArray(new String[0])); Image videoImage = getVideoImage(docker); @@ -211,7 +211,7 @@ public Map> getDockerSessionFactories( capabilities -> options.getSlotMatcher().matches(caps, capabilities), hostConfig, hostConfigKeys, - composeLabels)); + groupingLabels)); } LOG.info( String.format( @@ -261,15 +261,31 @@ private Map getDockerHostConfig(Optional info) { } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - private Map getComposeLabels(Optional info) { + private Map getGroupingLabels(Optional info) { if (info.isEmpty()) { return Collections.emptyMap(); } + // Get custom grouping labels from configuration + List customLabelKeys = + config.getAll(DOCKER_SECTION, "grouping-labels").orElseGet(Collections::emptyList); + Map allLabels = info.get().getLabels(); - // Filter for Docker Compose labels (com.docker.compose.*) + // Filter for project/grouping labels that work across orchestration systems + // Keep only project identifiers, exclude service-specific labels to prevent + // exit monitoring in Docker Compose, Podman Compose, etc. return allLabels.entrySet().stream() - .filter(entry -> entry.getKey().startsWith("com.docker.compose.")) + .filter( + entry -> { + String key = entry.getKey(); + // Docker Compose project label + if (key.equals("com.docker.compose.project")) return true; + // Podman Compose project label + if (key.equals("io.podman.compose.project")) return true; + // Custom user-defined grouping labels + if (customLabelKeys.contains(key)) return true; + return false; + }) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } diff --git a/java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java b/java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java index 0fab6badba53e..bf862ae880aba 100644 --- a/java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java +++ b/java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java @@ -105,7 +105,7 @@ public class DockerSessionFactory implements SessionFactory { private final Predicate predicate; private final Map hostConfig; private final List hostConfigKeys; - private final Map composeLabels; + private final Map groupingLabels; public DockerSessionFactory( Tracer tracer, @@ -124,7 +124,7 @@ public DockerSessionFactory( Predicate predicate, Map hostConfig, List hostConfigKeys, - Map composeLabels) { + Map groupingLabels) { this.tracer = Require.nonNull("Tracer", tracer); this.clientFactory = Require.nonNull("HTTP client", clientFactory); this.sessionTimeout = Require.nonNull("Session timeout", sessionTimeout); @@ -141,11 +141,7 @@ public DockerSessionFactory( this.predicate = Require.nonNull("Accepted capabilities predicate", predicate); this.hostConfig = Require.nonNull("Container host config", hostConfig); this.hostConfigKeys = Require.nonNull("Browser container host config keys", hostConfigKeys); - // Merge compose labels with oneoff=False to prevent triggering --exit-code-from dynamic grid - Map allLabels = - new HashMap<>(Require.nonNull("Docker Compose labels", composeLabels)); - allLabels.put("com.docker.compose.oneoff", "False"); - this.composeLabels = Collections.unmodifiableMap(allLabels); + this.groupingLabels = Require.nonNull("Container grouping labels", groupingLabels); } @Override @@ -332,7 +328,7 @@ private Container createBrowserContainer( .network(networkName) .devices(devices) .applyHostConfig(hostConfig, hostConfigKeys) - .labels(composeLabels) + .labels(groupingLabels) .name(containerName); Optional path = ofNullable(this.assetsPath); if (path.isPresent() && videoImage == null && recordVideoForSession(sessionCapabilities)) { @@ -397,7 +393,7 @@ private Container startVideoContainer( .env(envVars) .bind(volumeBinds) .network(networkName) - .labels(composeLabels) + .labels(groupingLabels) .name(containerName); if (!runningInDocker) { videoPort = PortProber.findFreePort(); From 4c17445bc45e12785e33254c3850dc3fa71df4f2 Mon Sep 17 00:00:00 2001 From: Viet Nguyen Duc Date: Thu, 20 Nov 2025 23:22:19 +0700 Subject: [PATCH 2/2] Fix review comment Signed-off-by: Viet Nguyen Duc --- .../grid/node/docker/DockerOptions.java | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java b/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java index be25596727bc7..660188986dd0b 100644 --- a/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java +++ b/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java @@ -30,9 +30,11 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.logging.Logger; @@ -270,22 +272,14 @@ private Map getGroupingLabels(Optional info) { List customLabelKeys = config.getAll(DOCKER_SECTION, "grouping-labels").orElseGet(Collections::emptyList); + Set groupingKeys = new HashSet<>(customLabelKeys); + groupingKeys.add("com.docker.compose.project"); + groupingKeys.add("io.podman.compose.project"); + Map allLabels = info.get().getLabels(); - // Filter for project/grouping labels that work across orchestration systems - // Keep only project identifiers, exclude service-specific labels to prevent - // exit monitoring in Docker Compose, Podman Compose, etc. + // Filter for grouping labels that work across orchestration systems return allLabels.entrySet().stream() - .filter( - entry -> { - String key = entry.getKey(); - // Docker Compose project label - if (key.equals("com.docker.compose.project")) return true; - // Podman Compose project label - if (key.equals("io.podman.compose.project")) return true; - // Custom user-defined grouping labels - if (customLabelKeys.contains(key)) return true; - return false; - }) + .filter(entry -> groupingKeys.contains(entry.getKey())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); }