Skip to content

Commit 909239c

Browse files
committed
[grid] Dynamic Grid working properly on Linux
Docker Networking is a little different in Linux and the approach of mapping a port and relying on the Docker daemon url is not enough, we need to use the container's IP and there is no need to map a port to the host for that. And after testing this approach in different OS, we can make this behaviour the default for Dynamic Grid, so we do not need to use the PortProber when we run inside Docker.
1 parent f0ca78c commit 909239c

File tree

2 files changed

+62
-53
lines changed

2 files changed

+62
-53
lines changed

java/server/src/org/openqa/selenium/grid/docker/DockerOptions.java

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,6 @@ public Map<Capabilities, Collection<SessionFactory>> getDockerSessionFactories(
124124
throw new DockerException("Unable to reach the Docker daemon at " + getDockerUri());
125125
}
126126

127-
DockerAssetsPath assetsPath = getAssetsPath(docker);
128-
129127
List<String> allConfigs = config.getAll(DOCKER_SECTION, "configs")
130128
.orElseThrow(() -> new DockerException("Unable to find docker configs"));
131129

@@ -141,10 +139,18 @@ public Map<Capabilities, Collection<SessionFactory>> getDockerSessionFactories(
141139
kinds.put(imageName, stereotype);
142140
}
143141

142+
// If Selenium Server is running inside a Docker container, we can inspect that container
143+
// to get the information from it.
144+
// Since Docker 1.12, the env var HOSTNAME has the container id (unless the user overwrites it)
145+
String hostname = HostIdentifier.getHostName();
146+
Optional<ContainerInfo> info = docker.inspect(new ContainerId(hostname));
147+
148+
DockerAssetsPath assetsPath = getAssetsPath(info);
149+
String networkName = getDockerNetworkName(info);
150+
144151
loadImages(docker, kinds.keySet().toArray(new String[0]));
145152
Image videoImage = getVideoImage(docker);
146153
loadImages(docker, videoImage.getName());
147-
String networkName = getDockerNetworkName(docker);
148154

149155
int maxContainerCount = Runtime.getRuntime().availableProcessors();
150156
ImmutableMultimap.Builder<Capabilities, SessionFactory> factories = ImmutableMultimap.builder();
@@ -162,7 +168,8 @@ public Map<Capabilities, Collection<SessionFactory>> getDockerSessionFactories(
162168
caps,
163169
videoImage,
164170
assetsPath,
165-
networkName));
171+
networkName,
172+
info.isPresent()));
166173
}
167174
LOG.info(String.format(
168175
"Mapping %s to docker image %s %d times",
@@ -178,53 +185,40 @@ private Image getVideoImage(Docker docker) {
178185
return docker.getImage(videoImage);
179186
}
180187

181-
private String getDockerNetworkName(Docker docker) {
182-
// Selenium Server is running inside a Docker container, we will inspect that container
183-
// to get the mounted volume and use that. If no volume was mounted, no assets will be saved.
184-
// Since Docker 1.12, the env var HOSTNAME has the container id (unless the user overwrites it)
185-
String hostname = HostIdentifier.getHostName();
186-
Optional<ContainerInfo> info = docker.inspect(new ContainerId(hostname));
188+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
189+
private String getDockerNetworkName(Optional<ContainerInfo> info) {
187190
if (info.isPresent()) {
188191
return info.get().getNetworkName();
189192
}
190193
return DEFAULT_DOCKER_NETWORK;
191194
}
192195

193-
private DockerAssetsPath getAssetsPath(Docker docker) {
194-
Optional<String> assetsPath = config.get(DOCKER_SECTION, "assets-path");
195-
if (assetsPath.isPresent()) {
196-
// We assume the user is not running the Selenium Server inside a Docker container
197-
// Therefore, we have access to the assets path on the host
198-
return new DockerAssetsPath(assetsPath.get(), assetsPath.get());
199-
}
200-
// Selenium Server is running inside a Docker container, we will inspect that container
201-
// to get the mounted volume and use that. If no volume was mounted, no assets will be saved.
202-
// Since Docker 1.12, the env var HOSTNAME has the container id (unless the user overwrites it)
203-
String hostname = HostIdentifier.getHostName();
204-
205-
Optional<ContainerInfo> info = docker.inspect(new ContainerId(hostname));
206-
if (!info.isPresent()) {
207-
return null;
196+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
197+
private DockerAssetsPath getAssetsPath(Optional<ContainerInfo> info) {
198+
if (info.isPresent()) {
199+
Optional<Map<String, Object>> mountedVolume = info.get().getMountedVolumes()
200+
.stream()
201+
.filter(
202+
mounted ->
203+
DEFAULT_ASSETS_PATH.equalsIgnoreCase(String.valueOf(mounted.get("Destination"))))
204+
.findFirst();
205+
206+
if (mountedVolume.isPresent()) {
207+
String hostPath = String.valueOf(mountedVolume.get().get("Source"));
208+
return new DockerAssetsPath(hostPath, DEFAULT_ASSETS_PATH);
209+
}
208210
}
209211

210-
Optional<Map<String, Object>> mountedVolume = info.get().getMountedVolumes()
211-
.stream()
212-
.filter(
213-
mounted ->
214-
DEFAULT_ASSETS_PATH.equalsIgnoreCase(String.valueOf(mounted.get("Destination"))))
215-
.findFirst();
216-
217-
if (mountedVolume.isPresent()) {
218-
String hostPath = String.valueOf(mountedVolume.get().get("Source"));
219-
return new DockerAssetsPath(hostPath, DEFAULT_ASSETS_PATH);
220-
}
212+
Optional<String> assetsPath = config.get(DOCKER_SECTION, "assets-path");
213+
// We assume the user is not running the Selenium Server inside a Docker container
214+
// Therefore, we have access to the assets path on the host
215+
return assetsPath.map(path -> new DockerAssetsPath(path, path)).orElse(null);
221216

222-
return null;
223217
}
224218

225219
private void loadImages(Docker docker, String... imageNames) {
226220
CompletableFuture<Void> cd = CompletableFuture.allOf(
227-
Arrays.stream(imageNames)
221+
Arrays.stream(imageNames)
228222
.map(name -> CompletableFuture.supplyAsync(() -> docker.getImage(name)))
229223
.toArray(CompletableFuture[]::new));
230224

java/server/src/org/openqa/selenium/grid/docker/DockerSessionFactory.java

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.openqa.selenium.TimeoutException;
2626
import org.openqa.selenium.WebDriverException;
2727
import org.openqa.selenium.docker.Container;
28+
import org.openqa.selenium.docker.ContainerConfig;
2829
import org.openqa.selenium.docker.ContainerInfo;
2930
import org.openqa.selenium.docker.Docker;
3031
import org.openqa.selenium.docker.Image;
@@ -94,6 +95,7 @@ public class DockerSessionFactory implements SessionFactory {
9495
private final Image videoImage;
9596
private final DockerAssetsPath assetsPath;
9697
private final String networkName;
98+
private final boolean runningInDocker;
9799

98100
public DockerSessionFactory(
99101
Tracer tracer,
@@ -104,7 +106,8 @@ public DockerSessionFactory(
104106
Capabilities stereotype,
105107
Image videoImage,
106108
DockerAssetsPath assetsPath,
107-
String networkName) {
109+
String networkName,
110+
boolean runningInDocker) {
108111
this.tracer = Require.nonNull("Tracer", tracer);
109112
this.clientFactory = Require.nonNull("HTTP client", clientFactory);
110113
this.docker = Require.nonNull("Docker command", docker);
@@ -115,6 +118,7 @@ public DockerSessionFactory(
115118
Require.nonNull("Stereotype", stereotype));
116119
this.videoImage = videoImage;
117120
this.assetsPath = assetsPath;
121+
this.runningInDocker = runningInDocker;
118122
}
119123

120124
@Override
@@ -130,23 +134,27 @@ public boolean test(Capabilities capabilities) {
130134
@Override
131135
public Either<WebDriverException, ActiveSession> apply(CreateSessionRequest sessionRequest) {
132136
LOG.info("Starting session for " + sessionRequest.getCapabilities());
133-
int port = PortProber.findFreePort();
134-
URL remoteAddress = getUrl(port);
135137

136-
HttpClient client = clientFactory.createClient(remoteAddress);
138+
int port = runningInDocker ? 4444 : PortProber.findFreePort();
137139
try (Span span = tracer.getCurrentContext().createSpan("docker_session_factory.apply")) {
138140
Map<String, EventAttributeValue> attributeMap = new HashMap<>();
139141
attributeMap.put(AttributeKey.LOGGER_CLASS.getKey(),
140142
EventAttribute.setValue(this.getClass().getName()));
141-
LOG.info("Creating container, mapping container port 4444 to " + port);
143+
String logMessage = runningInDocker ? "Creating container..." :
144+
"Creating container, mapping container port 4444 to " + port;
145+
LOG.info(logMessage);
142146
Container container = createBrowserContainer(port, sessionRequest.getCapabilities());
143147
container.start();
144148
ContainerInfo containerInfo = container.inspect();
145149

150+
String containerIp = containerInfo.getIp();
151+
URL remoteAddress = getUrl(port, containerIp);
152+
HttpClient client = clientFactory.createClient(remoteAddress);
153+
146154
attributeMap.put("docker.browser.image", EventAttribute.setValue(browserImage.toString()));
147155
attributeMap.put("container.port", EventAttribute.setValue(port));
148156
attributeMap.put("container.id", EventAttribute.setValue(container.getId().toString()));
149-
attributeMap.put("container.ip", EventAttribute.setValue(containerInfo.getIp()));
157+
attributeMap.put("container.ip", EventAttribute.setValue(containerIp));
150158
attributeMap.put("docker.server.url", EventAttribute.setValue(remoteAddress.toString()));
151159

152160
LOG.info(
@@ -212,7 +220,7 @@ public Either<WebDriverException, ActiveSession> apply(CreateSessionRequest sess
212220
String containerPath = path.get().getContainerPath(id);
213221
saveSessionCapabilities(mergedCapabilities, containerPath);
214222
String hostPath = path.get().getHostPath(id);
215-
videoContainer = startVideoContainer(mergedCapabilities, containerInfo.getIp(), hostPath);
223+
videoContainer = startVideoContainer(mergedCapabilities, containerIp, hostPath);
216224
}
217225

218226
Dialect downstream = sessionRequest.getDownstreamDialects().contains(result.getDialect()) ?
@@ -249,11 +257,14 @@ public Either<WebDriverException, ActiveSession> apply(CreateSessionRequest sess
249257
private Container createBrowserContainer(int port, Capabilities sessionCapabilities) {
250258
Map<String, String> browserContainerEnvVars = getBrowserContainerEnvVars(sessionCapabilities);
251259
Map<String, String> devShmMount = Collections.singletonMap("/dev/shm", "/dev/shm");
252-
return docker.create(image(browserImage)
253-
.env(browserContainerEnvVars)
254-
.bind(devShmMount)
255-
.map(Port.tcp(4444), Port.tcp(port))
256-
.network(networkName));
260+
ContainerConfig containerConfig = image(browserImage)
261+
.env(browserContainerEnvVars)
262+
.bind(devShmMount)
263+
.network(networkName);
264+
if (!runningInDocker) {
265+
containerConfig = containerConfig.map(Port.tcp(4444), Port.tcp(port));
266+
}
267+
return docker.create(containerConfig);
257268
}
258269

259270
private Map<String, String> getBrowserContainerEnvVars(Capabilities sessionRequestCapabilities) {
@@ -365,11 +376,15 @@ private void waitForServerToStart(HttpClient client, Duration duration) {
365376
});
366377
}
367378

368-
private URL getUrl(int port) {
379+
private URL getUrl(int port, String containerIp) {
369380
try {
370381
String host = "localhost";
371-
if (dockerUri.getScheme().startsWith("tcp") || dockerUri.getScheme().startsWith("http")) {
372-
host = dockerUri.getHost();
382+
if (runningInDocker) {
383+
host = containerIp;
384+
} else {
385+
if (dockerUri.getScheme().startsWith("tcp") || dockerUri.getScheme().startsWith("http")) {
386+
host = dockerUri.getHost();
387+
}
373388
}
374389
return new URL(String.format("http://%s:%s/wd/hub", host, port));
375390
} catch (MalformedURLException e) {

0 commit comments

Comments
 (0)