package org.arquillian.cube.docker.impl.client; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.Future; import org.arquillian.cube.docker.impl.docker.DockerClientExecutor; import org.arquillian.cube.spi.ConnectionMode; import org.arquillian.cube.spi.CubeConfiguration; import org.arquillian.cube.spi.event.CreateCube; import org.arquillian.cube.spi.event.CubeControlEvent; 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.arquillian.cube.spi.event.lifecycle.AfterAutoStart; import org.arquillian.cube.spi.event.lifecycle.AfterAutoStop; import org.arquillian.cube.spi.event.lifecycle.BeforeAutoStart; import org.arquillian.cube.spi.event.lifecycle.BeforeAutoStop; import org.jboss.arquillian.core.api.Event; import org.jboss.arquillian.core.api.Instance; import org.jboss.arquillian.core.api.annotation.Inject; import org.jboss.arquillian.core.api.annotation.Observes; import org.jboss.arquillian.core.api.threading.ExecutorService; import org.jboss.arquillian.test.spi.event.suite.AfterSuite; import org.jboss.arquillian.test.spi.event.suite.BeforeSuite; public class CubeSuiteLifecycleController { @Inject private Event<AfterAutoStart> afterAutoStartEvent; @Inject private Event<BeforeAutoStart> beforeAutoStartEvent; @Inject private Event<BeforeAutoStop> beforeAutoStopEvent; @Inject private Event<AfterAutoStop> afterAutoStopEvent; @Inject private Event<CubeControlEvent> controlEvent; @Inject private Instance<ExecutorService> executorServiceInst; @Inject private Instance<DockerClientExecutor> dockerClientExecutor; public void startAutoContainers(@Observes(precedence = 100) BeforeSuite event, CubeConfiguration cubeConfiguration, CubeDockerConfiguration dockerConfiguration) { beforeAutoStartEvent.fire(new BeforeAutoStart()); final DockerAutoStartOrder dockerAutoStartOrder = dockerConfiguration.getDockerAutoStartOrder(); List<String[]> autoStartSteps = dockerAutoStartOrder.getAutoStartOrder(dockerConfiguration); startAllSteps(autoStartSteps, cubeConfiguration.getConnectionMode()); afterAutoStartEvent.fire(new AfterAutoStart()); } public void stopAutoContainers(@Observes(precedence = -100) AfterSuite event, CubeDockerConfiguration configuration) { beforeAutoStopEvent.fire(new BeforeAutoStop()); final DockerAutoStartOrder dockerAutoStartOrder = configuration.getDockerAutoStartOrder(); List<String[]> autoStopSteps = dockerAutoStartOrder.getAutoStopOrder(configuration); stopAllSteps(autoStopSteps); afterAutoStopEvent.fire(new AfterAutoStop()); } private void startAllSteps(List<String[]> autoStartSteps, ConnectionMode connectionMode) { for (final String[] cubeIds : autoStartSteps) { Map<String, Future<RuntimeException>> stepStatus = new HashMap<>(); // Start for (final String cubeId : cubeIds) { Future<RuntimeException> result = executorServiceInst.get().submit(new StartCubes(cubeId, connectionMode)); stepStatus.put(cubeId, result); } waitForCompletion(stepStatus, "Could not auto start container"); } } private void stopAllSteps(List<String[]> autoStopSteps) { for (final String[] cubeIds : autoStopSteps) { Map<String, Future<RuntimeException>> stepStatus = new HashMap<>(); // Start for (final String cubeId : cubeIds) { Future<RuntimeException> result = executorServiceInst.get().submit(new StopCubes(cubeId)); stepStatus.put(cubeId, result); } // wait waitForCompletion(stepStatus, "Could not auto stop container"); } } private void waitForCompletion(Map<String, Future<RuntimeException>> stepStatus, String message) { for (final Map.Entry<String, Future<RuntimeException>> result : stepStatus.entrySet()) { try { RuntimeException e = result.getValue().get(); if (e != null) { throw e; } } catch (Exception e) { throw new RuntimeException(message + " " + result.getKey(), e); } } } private boolean isCubeRunning(String cube) { //TODO should we create an adapter class so we don't expose client classes in this part? List<com.github.dockerjava.api.model.Container> 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)) { //cube id is the container name in docker0 Id in docker is the hash that identifies it. return true; } } } return false; } private final class StartCubes implements Callable<RuntimeException> { private final ConnectionMode connectionMode; private final String cubeId; private StartCubes(String cubeId, ConnectionMode connectionMode) { this.cubeId = cubeId; this.connectionMode = connectionMode; } @Override public RuntimeException call() throws Exception { try { if (connectionMode.isAllowReconnect() && isCubeRunning(cubeId)) { controlEvent.fire(new PreRunningCube(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; } return null; } } private final class StopCubes implements Callable<RuntimeException> { private final String cubeId; private StopCubes(String cubeId) { this.cubeId = cubeId; } @Override public RuntimeException call() throws Exception { try { controlEvent.fire(new StopCube(cubeId)); controlEvent.fire(new DestroyCube(cubeId)); } catch (RuntimeException e) { return e; } return null; } } }