Skip to content

Commit

Permalink
[docker + grid] basic grid integration of docker containers
Browse files Browse the repository at this point in the history
  • Loading branch information
shs96c committed Mar 19, 2019
1 parent 8a6ade1 commit 75a7b00
Show file tree
Hide file tree
Showing 10 changed files with 464 additions and 1 deletion.
1 change: 1 addition & 0 deletions java/server/src/org/openqa/selenium/docker/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ java_library(
"//third_party/java/guava:guava",
],
visibility = [
"//java/server/src/org/openqa/selenium/grid/docker:",
],
)
3 changes: 2 additions & 1 deletion java/server/src/org/openqa/selenium/docker/Docker.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public Image pull(String name, String tag) {

LOG.info("Pull complete");

return findImage(image -> image.getTags().contains(name + ":" + tag));
return findImage(new ImageNamePredicate(name, tag));
}

public List<Image> listImages() {
Expand Down Expand Up @@ -138,4 +138,5 @@ public Container create(ContainerInfo info) {

return new Container(client, new ContainerId((String) toRead.get("Id")));
}

}
55 changes: 55 additions & 0 deletions java/server/src/org/openqa/selenium/docker/ImageNamePredicate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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.docker;

import java.util.Objects;
import java.util.function.Predicate;

public class ImageNamePredicate implements Predicate<Image> {

private final String name;
private final String tag;

public ImageNamePredicate(String name, String tag) {
this.name = Objects.requireNonNull(name);
this.tag = Objects.requireNonNull(tag);
}

public ImageNamePredicate(String name) {
Objects.requireNonNull(name);
int index = name.indexOf(":");
if (index == -1) {
this.tag = "latest";
this.name = name;
} else {
this.name = name.substring(0, index);
this.tag = name.substring(index + 1);
}

}

@Override
public boolean test(Image image) {
return image.getTags().contains(name + ":" + tag);
}

@Override
public String toString() {
return "by tag: " + name + ":" + tag;
}
}
19 changes: 19 additions & 0 deletions java/server/src/org/openqa/selenium/grid/docker/BUCK
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
java_library(
name = "docker",
srcs = glob(["*.java"]),
exported_deps = [
"//java/client/src/org/openqa/selenium/remote:remote",
],
deps = [
"//java/client/src/org/openqa/selenium/support:support",
"//java/server/src/org/openqa/selenium/docker:docker",
"//java/server/src/org/openqa/selenium/grid/config:config",
"//java/server/src/org/openqa/selenium/grid/data:data",
"//java/server/src/org/openqa/selenium/grid/node/local:local",
"//java/server/src/org/openqa/selenium/grid/web:web",
"//third_party/java/beust:jcommander",
],
visibility = [
"//java/server/src/org/openqa/selenium/grid/node/...",
],
)
50 changes: 50 additions & 0 deletions java/server/src/org/openqa/selenium/grid/docker/DockerFlags.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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.docker;

import com.beust.jcommander.Parameter;

import org.openqa.selenium.grid.config.ConfigValue;

import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URL;

public class DockerFlags {

@Parameter(
names = {"--docker-url"},
description = "URL for connecting to the docker daemon")
@ConfigValue(section = "docker", name = "url")
private URL dockerUrl;

@Parameter(
names = {"--docker"},
description = "Whether docker integration is enabled",
arity = 1)
@ConfigValue(section = "docker", name = "enabled")
private boolean isEnabled = true;

public DockerFlags() {
try {
dockerUrl = new URL("http://localhost:2375");
} catch (MalformedURLException e) {
throw new UncheckedIOException(e);
}
}
}
131 changes: 131 additions & 0 deletions java/server/src/org/openqa/selenium/grid/docker/DockerOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// 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.docker;

import static java.util.logging.Level.WARNING;
import static org.openqa.selenium.remote.http.HttpMethod.GET;

import org.openqa.selenium.ImmutableCapabilities;
import org.openqa.selenium.docker.Docker;
import org.openqa.selenium.docker.Image;
import org.openqa.selenium.docker.ImageNamePredicate;
import org.openqa.selenium.grid.config.Config;
import org.openqa.selenium.grid.config.ConfigException;
import org.openqa.selenium.grid.node.local.LocalNode;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Logger;
import java.util.stream.Collectors;

public class DockerOptions {

public static final Logger LOG = Logger.getLogger(DockerOptions.class.getName());
private final Config config;

public DockerOptions(Config config) {
this.config = Objects.requireNonNull(config);
}

private URL getDockerUrl() {
try {
String raw = config.get("docker", "url")
.orElseThrow(() -> new ConfigException("No docker url configured"));
return new URL(raw);
} catch (MalformedURLException e) {
throw new UncheckedIOException(e);
}
}

public boolean isEnabled(HttpClient.Factory clientFactory) {
if (!config.getBool("docker", "enabled").orElse(false)) {
return false;
}

// Is the daemon up and running?
URL url = getDockerUrl();
HttpClient client = clientFactory.createClient(url);

try {
HttpResponse response = client.execute(new HttpRequest(GET, "/_ping"));
if (response.getStatus() != 200) {
LOG.warning(String.format("Docker config enabled, but daemon unreachable: %s", url));
return false;
}

return true;
} catch (IOException e) {
LOG.log(WARNING, "Unable to ping docker daemon: " + e.getMessage(), e);
return false;
}
}

public void configure(HttpClient.Factory clientFactory, LocalNode.Builder node)
throws IOException {
HttpClient client = clientFactory.createClient(new URL("http://localhost:2375"));
Docker docker = new Docker(client);

loadImages(
docker,
"selenium/standalone-firefox:3.141.59",
"selenium/standalone-chrome:3.141.59");

Image firefox = docker.findImage(new ImageNamePredicate("selenium/standalone-firefox:3.141.59"));
for (int i = 0; i < Runtime.getRuntime().availableProcessors(); i++) {
node.add(new ImmutableCapabilities("browserName", "firefox"),
new DockerSessionFactory(clientFactory, docker, firefox));
}

Image chrome = docker.findImage(new ImageNamePredicate("selenium/standalone-chrome:3.141.59"));
for (int i = 0; i < Runtime.getRuntime().availableProcessors(); i++) {
node.add(new ImmutableCapabilities("browserName", "chrome"),
new DockerSessionFactory(clientFactory, docker, chrome));
}
}

private void loadImages(Docker docker, String... imageNames) {
List<CompletableFuture<Image>> allFutures = Arrays.stream(imageNames)
.map(entry -> {
int index = entry.lastIndexOf(':');
if (index == -1) {
throw new RuntimeException("Unable to determine tag from " + entry);
}
String name = entry.substring(0, index);
String version = entry.substring(index + 1);

return CompletableFuture.supplyAsync(() -> docker.pull(name, version));
})
.collect(Collectors.toList());

CompletableFuture<Void>
cd =
CompletableFuture.allOf(allFutures.toArray(new CompletableFuture[0]));
cd.whenComplete((ignored, throwable) -> {

});
}
}
65 changes: 65 additions & 0 deletions java/server/src/org/openqa/selenium/grid/docker/DockerSession.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// 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.docker;

import static org.openqa.selenium.remote.http.HttpMethod.DELETE;

import org.openqa.selenium.Capabilities;
import org.openqa.selenium.docker.Container;
import org.openqa.selenium.grid.data.Session;
import org.openqa.selenium.grid.web.CommandHandler;
import org.openqa.selenium.grid.web.ReverseProxyHandler;
import org.openqa.selenium.remote.SessionId;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;

import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import java.util.Objects;

class DockerSession extends Session implements CommandHandler {

private final Container container;
private final CommandHandler handler;
private final String killUrl;

DockerSession(
Container container,
SessionId id,
URI uri,
Capabilities capabilities,
HttpClient client) {
super(id, uri, capabilities);
this.container = Objects.requireNonNull(container);

this.handler = new ReverseProxyHandler(Objects.requireNonNull(client));
this.killUrl = "/session/" + id;
}

@Override
public void execute(HttpRequest req, HttpResponse resp) throws IOException {
handler.execute(req, resp);

if (req.getMethod() == DELETE && req.getUri().equals(killUrl)) {
container.stop(Duration.ofMinutes(1));
container.delete();
}
}
}

0 comments on commit 75a7b00

Please sign in to comment.