From b9d2a9df14befbf633a6cd573eb9bd103db72994 Mon Sep 17 00:00:00 2001 From: Alex Soto Date: Sun, 1 Feb 2015 19:49:39 +0100 Subject: [PATCH 1/3] resolves issue #77 by adding observer event for prestarted containers. --- .../arquillian/cube/impl/client/CubeLifecycleController.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docker/src/main/java/org/arquillian/cube/impl/client/CubeLifecycleController.java b/docker/src/main/java/org/arquillian/cube/impl/client/CubeLifecycleController.java index 43aca66d2..baca6b401 100644 --- a/docker/src/main/java/org/arquillian/cube/impl/client/CubeLifecycleController.java +++ b/docker/src/main/java/org/arquillian/cube/impl/client/CubeLifecycleController.java @@ -4,6 +4,7 @@ import org.arquillian.cube.spi.CubeRegistry; import org.arquillian.cube.spi.event.CreateCube; import org.arquillian.cube.spi.event.DestroyCube; +import org.arquillian.cube.spi.event.PreRunningCube; import org.arquillian.cube.spi.event.StartCube; import org.arquillian.cube.spi.event.StopCube; import org.jboss.arquillian.core.api.annotation.Observes; @@ -26,6 +27,10 @@ public void destroy(@Observes DestroyCube event, CubeRegistry registry) { validateAndGet(registry, event.getCubeId()).destroy(); } + public void changeToPreRunning(@Observes PreRunningCube event, CubeRegistry registry) { + validateAndGet(registry, event.getCubeId()).changeToPreRunning(); + } + private Cube validateAndGet(CubeRegistry registry, String cubeId) { Cube cube = registry.getCube(cubeId); if(cube == null) { From ee3d31c575688a3c124ee01989e2f537ef723115 Mon Sep 17 00:00:00 2001 From: Alex Soto Date: Sun, 1 Feb 2015 19:55:14 +0100 Subject: [PATCH 2/3] resolves issue #88 by adding unix socket resolution. --- .../cube/impl/util/BindingUtil.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docker/src/main/java/org/arquillian/cube/impl/util/BindingUtil.java b/docker/src/main/java/org/arquillian/cube/impl/util/BindingUtil.java index 6b3004896..d693fcae4 100644 --- a/docker/src/main/java/org/arquillian/cube/impl/util/BindingUtil.java +++ b/docker/src/main/java/org/arquillian/cube/impl/util/BindingUtil.java @@ -1,5 +1,6 @@ package org.arquillian.cube.impl.util; +import java.net.URI; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -13,6 +14,8 @@ public final class BindingUtil { + private static final String LOCALHOST = "localhost"; + private static final String UNIX_SOCKET_SCHEME = "unix"; public static final String PORTS_SEPARATOR = "->"; private static final String NO_GATEWAY = null; @@ -24,15 +27,7 @@ public static Binding binding(DockerClientExecutor executor, String cubeId) { HostConfig hostConfig = inspectResponse.getHostConfig(); - /** - * This isn't actually the gateway IP of the host, it's the ip of the gateway within the docker runtime, - * which may not be accessible to us. We need the IP the client uses, that will be routable, and will - * allow us to validate the ports in later phases - String gatewayIp = inspectResponse.getNetworkSettings().getGateway(); - - **/ - - final String dockerIp = executor.getDockerUri().getHost(); + String dockerIp = getDockerServerIp(executor); Binding binding = new Binding(dockerIp); for (Entry bind : hostConfig.getPortBindings() @@ -45,6 +40,12 @@ public static Binding binding(DockerClientExecutor executor, String cubeId) { return binding; } + private static String getDockerServerIp(DockerClientExecutor executor) { + URI dockerUri = executor.getDockerUri(); + String dockerIp = UNIX_SOCKET_SCHEME.equalsIgnoreCase(dockerUri.getScheme()) ? LOCALHOST : dockerUri.getHost(); + return dockerIp; + } + public static Binding binding(Map cubeConfiguration) { Binding binding = new Binding(NO_GATEWAY); From e900b51d36e8936057d84704561ed8fdfbd82d52 Mon Sep 17 00:00:00 2001 From: lordofthejars Date: Sun, 1 Feb 2015 20:17:21 +0100 Subject: [PATCH 3/3] adds ss command for remote ping. --- .../cube/impl/await/AwaitStrategyFactory.java | 2 +- .../cube/impl/await/PollingAwaitStrategy.java | 48 ++++++++++++++++--- .../impl/docker/DockerClientExecutor.java | 4 ++ .../org/arquillian/cube/impl/util/Ping.java | 33 +++++++++++++ .../org/arquillian/cube/impl/await/ss.sh | 1 + .../cube/impl/await/AwaitStrategyTest.java | 47 +++++++++++++----- 6 files changed, 117 insertions(+), 18 deletions(-) create mode 100644 docker/src/main/resources/org/arquillian/cube/impl/await/ss.sh diff --git a/docker/src/main/java/org/arquillian/cube/impl/await/AwaitStrategyFactory.java b/docker/src/main/java/org/arquillian/cube/impl/await/AwaitStrategyFactory.java index a269792c2..f183367d9 100644 --- a/docker/src/main/java/org/arquillian/cube/impl/await/AwaitStrategyFactory.java +++ b/docker/src/main/java/org/arquillian/cube/impl/await/AwaitStrategyFactory.java @@ -28,7 +28,7 @@ public static final AwaitStrategy create(DockerClientExecutor dockerClientExecut String strategy = ((String) awaitOptions.get(STRATEGY)).toLowerCase(); switch(strategy) { - case PollingAwaitStrategy.TAG: return new PollingAwaitStrategy(cube, awaitOptions); + case PollingAwaitStrategy.TAG: return new PollingAwaitStrategy(cube, dockerClientExecutor, awaitOptions); case NativeAwaitStrategy.TAG: return new NativeAwaitStrategy(cube, dockerClientExecutor); case StaticAwaitStrategy.TAG: return new StaticAwaitStrategy(cube, awaitOptions); case SleepingAwaitStrategy.TAG: return new SleepingAwaitStrategy(cube, awaitOptions); diff --git a/docker/src/main/java/org/arquillian/cube/impl/await/PollingAwaitStrategy.java b/docker/src/main/java/org/arquillian/cube/impl/await/PollingAwaitStrategy.java index c959f1952..a306062fb 100644 --- a/docker/src/main/java/org/arquillian/cube/impl/await/PollingAwaitStrategy.java +++ b/docker/src/main/java/org/arquillian/cube/impl/await/PollingAwaitStrategy.java @@ -1,9 +1,12 @@ package org.arquillian.cube.impl.await; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; +import org.arquillian.cube.impl.docker.DockerClientExecutor; +import org.arquillian.cube.impl.util.IOUtil; import org.arquillian.cube.impl.util.Ping; import org.arquillian.cube.spi.Binding; import org.arquillian.cube.spi.Binding.PortBinding; @@ -18,17 +21,22 @@ public class PollingAwaitStrategy implements AwaitStrategy { private static final int DEFAULT_POLL_ITERATIONS = 10; private static final int DEFAULT_SLEEP_POLL_TIME = 500; private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS; + private static final String DEFAULT_POLL_TYPE = "ping"; private static final String POLLING_TIME = "sleepPollingTime"; private static final String ITERATIONS = "iterations"; + private static final String POLL_TYPE = "type"; private int pollIterations = DEFAULT_POLL_ITERATIONS; private int sleepPollTime = DEFAULT_SLEEP_POLL_TIME; private TimeUnit timeUnit = DEFAULT_TIME_UNIT; - + private String type = DEFAULT_POLL_TYPE; + + private DockerClientExecutor dockerClientExecutor; private Cube cube; - public PollingAwaitStrategy(Cube cube, Map params) { + public PollingAwaitStrategy(Cube cube, DockerClientExecutor dockerClientExecutor, Map params) { this.cube = cube; + this.dockerClientExecutor = dockerClientExecutor; if (params.containsKey(POLLING_TIME)) { configurePollingTime(params); } @@ -36,6 +44,10 @@ public PollingAwaitStrategy(Cube cube, Map params) { if (params.containsKey(ITERATIONS)) { this.pollIterations = (Integer) params.get(ITERATIONS); } + + if(params.containsKey(POLL_TYPE)) { + this.type = (String) params.get(POLL_TYPE); + } } private void configurePollingTime(Map params) { @@ -70,18 +82,42 @@ public TimeUnit getTimeUnit() { return timeUnit; } + public String getType() { + return type; + } + @Override public boolean await() { Binding bindings = cube.bindings(); for (PortBinding ports : bindings.getPortBindings()) { - log.fine(String.format("Pinging host (gateway) %s and port %s", bindings.getIP(), ports.getBindingPort())); - if (!Ping.ping(bindings.getIP(), ports.getBindingPort(), this.pollIterations, this.sleepPollTime, - this.timeUnit)) { - return false; + log.fine(String.format("Pinging host %s and port %s with type", bindings.getIP(), ports.getBindingPort(), this.type)); + + switch(this.type) { + case "ping": { + if (!Ping.ping(bindings.getIP(), ports.getBindingPort(), this.pollIterations, this.sleepPollTime, + this.timeUnit)) { + return false; + } + } + + break; + case "sscommand": { + if(!Ping.ping(dockerClientExecutor, cube.getId(), resolveCommand("ss", ports.getExposedPort()), this.pollIterations, this.sleepPollTime, this.timeUnit)) { + return false; + } + } } + } return true; } + + private String resolveCommand(String command, int port) { + Map values = new HashMap(); + values.put("port", Integer.toString(port)); + String templateContent = IOUtil.asStringPreservingNewLines(PollingAwaitStrategy.class.getResourceAsStream(command+".sh")); + return IOUtil.replacePlaceholders(templateContent, values); + } } diff --git a/docker/src/main/java/org/arquillian/cube/impl/docker/DockerClientExecutor.java b/docker/src/main/java/org/arquillian/cube/impl/docker/DockerClientExecutor.java index fa62160a3..fb8f70641 100644 --- a/docker/src/main/java/org/arquillian/cube/impl/docker/DockerClientExecutor.java +++ b/docker/src/main/java/org/arquillian/cube/impl/docker/DockerClientExecutor.java @@ -468,6 +468,10 @@ public void pullImage(String imageName) { IOUtil.asString(exec); } + public String execStart(String containerId, String...commands) { + //TODO implement when docker-java API is updated. + return "1"; + } /** * Get the URI of the docker host diff --git a/docker/src/main/java/org/arquillian/cube/impl/util/Ping.java b/docker/src/main/java/org/arquillian/cube/impl/util/Ping.java index c8d12c987..c14209401 100644 --- a/docker/src/main/java/org/arquillian/cube/impl/util/Ping.java +++ b/docker/src/main/java/org/arquillian/cube/impl/util/Ping.java @@ -5,12 +5,32 @@ import java.net.UnknownHostException; import java.util.concurrent.TimeUnit; +import org.arquillian.cube.impl.docker.DockerClientExecutor; + public final class Ping { private Ping() { super(); } + public static boolean ping(DockerClientExecutor dockerClientExecutor, String containerId, String command, int totalIterations, long sleep, TimeUnit timeUnit) { + boolean result = false; + int iteration = 0; + + do { + result = execContainerPing(dockerClientExecutor, containerId, command); + if(!result) { + iteration++; + try { + timeUnit.sleep(sleep); + } catch (InterruptedException e) { + } + } + } while(!result && iteration < totalIterations); + + return result; + } + public static boolean ping(String host, int port, int totalIterations, long sleep, TimeUnit timeUnit) { boolean result = false; int iteration = 0; @@ -29,6 +49,19 @@ public static boolean ping(String host, int port, int totalIterations, long slee return result; } + + private static boolean execContainerPing(DockerClientExecutor dockerClientExecutor, String containerId, String command) { + String result = dockerClientExecutor.execStart(containerId, command); + try { + int numberOfListenConnectons = Integer.parseInt(result.trim()); + //This number is based in that a port will be opened only as tcp or as udp. + //We will need another issue to modify cube internals to save if port is udp or tcp. + return numberOfListenConnectons > 0; + } catch(NumberFormatException e) { + return false; + } + } + private static boolean ping(String host, int port) { Socket socket = null; try { diff --git a/docker/src/main/resources/org/arquillian/cube/impl/await/ss.sh b/docker/src/main/resources/org/arquillian/cube/impl/await/ss.sh new file mode 100644 index 000000000..e3d1aef5f --- /dev/null +++ b/docker/src/main/resources/org/arquillian/cube/impl/await/ss.sh @@ -0,0 +1 @@ +ss -lntu | grep '${port}' | grep LISTEN -c diff --git a/docker/src/test/java/org/arquillian/cube/impl/await/AwaitStrategyTest.java b/docker/src/test/java/org/arquillian/cube/impl/await/AwaitStrategyTest.java index e3987a02d..01f66b081 100644 --- a/docker/src/test/java/org/arquillian/cube/impl/await/AwaitStrategyTest.java +++ b/docker/src/test/java/org/arquillian/cube/impl/await/AwaitStrategyTest.java @@ -68,14 +68,6 @@ public class AwaitStrategyTest { " sleepPollingTime: 200\n" + " iterations: 3"; - private static final String CONTENT_WITH_POLLING_STRATEGY_WITHOUT_DEFAULTS_AND_UNIT = "tomcat:\n" + - " image: tutum/tomcat:7.0\n" + - " exposedPorts: [8089/tcp]\n" + - " await:\n" + - " strategy: polling\n" + - " sleepPollingTime: 200 s\n" + - " iterations: 3"; - private static final String CONTENT_WITH_NATIVE_STRATEGY = "tomcat:\n" + " image: tutum/tomcat:7.0\n" + " exposedPorts: [8089/tcp]\n" + @@ -89,7 +81,24 @@ public class AwaitStrategyTest { " await:\n" + " strategy: sleeping\n" + " sleepTime: 200 s\n"; - + + private static final String CONTENT_WITH_POLLING_STRATEGY_WITHOUT_DEFAULTS_AND_UNIT = "tomcat:\n" + + " image: tutum/tomcat:7.0\n" + + " exposedPorts: [8089/tcp]\n" + + " await:\n" + + " strategy: polling\n" + + " sleepPollingTime: 200 s\n" + + " iterations: 3"; + + private static final String CONTENT_WITH_SSCOMMAND_STRATEGY = "tomcat:\n" + + " image: tutum/tomcat:7.0\n" + + " exposedPorts: [8089/tcp]\n" + + " await:\n" + + " strategy: polling\n" + + " sleepPollingTime: 200 s\n" + + " iterations: 3\n" + + " type: sscommand"; + @Mock private Cube cube; @@ -206,7 +215,7 @@ public void should_create_sleeping_await_strategy_with_specific_times() { assertThat(strategy, instanceOf(SleepingAwaitStrategy.class)); assertThat(((SleepingAwaitStrategy)strategy).getSleepTime(), is(200)); } - + @Test public void should_create_polling_await_strategy_with_specific_times_and_unit() { @@ -221,8 +230,23 @@ public void should_create_polling_await_strategy_with_specific_times_and_unit() assertThat(((PollingAwaitStrategy)strategy).getPollIterations(), is(3)); assertThat(((PollingAwaitStrategy)strategy).getSleepPollTime(), is(200)); assertThat(((PollingAwaitStrategy)strategy).getTimeUnit(), is(TimeUnit.SECONDS)); + assertThat(((PollingAwaitStrategy)strategy).getType(), is("ping")); } - + + @Test + public void should_create_polling_await_strategy_with_specific_type() { + + @SuppressWarnings("unchecked") + Map content = (Map) new Yaml().load(CONTENT_WITH_SSCOMMAND_STRATEGY); + @SuppressWarnings("unchecked") + Map tomcatConfig = (Map) content.get("tomcat"); + + AwaitStrategy strategy = AwaitStrategyFactory.create(null, cube, tomcatConfig); + + assertThat(strategy, instanceOf(PollingAwaitStrategy.class)); + assertThat(((PollingAwaitStrategy)strategy).getType(), is("sscommand")); + } + @Test public void should_create_native_await_strategy() { @@ -235,4 +259,5 @@ public void should_create_native_await_strategy() { assertThat(strategy, instanceOf(NativeAwaitStrategy.class)); } + }