diff --git a/docker/src/main/java/org/arquillian/cube/impl/client/ConnectionMode.java b/docker/src/main/java/org/arquillian/cube/impl/client/ConnectionMode.java new file mode 100644 index 000000000..ce11f180c --- /dev/null +++ b/docker/src/main/java/org/arquillian/cube/impl/client/ConnectionMode.java @@ -0,0 +1,21 @@ +package org.arquillian.cube.impl.client; + +public enum ConnectionMode { + STARTANDSTOP(false, true), STARTORCONNECT(true, true), STARTORCONNECTANDLEAVE(true, false); + + private boolean allowReconnect = false; + private boolean stoppable = true; + + private ConnectionMode(boolean allowReconnect, boolean stoppable) { + this.allowReconnect = allowReconnect; + this.stoppable = stoppable; + } + + public boolean isAllowReconnect() { + return allowReconnect; + } + + public boolean isStoppable() { + return stoppable; + } +} diff --git a/docker/src/main/java/org/arquillian/cube/impl/client/CubeConfiguration.java b/docker/src/main/java/org/arquillian/cube/impl/client/CubeConfiguration.java index b4e4d4971..c26c61eda 100644 --- a/docker/src/main/java/org/arquillian/cube/impl/client/CubeConfiguration.java +++ b/docker/src/main/java/org/arquillian/cube/impl/client/CubeConfiguration.java @@ -19,13 +19,13 @@ public class CubeConfiguration { private static final String DOCKER_CONTAINERS_FILE = "dockerContainersFile"; private static final String DOCKER_REGISTRY = "dockerRegistry"; private static final String AUTO_START_CONTAINERS = "autoStartContainers"; - private static final String SHOULD_ALLOW_TO_CONNECT_TO_RUNNING_CONTAINERS = "shouldAllowToConnectToRunningContainers"; + private static final String CONNECTION_MODE = "connectionMode"; private static final String BOOT2DOCKER_PATH = "boot2dockerPath"; private String dockerServerVersion; private String dockerServerUri; private String dockerRegistry; - private boolean shouldAllowToConnectToRunningContainers = false; + private ConnectionMode connectionMode = ConnectionMode.STARTANDSTOP; private String boot2DockerPath; private String username; private String password; @@ -35,8 +35,8 @@ public class CubeConfiguration { private Map dockerContainersContent; - public boolean shouldAllowToConnectToRunningContainers() { - return shouldAllowToConnectToRunningContainers; + public ConnectionMode getConnectionMode() { + return connectionMode; } public String getDockerServerUri() { @@ -133,8 +133,8 @@ public static CubeConfiguration fromMap(Map map) { cubeConfiguration.autoStartContainers = ConfigUtil.trim(map.get(AUTO_START_CONTAINERS).split(",")); } - if(map.containsKey(SHOULD_ALLOW_TO_CONNECT_TO_RUNNING_CONTAINERS)) { - cubeConfiguration.shouldAllowToConnectToRunningContainers = Boolean.parseBoolean(map.get(SHOULD_ALLOW_TO_CONNECT_TO_RUNNING_CONTAINERS)); + if(map.containsKey(CONNECTION_MODE)) { + cubeConfiguration.connectionMode = ConnectionMode.valueOf(ConnectionMode.class, map.get(CONNECTION_MODE)); } return cubeConfiguration; } diff --git a/docker/src/main/java/org/arquillian/cube/impl/client/CubeSuiteLifecycleController.java b/docker/src/main/java/org/arquillian/cube/impl/client/CubeSuiteLifecycleController.java index 4c09adfbc..63b756534 100644 --- a/docker/src/main/java/org/arquillian/cube/impl/client/CubeSuiteLifecycleController.java +++ b/docker/src/main/java/org/arquillian/cube/impl/client/CubeSuiteLifecycleController.java @@ -35,7 +35,7 @@ public class CubeSuiteLifecycleController { public void startAutoContainers(@Observes(precedence = 100) BeforeSuite event, final CubeConfiguration configuration) { List autoStartSteps = AutoStartOrderUtil.getAutoStartOrder(configuration); - startAllSteps(autoStartSteps, configuration.shouldAllowToConnectToRunningContainers()); + startAllSteps(autoStartSteps, configuration.getConnectionMode()); } public void stopAutoContainers(@Observes(precedence = -100) AfterSuite event, CubeConfiguration configuration) { @@ -43,13 +43,13 @@ public void stopAutoContainers(@Observes(precedence = -100) AfterSuite event, Cu stopAllSteps(autoStopSteps); } - private void startAllSteps(List autoStartSteps, boolean allowToConnectToRunningContainers) { + private void startAllSteps(List autoStartSteps, ConnectionMode connectionMode) { for(final String[] cubeIds : autoStartSteps) { Map> stepStatus = new HashMap<>(); // Start for(final String cubeId : cubeIds) { - Future result = executorServiceInst.get().submit(new StartCubes(cubeId, allowToConnectToRunningContainers)); + Future result = executorServiceInst.get().submit(new StartCubes(cubeId, connectionMode)); stepStatus.put(cubeId, result); } @@ -100,22 +100,28 @@ private boolean isCubeRunning(String cube) { } private final class StartCubes implements Callable { - private final boolean allowToConnectToRunningContainers; + private final ConnectionMode connectionMode; private final String cubeId; - private StartCubes(String cubeId, boolean shouldAllowToConnectToRunningContainers) { + private StartCubes(String cubeId, ConnectionMode connectionMode) { this.cubeId = cubeId; - this.allowToConnectToRunningContainers = shouldAllowToConnectToRunningContainers; + this.connectionMode = connectionMode; } @Override public RuntimeException call() throws Exception { try { - if(allowToConnectToRunningContainers && isCubeRunning(cubeId)) { + if(connectionMode.isAllowReconnect() && isCubeRunning(cubeId)) { controlEvent.fire(new PreRunningCube(cubeId)); - } else { - controlEvent.fire(new CreateCube(cubeId)); - controlEvent.fire(new StartCube(cubeId)); + return null; + } + controlEvent.fire(new CreateCube(cubeId)); + controlEvent.fire(new StartCube(cubeId)); + + if(connectionMode.isAllowReconnect() && !connectionMode.isStoppable()) { + // If we allow reconnections and containers are none stoppable which means that they will be able to be + // reused in next executions then at this point we can assume that the container is a prerunning container. + controlEvent.fire(new PreRunningCube(cubeId)); } } catch(RuntimeException e) { return e; diff --git a/docker/src/main/java/org/arquillian/cube/impl/client/container/CubeContainerLifecycleController.java b/docker/src/main/java/org/arquillian/cube/impl/client/container/CubeContainerLifecycleController.java index 8938803d1..f015f5b23 100644 --- a/docker/src/main/java/org/arquillian/cube/impl/client/container/CubeContainerLifecycleController.java +++ b/docker/src/main/java/org/arquillian/cube/impl/client/container/CubeContainerLifecycleController.java @@ -2,6 +2,7 @@ import java.util.List; +import org.arquillian.cube.impl.client.ConnectionMode; import org.arquillian.cube.impl.client.CubeConfiguration; import org.arquillian.cube.impl.docker.DockerClientExecutor; import org.arquillian.cube.impl.util.ContainerUtil; @@ -29,7 +30,7 @@ public class CubeContainerLifecycleController { @Inject private Instance dockerClientExecutor; - + public void startCubeMappedContainer(@Observes BeforeStart event, CubeRegistry cubeRegistry, ContainerRegistry containerRegistry, CubeConfiguration cubeConfiguration) { Container container = ContainerUtil.getContainerByDeployableContainer(containerRegistry, @@ -42,14 +43,22 @@ public void startCubeMappedContainer(@Observes BeforeStart event, CubeRegistry c if (cube == null) { return; // No Cube found matching Container name, not managed by Cube } + ConnectionMode connectionMode = cubeConfiguration.getConnectionMode(); - if(cubeConfiguration.shouldAllowToConnectToRunningContainers() && isCubeRunning(cube)) { + if (connectionMode.isAllowReconnect() && isCubeRunning(cube)) { controlEvent.fire(new PreRunningCube(cube)); - return; //Container is already running and user has configured to reuse it. + return; } - + controlEvent.fire(new CreateCube(cube)); controlEvent.fire(new StartCube(cube)); + + if (connectionMode.isAllowReconnect() && !connectionMode.isStoppable()) { + // If we allow reconnections and containers are none stoppable which means that they will be able to be + // reused in next executions then at this point we can assume that the container is a prerunning container. + + controlEvent.fire(new PreRunningCube(cube)); + } } public void stopCubeMappedContainer(@Observes AfterStop event, CubeRegistry cubeRegistry, @@ -68,19 +77,22 @@ public void stopCubeMappedContainer(@Observes AfterStop event, CubeRegistry cube controlEvent.fire(new StopCube(cube)); controlEvent.fire(new DestroyCube(cube)); } - + private boolean isCubeRunning(Cube cube) { - //TODO should we create an adapter class so we don't expose client classes in this part? - List runningContainers = dockerClientExecutor.get().listRunningContainers(); + // TODO should we create an adapter class so we don't expose client classes in this part? + List runningContainers = dockerClientExecutor.get() + .listRunningContainers(); for (com.github.dockerjava.api.model.Container container : runningContainers) { for (String name : container.getNames()) { - if(name.startsWith("/")) name = name.substring(1); //Names array adds an slash to the docker name container. - if(name.equals(cube.getId())) { //cube id is the container name in docker0 Id in docker is the hash that identifies it. + if (name.startsWith("/")) + name = name.substring(1); // Names array adds an slash to the docker name container. + if (name.equals(cube.getId())) { // cube id is the container name in docker0 Id in docker is the hash + // that identifies it. return true; } } } - + return false; } } 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 9d958e17c..96b4ab173 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 @@ -54,6 +54,7 @@ import com.github.dockerjava.api.model.Ports.Binding; import com.github.dockerjava.api.model.RestartPolicy; import com.github.dockerjava.api.model.Volume; +import com.github.dockerjava.api.model.VolumesFrom; import com.github.dockerjava.core.DockerClientBuilder; import com.github.dockerjava.core.DockerClientConfig; import com.github.dockerjava.core.DockerClientConfig.DockerClientConfigBuilder; @@ -282,7 +283,7 @@ public String createContainer(String name, Map containerConfigur if (containerConfiguration.containsKey(VOLUMES_FROM)) { List volumesFrom = asListOfString(containerConfiguration, VOLUMES_FROM); - createContainerCmd.withVolumesFrom(volumesFrom.toArray(new String[volumesFrom.size()])); + createContainerCmd.withVolumesFrom(toVolumesFrom(volumesFrom)); } try { @@ -755,6 +756,15 @@ private static final Volume[] toVolumes(List volumesList) { return volumes; } + private static final VolumesFrom[] toVolumesFrom(List volumesFromList) { + VolumesFrom[] volumesFrom = new VolumesFrom[volumesFromList.size()]; + + for(int i = 0; i < volumesFromList.size(); i++) { + volumesFrom[i] = VolumesFrom.parse(volumesFromList.get(i)); + } + return volumesFrom; + } + @SuppressWarnings("unchecked") private static final List> asListOfMap(Map map, String property) { return (List>) map.get(property); diff --git a/docker/src/test/java/org/arquillian/cube/impl/client/CubeSuiteLifecycleControllerTest.java b/docker/src/test/java/org/arquillian/cube/impl/client/CubeSuiteLifecycleControllerTest.java index 265312cfe..4ec9dea11 100644 --- a/docker/src/test/java/org/arquillian/cube/impl/client/CubeSuiteLifecycleControllerTest.java +++ b/docker/src/test/java/org/arquillian/cube/impl/client/CubeSuiteLifecycleControllerTest.java @@ -77,7 +77,7 @@ public void shouldStopAndDestroyAutoContainers() { public void shouldUsePreRunningContainers() { Map data = new HashMap(); data.put("autoStartContainers", "a,b"); - data.put("shouldAllowToConnectToRunningContainers", "true"); + data.put("connectionMode", ConnectionMode.STARTORCONNECT.name()); data.put("dockerContainers", "a:\n image: a\nb:\n image: a\n"); CubeConfiguration configuration = CubeConfiguration.fromMap(data); @@ -96,4 +96,51 @@ public void shouldUsePreRunningContainers() { assertEventFiredOnOtherThread(StartCube.class); assertEventFiredOnOtherThread(PreRunningCube.class); } + + @Test + public void shouldStartAContainerInStartOrConnectModeAndStopIt() { + Map data = new HashMap(); + data.put("autoStartContainers", "a,b"); + data.put("connectionMode", ConnectionMode.STARTORCONNECT.name()); + data.put("dockerContainers", "a:\n image: a\nb:\n image: a\n"); + + CubeConfiguration configuration = CubeConfiguration.fromMap(data); + bind(ApplicationScoped.class, CubeConfiguration.class, configuration); + Container container = mock(Container.class); + when(container.getNames()).thenReturn(new String[]{"alreadyrun"}); + when(executor.listRunningContainers()).thenReturn(Arrays.asList(container)); + bind(ApplicationScoped.class, DockerClientExecutor.class, executor); + + fire(new BeforeSuite()); + + assertEventFired(CreateCube.class, 2); + assertEventFired(StartCube.class, 2); + assertEventFired(PreRunningCube.class, 0); + assertEventFiredOnOtherThread(CreateCube.class); + assertEventFiredOnOtherThread(StartCube.class); + } + + @Test + public void shouldStartAContainerInStartOrConnectAndLeaveModeAndNotStopIt() { + Map data = new HashMap(); + data.put("autoStartContainers", "a,b"); + data.put("connectionMode", ConnectionMode.STARTORCONNECTANDLEAVE.name()); + data.put("dockerContainers", "a:\n image: a\nb:\n image: a\n"); + + CubeConfiguration configuration = CubeConfiguration.fromMap(data); + bind(ApplicationScoped.class, CubeConfiguration.class, configuration); + Container container = mock(Container.class); + when(container.getNames()).thenReturn(new String[]{"alreadyrun"}); + when(executor.listRunningContainers()).thenReturn(Arrays.asList(container)); + bind(ApplicationScoped.class, DockerClientExecutor.class, executor); + + fire(new BeforeSuite()); + + assertEventFired(CreateCube.class, 2); + assertEventFired(StartCube.class, 2); + assertEventFired(PreRunningCube.class, 2); + assertEventFiredOnOtherThread(CreateCube.class); + assertEventFiredOnOtherThread(StartCube.class); + assertEventFiredOnOtherThread(PreRunningCube.class); + } } diff --git a/docker/src/test/java/org/arquillian/cube/impl/client/container/CubeContainerLifecycleControllerTest.java b/docker/src/test/java/org/arquillian/cube/impl/client/container/CubeContainerLifecycleControllerTest.java index 38b79a9c4..18d8b0e02 100644 --- a/docker/src/test/java/org/arquillian/cube/impl/client/container/CubeContainerLifecycleControllerTest.java +++ b/docker/src/test/java/org/arquillian/cube/impl/client/container/CubeContainerLifecycleControllerTest.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; +import org.arquillian.cube.impl.client.ConnectionMode; import org.arquillian.cube.impl.client.CubeConfiguration; import org.arquillian.cube.impl.docker.DockerClientExecutor; import org.arquillian.cube.impl.model.DockerCubeRegistry; @@ -81,9 +82,9 @@ public void setup() { } @Test - public void shouldUsePreRunningContainer() { + public void shouldUsePreRunningContainerInStartOrConnectMode() { Map data = new HashMap(); - data.put("shouldAllowToConnectToRunningContainers", "true"); + data.put("connectionMode", ConnectionMode.STARTORCONNECT.name()); bind(ApplicationScoped.class, CubeConfiguration.class, CubeConfiguration.fromMap(data)); com.github.dockerjava.api.model.Container container = mock(com.github.dockerjava.api.model.Container.class); @@ -94,7 +95,41 @@ public void shouldUsePreRunningContainer() { fire(new BeforeStart(deployableContainer)); assertEventFired(PreRunningCube.class, 1); } - + + @Test + public void shouldStartAContainerInStartOrConnectModeAndStopIt() { + Map data = new HashMap(); + data.put("connectionMode", ConnectionMode.STARTORCONNECT.name()); + bind(ApplicationScoped.class, CubeConfiguration.class, CubeConfiguration.fromMap(data)); + + com.github.dockerjava.api.model.Container container = mock(com.github.dockerjava.api.model.Container.class); + when(container.getNames()).thenReturn(new String[]{CUBE_ID+"1"}); + when(executor.listRunningContainers()).thenReturn(Arrays.asList(container)); + bind(ApplicationScoped.class, DockerClientExecutor.class, executor); + + fire(new BeforeStart(deployableContainer)); + assertEventFired(CreateCube.class, 1); + assertEventFired(StartCube.class, 1); + assertEventFired(PreRunningCube.class, 0); + } + + @Test + public void shouldStartAContainerInStartOrConnectLeaveModeAndNotStopIt() { + Map data = new HashMap(); + data.put("connectionMode", ConnectionMode.STARTORCONNECTANDLEAVE.name()); + bind(ApplicationScoped.class, CubeConfiguration.class, CubeConfiguration.fromMap(data)); + + com.github.dockerjava.api.model.Container container = mock(com.github.dockerjava.api.model.Container.class); + when(container.getNames()).thenReturn(new String[]{CUBE_ID+"1"}); + when(executor.listRunningContainers()).thenReturn(Arrays.asList(container)); + bind(ApplicationScoped.class, DockerClientExecutor.class, executor); + + fire(new BeforeStart(deployableContainer)); + assertEventFired(CreateCube.class, 1); + assertEventFired(StartCube.class, 1); + assertEventFired(PreRunningCube.class, 1); + } + @Test public void shouldCreateAndStartCubeDuringBeforeStart() { fire(new BeforeStart(deployableContainer)); diff --git a/pom.xml b/pom.xml index 645aac63a..e324732bd 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ 1.1.7.Final 4.11 - 0.10.5 + 1.0.0 1.14 1.10.19