org.openqa.selenium.grid.node.docker.DockerOptions Maven / Gradle / Ivy
// 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.docker;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Platform;
import org.openqa.selenium.docker.ContainerId;
import org.openqa.selenium.docker.ContainerInfo;
import org.openqa.selenium.docker.Device;
import org.openqa.selenium.docker.Docker;
import org.openqa.selenium.docker.DockerException;
import org.openqa.selenium.docker.Image;
import org.openqa.selenium.grid.config.Config;
import org.openqa.selenium.grid.config.ConfigException;
import org.openqa.selenium.grid.node.SessionFactory;
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.json.Json;
import org.openqa.selenium.net.HostIdentifier;
import org.openqa.selenium.remote.http.ClientConfig;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.remote.tracing.Tracer;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import static org.openqa.selenium.Platform.WINDOWS;
import static org.openqa.selenium.docker.Device.device;
public class DockerOptions {
static final String DOCKER_SECTION = "docker";
static final String DEFAULT_ASSETS_PATH = "/opt/selenium/assets";
static final String DEFAULT_DOCKER_URL = "unix:/var/run/docker.sock";
static final String DEFAULT_VIDEO_IMAGE = "selenium/video:latest";
static final int DEFAULT_MAX_SESSIONS = Runtime.getRuntime().availableProcessors();
private static final String DEFAULT_DOCKER_NETWORK = "bridge";
private static final Logger LOG = Logger.getLogger(DockerOptions.class.getName());
private static final Json JSON = new Json();
private final Config config;
public DockerOptions(Config config) {
this.config = Require.nonNull("Config", config);
}
private URI getDockerUri() {
try {
Optional possibleUri = config.get(DOCKER_SECTION, "url");
if (possibleUri.isPresent()) {
return new URI(possibleUri.get());
}
Optional possibleHost = config.get(DOCKER_SECTION, "host");
Optional possiblePort = config.getInt(DOCKER_SECTION, "port");
if (possibleHost.isPresent() && possiblePort.isPresent()) {
String host = possibleHost.get();
int port = possiblePort.get();
if (!(host.startsWith("tcp:") || host.startsWith("http:") || host.startsWith("https"))) {
host = String.format("http://%s:%s", host, port);
} else {
host = String.format("%s:%s", host, port);
}
URI uri = new URI(host);
return new URI(
"http",
uri.getUserInfo(),
uri.getHost(),
uri.getPort(),
uri.getPath(),
null,
null);
}
// Default for the system we're running on.
if (Platform.getCurrent().is(WINDOWS)) {
return new URI("http://localhost:2376");
}
return new URI(DEFAULT_DOCKER_URL);
} catch (URISyntaxException e) {
throw new ConfigException("Unable to determine docker url", e);
}
}
private boolean isEnabled(Docker docker) {
if (!config.getAll(DOCKER_SECTION, "configs").isPresent()) {
return false;
}
// Is the daemon up and running?
return docker.isSupported();
}
public Map> getDockerSessionFactories(
Tracer tracer,
HttpClient.Factory clientFactory,
Duration sessionTimeout) {
HttpClient client = clientFactory.createClient(
ClientConfig.defaultConfig().baseUri(getDockerUri()));
Docker docker = new Docker(client);
if (!isEnabled(docker)) {
throw new DockerException("Unable to reach the Docker daemon at " + getDockerUri());
}
List allConfigs = config.getAll(DOCKER_SECTION, "configs")
.orElseThrow(() -> new DockerException("Unable to find docker configs"));
Multimap kinds = HashMultimap.create();
for (int i = 0; i < allConfigs.size(); i++) {
String imageName = allConfigs.get(i);
i++;
if (i == allConfigs.size()) {
throw new DockerException("Unable to find JSON config");
}
Capabilities stereotype = JSON.toType(allConfigs.get(i), Capabilities.class);
kinds.put(imageName, stereotype);
}
List devicesMapping = getDevicesMapping();
// If Selenium Server is running inside a Docker container, we can inspect that container
// to get the information from it.
// Since Docker 1.12, the env var HOSTNAME has the container id (unless the user overwrites it)
String hostname = HostIdentifier.getHostName();
Optional info = docker.inspect(new ContainerId(hostname));
DockerAssetsPath assetsPath = getAssetsPath(info);
String networkName = getDockerNetworkName(info);
loadImages(docker, kinds.keySet().toArray(new String[0]));
Image videoImage = getVideoImage(docker);
loadImages(docker, videoImage.getName());
// Hard coding the config section value "node" to avoid an extra dependency
int maxContainerCount = Math.min(config.getInt("node", "max-sessions")
.orElse(DEFAULT_MAX_SESSIONS), DEFAULT_MAX_SESSIONS);
ImmutableMultimap.Builder factories = ImmutableMultimap.builder();
kinds.forEach((name, caps) -> {
Image image = docker.getImage(name);
for (int i = 0; i < maxContainerCount; i++) {
factories.put(
caps,
new DockerSessionFactory(
tracer,
clientFactory,
sessionTimeout,
docker,
getDockerUri(),
image,
caps,
devicesMapping,
videoImage,
assetsPath,
networkName,
info.isPresent()));
}
LOG.info(String.format(
"Mapping %s to docker image %s %d times",
caps,
name,
maxContainerCount));
});
return factories.build().asMap();
}
protected List getDevicesMapping() {
Pattern linuxDeviceMappingWithDefaultPermissionsPattern = Pattern.compile("^([\\w\\/-]+):([\\w\\/-]+)$");
Pattern linuxDeviceMappingWithPermissionsPattern = Pattern.compile("^([\\w\\/-]+):([\\w\\/-]+):([\\w]+)$");
List devices = config.getAll(DOCKER_SECTION, "devices")
.orElseGet(Collections::emptyList);
List deviceMapping = new ArrayList<>();
for (int i = 0; i < devices.size(); i++) {
String deviceMappingDefined = devices.get(i).trim();
Matcher matcher = linuxDeviceMappingWithDefaultPermissionsPattern.matcher(deviceMappingDefined);
if (matcher.matches()) {
deviceMapping.add(device(matcher.group(1), matcher.group(2), null));
} else if ((matcher = linuxDeviceMappingWithPermissionsPattern.matcher(
deviceMappingDefined)).matches()) {
deviceMapping.add(device(matcher.group(1), matcher.group(2), matcher.group(3)));
}
}
return deviceMapping;
}
private Image getVideoImage(Docker docker) {
String videoImage = config.get(DOCKER_SECTION, "video-image").orElse(DEFAULT_VIDEO_IMAGE);
return docker.getImage(videoImage);
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private String getDockerNetworkName(Optional info) {
if (info.isPresent()) {
return info.get().getNetworkName();
}
return DEFAULT_DOCKER_NETWORK;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private DockerAssetsPath getAssetsPath(Optional info) {
if (info.isPresent()) {
Optional
© 2015 - 2025 Weber Informatics LLC | Privacy Policy