/*- * -\-\- * docker-client * -- * Copyright (C) 2016 Spotify AB * -- * Licensed 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 com.spotify.docker.test; import com.google.common.base.Preconditions; import com.spotify.docker.client.DockerClient; import com.spotify.docker.client.exceptions.ContainerNotFoundException; import com.spotify.docker.client.exceptions.DockerException; import com.spotify.docker.client.messages.ContainerConfig; import com.spotify.docker.client.messages.ContainerCreation; import java.lang.reflect.Method; import org.junit.rules.MethodRule; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.Statement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The {@link DockerContainer} is a JUnit {@link MethodRule} which automatically creates, starts, * kills and removes Docker containers used in unit tests. The settings for the commands can be * provided using the {@link CreateContainer} annotation on the test method. * * <p></p> For example: * * <pre> * {@code * public class DockerAttachTest { * * private static DockerClient dockerClient; * * @Rule * public DockerContainer dockerContainer = new DockerContainer(dockerClient); * * @BeforeClass * public static void setUp() throws DockerCertificateException { * dockerClient = DefaultDockerClient.fromEnv().readTimeoutMillis(120000).build(); * } * * @Test * @CreateContainer(image = "busybox", command = * {"sh", "-c", "echo \"test\""}, start = true) * public void testIt() throws IOException, DockerException, InterruptedException { * * String containerId = dockerContainer.getContainerId(); * dockerClient.waitContainer(containerId); * LogStream logStream = dockerClient.logs(containerId, LogsParameter.STDOUT); * assertThat(logStream.readFully(), equalTo("test\n")); * } * * @AfterClass * public static void cleanUp() { * dockerClient.close(); * } * * } * } * </pre> * * @author Jan-Willem Gmelig Meyling */ public class DockerContainer implements MethodRule { private static final Logger log = LoggerFactory.getLogger(DockerContainer.class); private final DockerClient dockerClient; private String containerId; public DockerContainer(final DockerClient dockerClient) { Preconditions.checkNotNull(dockerClient); this.dockerClient = dockerClient; } @Override public Statement apply(final Statement base, final FrameworkMethod method, final Object target) { final Method javaMethod = method.getMethod(); final CreateContainer createContainerAnnotation = javaMethod.getAnnotation(CreateContainer.class); if (createContainerAnnotation == null) { return base; } return new Statement() { @Override public void evaluate() throws Throwable { try { final ContainerCreation containerCreation = createContainer(createContainerAnnotation); containerId = containerCreation.id(); if (createContainerAnnotation.start()) { startContainer(createContainerAnnotation); } base.evaluate(); } finally { cleanup(); } } }; } /** * Create a new Docker container based on the {@link CreateContainer} annotation. * * @return {@link ContainerCreation} response */ protected ContainerCreation createContainer(CreateContainer createContainerAnnotation) throws DockerException, InterruptedException { Preconditions.checkNotNull(createContainerAnnotation); final ContainerConfig.Builder configBuilder = ContainerConfig.builder() .image(createContainerAnnotation.image()) .addVolumes(createContainerAnnotation.volumes()) .cmd(createContainerAnnotation.command()); final ContainerConfig createContainerConfig = configBuilder.build(); log.debug("Creating Docker container using config {}", createContainerConfig); final ContainerCreation creation = dockerClient.createContainer(createContainerConfig); containerId = creation.id(); log.debug("Created Docker container {}", containerId); return creation; } /** * Start a created container. */ protected void startContainer(CreateContainer createContainerAnnotation) throws DockerException, InterruptedException { Preconditions.checkNotNull(createContainerAnnotation); Preconditions.checkNotNull(containerId); log.debug("Starting Docker container {}", containerId); dockerClient.startContainer(containerId); log.debug("Started Docker container {}", containerId); } /** * Clean up created container. */ protected void cleanup() throws InterruptedException, DockerException { if (containerId == null) { // The container was never created return; } InterruptedException interuptedException = null; DockerException dockerException = null; try { while (dockerClient.inspectContainer(containerId).state().running()) { log.debug("Killing Docker container {}", containerId); dockerClient.killContainer(containerId); Thread.sleep(50); } log.debug("Killed Docker container {}", containerId); } catch (ContainerNotFoundException e) { // Already shutdown log.warn(e.getMessage(), e); } catch (DockerException e) { dockerException = e; } catch (InterruptedException e) { interuptedException = e; } try { log.debug("Removing Docker container {}", containerId); dockerClient.removeContainer(containerId); log.debug("Removed Docker container {}", containerId); } catch (ContainerNotFoundException e) { // Already removed log.warn(e.getMessage(), e); } catch (DockerException e) { dockerException = e; } catch (InterruptedException e) { interuptedException = e; } // Fail the test if clean up failed if (interuptedException != null) { throw interuptedException; } else if (dockerException != null) { throw dockerException; } } public DockerClient getDockerClient() { return dockerClient; } public String getContainerId() { return containerId; } }