From a73ba2f14f548762565725c96444edc65aa44407 Mon Sep 17 00:00:00 2001 From: Viet Nguyen Duc Date: Mon, 8 Apr 2024 23:52:11 +0000 Subject: [PATCH 1/2] [grid][java] browser containers provisioned in dynamic grid uses node-docker hostConfig Signed-off-by: Viet Nguyen Duc --- .../selenium/docker/ContainerConfig.java | 34 +++++++++++++++++++ .../openqa/selenium/docker/ContainerInfo.java | 12 ++++++- .../docker/v1_41/InspectContainer.java | 3 +- .../grid/node/docker/DockerFlags.java | 12 +++++++ .../grid/node/docker/DockerOptions.java | 13 ++++++- .../node/docker/DockerSessionFactory.java | 18 +++++++--- 6 files changed, 85 insertions(+), 7 deletions(-) diff --git a/java/src/org/openqa/selenium/docker/ContainerConfig.java b/java/src/org/openqa/selenium/docker/ContainerConfig.java index 27cc002ebe85b..92dc0d08c5ed9 100644 --- a/java/src/org/openqa/selenium/docker/ContainerConfig.java +++ b/java/src/org/openqa/selenium/docker/ContainerConfig.java @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimap; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -42,6 +43,7 @@ public class ContainerConfig { private final String networkName; private final boolean autoRemove; private final long shmSize; + private final Map hostConfig; public ContainerConfig( Image image, @@ -51,6 +53,18 @@ public ContainerConfig( List devices, String networkName, long shmSize) { + this(image, portBindings, envVars, volumeBinds, devices, networkName, shmSize, new HashMap<>()); + } + + public ContainerConfig( + Image image, + Multimap> portBindings, + Map envVars, + Map volumeBinds, + List devices, + String networkName, + long shmSize, + Map hostConfig) { this.image = image; this.portBindings = portBindings; this.envVars = envVars; @@ -59,6 +73,7 @@ public ContainerConfig( this.networkName = networkName; this.autoRemove = true; this.shmSize = shmSize; + this.hostConfig = hostConfig; } public static ContainerConfig image(Image image) { @@ -123,6 +138,17 @@ public ContainerConfig devices(List devices) { image, portBindings, envVars, volumeBinds, devices, networkName, shmSize); } + public ContainerConfig getHostConfig(Map hostConfig, List configKeys) { + Map setHostConfig = + configKeys.stream() + .filter(hostConfig::containsKey) + .filter(key -> hostConfig.get(key) != null) + .collect(Collectors.toMap(key -> key, hostConfig::get)); + + return new ContainerConfig( + image, portBindings, envVars, volumeBinds, devices, networkName, shmSize, setHostConfig); + } + @Override public String toString() { return "ContainerConfig{" @@ -142,6 +168,8 @@ public String toString() { + autoRemove + ", shmSize=" + shmSize + + ", hostConfig=" + + hostConfig + '}'; } @@ -175,6 +203,12 @@ private Map toJson() { "Binds", volumeBinds, "Devices", devicesMapping); + if (this.hostConfig != null && !this.hostConfig.isEmpty()) { + Map copyMap = new HashMap<>(hostConfig); + copyMap.putAll(this.hostConfig); + hostConfig = ImmutableMap.copyOf(copyMap); + } + return ImmutableMap.of( "Image", image.getId(), "Env", envVars, diff --git a/java/src/org/openqa/selenium/docker/ContainerInfo.java b/java/src/org/openqa/selenium/docker/ContainerInfo.java index fb1d49dc3c700..50d9c8a8d887b 100644 --- a/java/src/org/openqa/selenium/docker/ContainerInfo.java +++ b/java/src/org/openqa/selenium/docker/ContainerInfo.java @@ -29,13 +29,19 @@ public class ContainerInfo { private final ContainerId id; private final List> mountedVolumes; private final String networkName; + private Map hostConfig; public ContainerInfo( - ContainerId id, String ip, List> mountedVolumes, String networkName) { + ContainerId id, + String ip, + List> mountedVolumes, + String networkName, + Map hostConfig) { this.ip = Require.nonNull("Container ip address", ip); this.id = Require.nonNull("Container id", id); this.mountedVolumes = Require.nonNull("Mounted volumes", mountedVolumes); this.networkName = Require.nonNull("Network name", networkName); + this.hostConfig = Require.nonNull("Host config", hostConfig); } public String getIp() { @@ -54,6 +60,10 @@ public String getNetworkName() { return networkName; } + public Map getHostConfig() { + return this.hostConfig; + } + @Override public String toString() { return "ContainerInfo{" diff --git a/java/src/org/openqa/selenium/docker/v1_41/InspectContainer.java b/java/src/org/openqa/selenium/docker/v1_41/InspectContainer.java index dc88538755c3a..455f549c28c6f 100644 --- a/java/src/org/openqa/selenium/docker/v1_41/InspectContainer.java +++ b/java/src/org/openqa/selenium/docker/v1_41/InspectContainer.java @@ -67,7 +67,8 @@ public ContainerInfo apply(ContainerId id) { ArrayList mounts = (ArrayList) rawInspectInfo.get("Mounts"); List> mountedVolumes = mounts.stream().map(mount -> (Map) mount).collect(Collectors.toList()); + Map hostConfig = (Map) rawInspectInfo.get("HostConfig"); - return new ContainerInfo(id, ip, mountedVolumes, networkName); + return new ContainerInfo(id, ip, mountedVolumes, networkName, hostConfig); } } 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 20e0995373389..0bbea38ce80bc 100644 --- a/java/src/org/openqa/selenium/grid/node/docker/DockerFlags.java +++ b/java/src/org/openqa/selenium/grid/node/docker/DockerFlags.java @@ -69,6 +69,18 @@ public class DockerFlags implements HasRoles { "[\"selenium/standalone-firefox:latest\", \"{\\\"browserName\\\": \\\"firefox\\\"}\"]") private List images2Capabilities; + @Parameter( + names = {"--docker-host-config-keys"}, + description = + "Node container host config keys to be passed to the browsers container. Keys name can be" + + " found in the Docker API documentation, or by running `docker inspect` the" + + " node-docker container.") + @ConfigValue( + section = DockerOptions.DOCKER_SECTION, + name = "host-config-keys", + example = "[\"Dns\", \"DnsOptions\", \"DnsSearch\", \"ExtraHosts\"]") + private List hostConfigKeys; + @Parameter( names = {"--docker-devices"}, description = 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 68491ed2bd8c0..2904c57c285e7 100644 --- a/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java +++ b/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java @@ -129,6 +129,9 @@ public Map> getDockerSessionFactories( .getAll(DOCKER_SECTION, "configs") .orElseThrow(() -> new DockerException("Unable to find docker configs")); + List hostConfigKeys = + config.getAll(DOCKER_SECTION, "host-config-keys").orElseGet(Collections::emptyList); + Multimap kinds = HashMultimap.create(); for (int i = 0; i < allConfigs.size(); i++) { String imageName = allConfigs.get(i); @@ -152,6 +155,7 @@ public Map> getDockerSessionFactories( DockerAssetsPath assetsPath = getAssetsPath(info); String networkName = getDockerNetworkName(info); + Map hostConfig = getDockerHostConfig(info); loadImages(docker, kinds.keySet().toArray(new String[0])); Image videoImage = getVideoImage(docker); @@ -182,7 +186,9 @@ public Map> getDockerSessionFactories( assetsPath, networkName, info.isPresent(), - capabilities -> options.getSlotMatcher().matches(caps, capabilities))); + capabilities -> options.getSlotMatcher().matches(caps, capabilities), + hostConfig, + hostConfigKeys)); } LOG.info( String.format( @@ -229,6 +235,11 @@ private String getDockerNetworkName(Optional info) { return DEFAULT_DOCKER_NETWORK; } + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private Map getDockerHostConfig(Optional info) { + return info.map(ContainerInfo::getHostConfig).orElse(null); + } + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private DockerAssetsPath getAssetsPath(Optional info) { if (info.isPresent()) { 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 ff1e3250b21c8..fb7620627d9c7 100644 --- a/java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java +++ b/java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java @@ -101,6 +101,8 @@ public class DockerSessionFactory implements SessionFactory { private final String networkName; private final boolean runningInDocker; private final Predicate predicate; + private final Map hostConfig; + private final List hostConfigKeys; public DockerSessionFactory( Tracer tracer, @@ -115,7 +117,9 @@ public DockerSessionFactory( DockerAssetsPath assetsPath, String networkName, boolean runningInDocker, - Predicate predicate) { + Predicate predicate, + Map hostConfig, + List hostConfigKeys) { this.tracer = Require.nonNull("Tracer", tracer); this.clientFactory = Require.nonNull("HTTP client", clientFactory); this.sessionTimeout = Require.nonNull("Session timeout", sessionTimeout); @@ -129,6 +133,8 @@ public DockerSessionFactory( this.assetsPath = assetsPath; this.runningInDocker = runningInDocker; 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); } @Override @@ -154,7 +160,8 @@ public Either apply(CreateSessionRequest sess ? "Creating container..." : "Creating container, mapping container port 4444 to " + port; LOG.info(logMessage); - Container container = createBrowserContainer(port, sessionRequest.getDesiredCapabilities()); + Container container = + createBrowserContainer(port, sessionRequest.getDesiredCapabilities(), this.hostConfig); container.start(); ContainerInfo containerInfo = container.inspect(); @@ -277,7 +284,8 @@ private Capabilities addForwardCdpEndpoint( .setCapability("se:forwardCdp", forwardCdpPath); } - private Container createBrowserContainer(int port, Capabilities sessionCapabilities) { + private Container createBrowserContainer( + int port, Capabilities sessionCapabilities, Map hostConfig) { Map browserContainerEnvVars = getBrowserContainerEnvVars(sessionCapabilities); long browserContainerShmMemorySize = 2147483648L; // 2GB ContainerConfig containerConfig = @@ -285,10 +293,12 @@ private Container createBrowserContainer(int port, Capabilities sessionCapabilit .env(browserContainerEnvVars) .shmMemorySize(browserContainerShmMemorySize) .network(networkName) - .devices(devices); + .devices(devices) + .getHostConfig(hostConfig, hostConfigKeys); if (!runningInDocker) { containerConfig = containerConfig.map(Port.tcp(4444), Port.tcp(port)); } + LOG.fine("Container config: " + containerConfig); return docker.create(containerConfig); } From 39e101ddb86d58f14c87b8ecdd44ab5119b8d2a2 Mon Sep 17 00:00:00 2001 From: Viet Nguyen Duc Date: Thu, 11 Apr 2024 21:43:14 +0000 Subject: [PATCH 2/2] update code as suggestions Signed-off-by: Viet Nguyen Duc --- .../openqa/selenium/docker/ContainerConfig.java | 14 +++++++++++--- .../selenium/docker/v1_41/InspectContainer.java | 4 +++- .../selenium/grid/node/docker/DockerFlags.java | 8 ++++---- .../selenium/grid/node/docker/DockerOptions.java | 2 +- .../grid/node/docker/DockerSessionFactory.java | 8 +++----- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/java/src/org/openqa/selenium/docker/ContainerConfig.java b/java/src/org/openqa/selenium/docker/ContainerConfig.java index 92dc0d08c5ed9..519829762f6d9 100644 --- a/java/src/org/openqa/selenium/docker/ContainerConfig.java +++ b/java/src/org/openqa/selenium/docker/ContainerConfig.java @@ -53,7 +53,15 @@ public ContainerConfig( List devices, String networkName, long shmSize) { - this(image, portBindings, envVars, volumeBinds, devices, networkName, shmSize, new HashMap<>()); + this( + image, + portBindings, + envVars, + volumeBinds, + devices, + networkName, + shmSize, + ImmutableMap.of()); } public ContainerConfig( @@ -138,7 +146,7 @@ public ContainerConfig devices(List devices) { image, portBindings, envVars, volumeBinds, devices, networkName, shmSize); } - public ContainerConfig getHostConfig(Map hostConfig, List configKeys) { + public ContainerConfig applyHostConfig(Map hostConfig, List configKeys) { Map setHostConfig = configKeys.stream() .filter(hostConfig::containsKey) @@ -203,7 +211,7 @@ private Map toJson() { "Binds", volumeBinds, "Devices", devicesMapping); - if (this.hostConfig != null && !this.hostConfig.isEmpty()) { + if (!this.hostConfig.isEmpty()) { Map copyMap = new HashMap<>(hostConfig); copyMap.putAll(this.hostConfig); hostConfig = ImmutableMap.copyOf(copyMap); diff --git a/java/src/org/openqa/selenium/docker/v1_41/InspectContainer.java b/java/src/org/openqa/selenium/docker/v1_41/InspectContainer.java index 455f549c28c6f..744b6196a10b9 100644 --- a/java/src/org/openqa/selenium/docker/v1_41/InspectContainer.java +++ b/java/src/org/openqa/selenium/docker/v1_41/InspectContainer.java @@ -23,6 +23,7 @@ import static org.openqa.selenium.remote.http.HttpMethod.GET; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.logging.Logger; @@ -67,7 +68,8 @@ public ContainerInfo apply(ContainerId id) { ArrayList mounts = (ArrayList) rawInspectInfo.get("Mounts"); List> mountedVolumes = mounts.stream().map(mount -> (Map) mount).collect(Collectors.toList()); - Map hostConfig = (Map) rawInspectInfo.get("HostConfig"); + Map hostConfig = + (Map) rawInspectInfo.getOrDefault("HostConfig", Collections.emptyMap()); return new ContainerInfo(id, ip, mountedVolumes, networkName, hostConfig); } 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 0bbea38ce80bc..02af7f7c2dd41 100644 --- a/java/src/org/openqa/selenium/grid/node/docker/DockerFlags.java +++ b/java/src/org/openqa/selenium/grid/node/docker/DockerFlags.java @@ -72,13 +72,13 @@ public class DockerFlags implements HasRoles { @Parameter( names = {"--docker-host-config-keys"}, description = - "Node container host config keys to be passed to the browsers container. Keys name can be" - + " found in the Docker API documentation, or by running `docker inspect` the" - + " node-docker container.") + "Specify which docker host configuration keys should be passed to browser containers." + + " Keys name can be found in the Docker API documentation, or by running `docker" + + " inspect` the node-docker container.") @ConfigValue( section = DockerOptions.DOCKER_SECTION, name = "host-config-keys", - example = "[\"Dns\", \"DnsOptions\", \"DnsSearch\", \"ExtraHosts\"]") + example = "[\"Dns\", \"DnsOptions\", \"DnsSearch\", \"ExtraHosts\", \"Binds\"]") private List hostConfigKeys; @Parameter( 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 2904c57c285e7..d5bff8e52725b 100644 --- a/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java +++ b/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java @@ -237,7 +237,7 @@ private String getDockerNetworkName(Optional info) { @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private Map getDockerHostConfig(Optional info) { - return info.map(ContainerInfo::getHostConfig).orElse(null); + return info.map(ContainerInfo::getHostConfig).orElse(Collections.emptyMap()); } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 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 fb7620627d9c7..dd0ed03d6fde0 100644 --- a/java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java +++ b/java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java @@ -160,8 +160,7 @@ public Either apply(CreateSessionRequest sess ? "Creating container..." : "Creating container, mapping container port 4444 to " + port; LOG.info(logMessage); - Container container = - createBrowserContainer(port, sessionRequest.getDesiredCapabilities(), this.hostConfig); + Container container = createBrowserContainer(port, sessionRequest.getDesiredCapabilities()); container.start(); ContainerInfo containerInfo = container.inspect(); @@ -284,8 +283,7 @@ private Capabilities addForwardCdpEndpoint( .setCapability("se:forwardCdp", forwardCdpPath); } - private Container createBrowserContainer( - int port, Capabilities sessionCapabilities, Map hostConfig) { + private Container createBrowserContainer(int port, Capabilities sessionCapabilities) { Map browserContainerEnvVars = getBrowserContainerEnvVars(sessionCapabilities); long browserContainerShmMemorySize = 2147483648L; // 2GB ContainerConfig containerConfig = @@ -294,7 +292,7 @@ private Container createBrowserContainer( .shmMemorySize(browserContainerShmMemorySize) .network(networkName) .devices(devices) - .getHostConfig(hostConfig, hostConfigKeys); + .applyHostConfig(hostConfig, hostConfigKeys); if (!runningInDocker) { containerConfig = containerConfig.map(Port.tcp(4444), Port.tcp(port)); }