diff --git a/src/main/java/com/palantir/docker/compose/DockerComposeRule.java b/src/main/java/com/palantir/docker/compose/DockerComposeRule.java index 52b3a7e24..fc4865a61 100644 --- a/src/main/java/com/palantir/docker/compose/DockerComposeRule.java +++ b/src/main/java/com/palantir/docker/compose/DockerComposeRule.java @@ -142,7 +142,7 @@ public void before() throws IOException, InterruptedException { @Override public void after() { try { - shutdownStrategy().shutdown(dockerCompose()); + shutdownStrategy().shutdown(this); logCollector().stopCollecting(); } catch (IOException | InterruptedException e) { throw new RuntimeException("Error cleaning up docker compose cluster", e); diff --git a/src/main/java/com/palantir/docker/compose/configuration/ShutdownStrategy.java b/src/main/java/com/palantir/docker/compose/configuration/ShutdownStrategy.java index 911fc5145..693daa52b 100644 --- a/src/main/java/com/palantir/docker/compose/configuration/ShutdownStrategy.java +++ b/src/main/java/com/palantir/docker/compose/configuration/ShutdownStrategy.java @@ -4,8 +4,8 @@ package com.palantir.docker.compose.configuration; +import com.palantir.docker.compose.DockerComposeRule; import com.palantir.docker.compose.execution.AggressiveShutdownStrategy; -import com.palantir.docker.compose.execution.DockerCompose; import com.palantir.docker.compose.execution.GracefulShutdownStrategy; import com.palantir.docker.compose.execution.SkipShutdownStrategy; import java.io.IOException; @@ -20,6 +20,6 @@ public interface ShutdownStrategy { ShutdownStrategy GRACEFUL = new GracefulShutdownStrategy(); ShutdownStrategy SKIP = new SkipShutdownStrategy(); - void shutdown(DockerCompose dockerCompose) throws IOException, InterruptedException; + void shutdown(DockerComposeRule rule) throws IOException, InterruptedException; } diff --git a/src/main/java/com/palantir/docker/compose/connection/ContainerName.java b/src/main/java/com/palantir/docker/compose/connection/ContainerName.java new file mode 100644 index 000000000..c8c187993 --- /dev/null +++ b/src/main/java/com/palantir/docker/compose/connection/ContainerName.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Palantir Technologies, Inc. All rights reserved. + */ + +package com.palantir.docker.compose.connection; + +import static java.util.stream.Collectors.joining; + +import java.util.Arrays; +import org.immutables.value.Value; + +@Value.Immutable +public abstract class ContainerName { + + public abstract String rawName(); + + public abstract String semanticName(); + + @Override + public String toString() { + return semanticName(); + } + + public static ContainerName fromPsLine(String psLine) { + String[] lineComponents = psLine.split(" "); + String rawName = lineComponents[0]; + + if (probablyCustomName(rawName)) { + return ImmutableContainerName.builder() + .rawName(rawName) + .semanticName(rawName) + .build(); + } + + String semanticName = withoutDirectory(withoutScaleNumber(rawName)); + return ImmutableContainerName.builder() + .rawName(rawName) + .semanticName(semanticName) + .build(); + } + + private static boolean probablyCustomName(String rawName) { + return !(rawName.split("_").length >= 3); + } + + private static String withoutDirectory(String rawName) { + return Arrays.stream(rawName.split("_")) + .skip(1) + .collect(joining("_")); + } + + public static String withoutScaleNumber(String rawName) { + String[] components = rawName.split("_"); + return Arrays.stream(components) + .limit(components.length - 1) + .collect(joining("_")); + } + +} diff --git a/src/main/java/com/palantir/docker/compose/connection/ContainerNames.java b/src/main/java/com/palantir/docker/compose/connection/ContainerNames.java index d6a8b1188..9d5048799 100644 --- a/src/main/java/com/palantir/docker/compose/connection/ContainerNames.java +++ b/src/main/java/com/palantir/docker/compose/connection/ContainerNames.java @@ -16,98 +16,33 @@ package com.palantir.docker.compose.connection; import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; -import java.util.Objects; -import java.util.function.Function; import java.util.stream.Stream; -public class ContainerNames implements Iterable { +public class ContainerNames { - private final List containerNames; + private ContainerNames() {} - public ContainerNames(String singleContainerName) { - this(singletonList(singleContainerName)); - } - - public ContainerNames(List containerNames) { - this.containerNames = containerNames; - } - - public static ContainerNames parseFromDockerComposePs(String psOutput) { - String[] splitOnSeparator = psOutput.split("-+\n"); - if (splitOnSeparator.length < 2) { - return new ContainerNames(emptyList()); + public static List parseFromDockerComposePs(String psOutput) { + String[] psHeadAndBody = psOutput.split("-+\n"); + if (psHeadAndBody.length < 2) { + return emptyList(); } - return new ContainerNames(getContainerNamesAtStartOfLines(splitOnSeparator[1])); - } - - private static List getContainerNamesAtStartOfLines(String psContainerOutput) { - return Arrays.stream(psContainerOutput.split("\n")) - .map(String::trim) - .filter(line -> !line.isEmpty()) - .map(line -> line.split(" ")) - .map(psColumns -> psColumns[0]) - .map(withoutDirectory().andThen(withoutScaleNumber())) - .collect(toList()); - } - - public static Function withoutDirectory() { - return fullname -> Arrays.stream(fullname.split("_")) - .skip(1) - .collect(joining("_")); - } - - public static Function withoutScaleNumber() { - return fullname -> { - final String[] components = fullname.split("_"); - return Arrays.stream(components) - .limit(components.length - 1) - .collect(joining("_")); - }; - } - @Override - public Iterator iterator() { - return containerNames.iterator(); - } - - public Stream stream() { - return containerNames.stream(); - } - - public int size() { - return containerNames.size(); - } - - @Override - public int hashCode() { - return Objects.hash(containerNames); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - ContainerNames other = (ContainerNames) obj; - return Objects.equals(containerNames, other.containerNames); + String psBody = psHeadAndBody[1]; + return psBodyLines(psBody) + .map(ContainerName::fromPsLine) + .collect(toList()); } - @Override - public String toString() { - return "ContainerNames [containerNames=" + containerNames + "]"; + private static Stream psBodyLines(String psBody) { + String[] lines = psBody.split("\n"); + return Arrays.stream(lines) + .map(String::trim) + .filter(line -> !line.isEmpty()); } } diff --git a/src/main/java/com/palantir/docker/compose/execution/AggressiveShutdownStrategy.java b/src/main/java/com/palantir/docker/compose/execution/AggressiveShutdownStrategy.java index 5b0c822c6..d0dcadd1b 100644 --- a/src/main/java/com/palantir/docker/compose/execution/AggressiveShutdownStrategy.java +++ b/src/main/java/com/palantir/docker/compose/execution/AggressiveShutdownStrategy.java @@ -4,8 +4,13 @@ package com.palantir.docker.compose.execution; +import static java.util.stream.Collectors.toList; + +import com.palantir.docker.compose.DockerComposeRule; import com.palantir.docker.compose.configuration.ShutdownStrategy; +import com.palantir.docker.compose.connection.ContainerName; import java.io.IOException; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,9 +23,38 @@ public class AggressiveShutdownStrategy implements ShutdownStrategy { private static final Logger log = LoggerFactory.getLogger(AggressiveShutdownStrategy.class); @Override - public void shutdown(DockerCompose dockerCompose) throws IOException, InterruptedException { - log.info("Shutting down"); - dockerCompose.rm(); + public void shutdown(DockerComposeRule rule) throws IOException, InterruptedException { + List runningContainers = rule.dockerCompose().ps(); + + log.info("Shutting down {}", runningContainers.stream().map(ContainerName::semanticName).collect(toList())); + if (removeContainersCatchingErrors(rule, runningContainers)) { + return; + } + + log.debug("First shutdown attempted failed due to btrfs volume error... retrying"); + if (removeContainersCatchingErrors(rule, runningContainers)) { + return; + } + + log.warn("Couldn't shut down containers due to btrfs volume error, " + + "see https://circleci.com/docs/docker-btrfs-error/ for more info."); + } + + private boolean removeContainersCatchingErrors(DockerComposeRule rule, List runningContainers) throws IOException, InterruptedException { + try { + removeContainers(rule, runningContainers); + return true; + } catch (DockerExecutionException exception) { + return false; + } + } + + private void removeContainers(DockerComposeRule rule, List running) throws IOException, InterruptedException { + List rawContainerNames = running.stream() + .map(ContainerName::rawName) + .collect(toList()); + + rule.docker().rm(rawContainerNames); log.debug("Finished shutdown"); } diff --git a/src/main/java/com/palantir/docker/compose/execution/DefaultDockerCompose.java b/src/main/java/com/palantir/docker/compose/execution/DefaultDockerCompose.java index 301713381..ff59b51f7 100644 --- a/src/main/java/com/palantir/docker/compose/execution/DefaultDockerCompose.java +++ b/src/main/java/com/palantir/docker/compose/execution/DefaultDockerCompose.java @@ -25,12 +25,14 @@ import com.palantir.docker.compose.configuration.DockerComposeFiles; import com.palantir.docker.compose.configuration.ProjectName; import com.palantir.docker.compose.connection.Container; +import com.palantir.docker.compose.connection.ContainerName; import com.palantir.docker.compose.connection.ContainerNames; import com.palantir.docker.compose.connection.DockerMachine; import com.palantir.docker.compose.connection.Ports; import com.palantir.docker.compose.connection.State; import java.io.IOException; import java.io.OutputStream; +import java.util.List; import org.apache.commons.io.IOUtils; import org.joda.time.Duration; import org.slf4j.Logger; @@ -150,7 +152,7 @@ private String[] constructFullDockerComposeRunArguments(DockerComposeRunOption d } @Override - public ContainerNames ps() throws IOException, InterruptedException { + public List ps() throws IOException, InterruptedException { String psOutput = command.execute(Command.throwingOnError(), "ps"); return ContainerNames.parseFromDockerComposePs(psOutput); } diff --git a/src/main/java/com/palantir/docker/compose/execution/DelegatingDockerCompose.java b/src/main/java/com/palantir/docker/compose/execution/DelegatingDockerCompose.java index 7779e37e4..17c241868 100644 --- a/src/main/java/com/palantir/docker/compose/execution/DelegatingDockerCompose.java +++ b/src/main/java/com/palantir/docker/compose/execution/DelegatingDockerCompose.java @@ -16,11 +16,12 @@ package com.palantir.docker.compose.execution; import com.palantir.docker.compose.connection.Container; -import com.palantir.docker.compose.connection.ContainerNames; +import com.palantir.docker.compose.connection.ContainerName; import com.palantir.docker.compose.connection.Ports; import com.palantir.docker.compose.connection.State; import java.io.IOException; import java.io.OutputStream; +import java.util.List; abstract class DelegatingDockerCompose implements DockerCompose { private final DockerCompose dockerCompose; @@ -87,7 +88,7 @@ public String run(DockerComposeRunOption dockerComposeRunOption, String containe } @Override - public ContainerNames ps() throws IOException, InterruptedException { + public List ps() throws IOException, InterruptedException { return dockerCompose.ps(); } diff --git a/src/main/java/com/palantir/docker/compose/execution/DockerCompose.java b/src/main/java/com/palantir/docker/compose/execution/DockerCompose.java index 464f33b62..aea28a238 100644 --- a/src/main/java/com/palantir/docker/compose/execution/DockerCompose.java +++ b/src/main/java/com/palantir/docker/compose/execution/DockerCompose.java @@ -16,11 +16,12 @@ package com.palantir.docker.compose.execution; import com.palantir.docker.compose.connection.Container; -import com.palantir.docker.compose.connection.ContainerNames; +import com.palantir.docker.compose.connection.ContainerName; import com.palantir.docker.compose.connection.Ports; import com.palantir.docker.compose.connection.State; import java.io.IOException; import java.io.OutputStream; +import java.util.List; public interface DockerCompose { void build() throws IOException, InterruptedException; @@ -34,7 +35,7 @@ public interface DockerCompose { void kill(Container container) throws IOException, InterruptedException; String exec(DockerComposeExecOption dockerComposeExecOption, String containerName, DockerComposeExecArgument dockerComposeExecArgument) throws IOException, InterruptedException; String run(DockerComposeRunOption dockerComposeRunOption, String containerName, DockerComposeRunArgument dockerComposeRunArgument) throws IOException, InterruptedException; - ContainerNames ps() throws IOException, InterruptedException; + List ps() throws IOException, InterruptedException; Container container(String containerName); boolean writeLogs(String container, OutputStream output) throws IOException; Ports ports(String service) throws IOException, InterruptedException; diff --git a/src/main/java/com/palantir/docker/compose/execution/GracefulShutdownStrategy.java b/src/main/java/com/palantir/docker/compose/execution/GracefulShutdownStrategy.java index aea00dafe..17b3a16a2 100644 --- a/src/main/java/com/palantir/docker/compose/execution/GracefulShutdownStrategy.java +++ b/src/main/java/com/palantir/docker/compose/execution/GracefulShutdownStrategy.java @@ -4,6 +4,7 @@ package com.palantir.docker.compose.execution; +import com.palantir.docker.compose.DockerComposeRule; import com.palantir.docker.compose.configuration.ShutdownStrategy; import java.io.IOException; import org.slf4j.Logger; @@ -18,11 +19,11 @@ public class GracefulShutdownStrategy implements ShutdownStrategy { private static final Logger log = LoggerFactory.getLogger(GracefulShutdownStrategy.class); @Override - public void shutdown(DockerCompose dockerCompose) throws IOException, InterruptedException { + public void shutdown(DockerComposeRule rule) throws IOException, InterruptedException { log.debug("Killing docker-compose cluster"); - dockerCompose.down(); - dockerCompose.kill(); - dockerCompose.rm(); + rule.dockerCompose().down(); + rule.dockerCompose().kill(); + rule.dockerCompose().rm(); } } diff --git a/src/main/java/com/palantir/docker/compose/execution/RetryingDockerCompose.java b/src/main/java/com/palantir/docker/compose/execution/RetryingDockerCompose.java index a6298d525..de173391b 100644 --- a/src/main/java/com/palantir/docker/compose/execution/RetryingDockerCompose.java +++ b/src/main/java/com/palantir/docker/compose/execution/RetryingDockerCompose.java @@ -15,8 +15,9 @@ */ package com.palantir.docker.compose.execution; -import com.palantir.docker.compose.connection.ContainerNames; +import com.palantir.docker.compose.connection.ContainerName; import java.io.IOException; +import java.util.List; public class RetryingDockerCompose extends DelegatingDockerCompose { private final Retryer retryer; @@ -39,7 +40,7 @@ public void up() throws IOException, InterruptedException { } @Override - public ContainerNames ps() throws IOException, InterruptedException { + public List ps() throws IOException, InterruptedException { return retryer.runWithRetries(super::ps); } } diff --git a/src/main/java/com/palantir/docker/compose/execution/SkipShutdownStrategy.java b/src/main/java/com/palantir/docker/compose/execution/SkipShutdownStrategy.java index e3a8d70a7..039927fa8 100644 --- a/src/main/java/com/palantir/docker/compose/execution/SkipShutdownStrategy.java +++ b/src/main/java/com/palantir/docker/compose/execution/SkipShutdownStrategy.java @@ -4,6 +4,7 @@ package com.palantir.docker.compose.execution; +import com.palantir.docker.compose.DockerComposeRule; import com.palantir.docker.compose.configuration.ShutdownStrategy; import java.io.IOException; import org.slf4j.Logger; @@ -14,7 +15,7 @@ public class SkipShutdownStrategy implements ShutdownStrategy { private static final Logger log = LoggerFactory.getLogger(SkipShutdownStrategy.class); @Override - public void shutdown(DockerCompose dockerCompose) throws IOException, InterruptedException { + public void shutdown(DockerComposeRule rule) throws IOException, InterruptedException { log.warn("\n" + "******************************************************************************************\n" + "* docker-compose-rule has been configured to skip docker-compose shutdown: *\n" diff --git a/src/main/java/com/palantir/docker/compose/logging/FileLogCollector.java b/src/main/java/com/palantir/docker/compose/logging/FileLogCollector.java index 18a77e5c5..a3433f1ed 100644 --- a/src/main/java/com/palantir/docker/compose/logging/FileLogCollector.java +++ b/src/main/java/com/palantir/docker/compose/logging/FileLogCollector.java @@ -17,11 +17,12 @@ import static com.google.common.base.Preconditions.checkArgument; -import com.palantir.docker.compose.connection.ContainerNames; +import com.palantir.docker.compose.connection.ContainerName; import com.palantir.docker.compose.execution.DockerCompose; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -56,12 +57,13 @@ public synchronized void startCollecting(DockerCompose dockerCompose) throws IOE if (executor != null) { throw new RuntimeException("Cannot start collecting the same logs twice"); } - ContainerNames containerNames = dockerCompose.ps(); + List containerNames = dockerCompose.ps(); if (containerNames.size() == 0) { return; } executor = Executors.newFixedThreadPool(containerNames.size()); containerNames.stream() + .map(ContainerName::semanticName) .forEachOrdered(container -> this.collectLogs(container, dockerCompose)); } diff --git a/src/test/java/com/palantir/docker/compose/AggressiveShutdownStrategyIntegrationTest.java b/src/test/java/com/palantir/docker/compose/AggressiveShutdownStrategyIntegrationTest.java new file mode 100644 index 000000000..058e15e0d --- /dev/null +++ b/src/test/java/com/palantir/docker/compose/AggressiveShutdownStrategyIntegrationTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016 Palantir Technologies, Inc. All rights reserved. + */ + +package com.palantir.docker.compose; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import com.palantir.docker.compose.configuration.ShutdownStrategy; +import com.palantir.docker.compose.logging.DoNothingLogCollector; +import org.junit.Test; + +public class AggressiveShutdownStrategyIntegrationTest { + + @Test + public void shut_down_multiple_containers_immediately() throws Exception { + DockerComposeRule rule = DockerComposeRule.builder() + .file("src/test/resources/shutdown-strategy.yaml") + .logCollector(new DoNothingLogCollector()) + .retryAttempts(0) + .shutdownStrategy(ShutdownStrategy.AGGRESSIVE) + .build(); + + assertThat(rule.dockerCompose().ps(), is(TestContainerNames.of())); + + rule.before(); + assertThat(rule.dockerCompose().ps().size(), is(2)); + rule.after(); + + assertThat(rule.dockerCompose().ps(), is(TestContainerNames.of())); + } + +} diff --git a/src/test/java/com/palantir/docker/compose/AggressiveShutdownStrategyTest.java b/src/test/java/com/palantir/docker/compose/AggressiveShutdownStrategyTest.java new file mode 100644 index 000000000..f166af013 --- /dev/null +++ b/src/test/java/com/palantir/docker/compose/AggressiveShutdownStrategyTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2016 Palantir Technologies, Inc. All rights reserved. + */ + +package com.palantir.docker.compose; + +import static org.mockito.Matchers.anyListOf; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.palantir.docker.compose.configuration.ShutdownStrategy; +import com.palantir.docker.compose.execution.Docker; +import com.palantir.docker.compose.execution.DockerCompose; +import com.palantir.docker.compose.execution.DockerExecutionException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class AggressiveShutdownStrategyTest { + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + private final DockerComposeRule rule = mock(DockerComposeRule.class); + private final Docker mockDocker = mock(Docker.class); + + private static final String btrfs_message = "'docker rm -f test-1.container.name test-2.container.name' " + + "returned exit code 1\nThe output was:\nFailed to remove container (test-1.container.name): " + + "Error response from daemon: Driver btrfs failed to remove root filesystem "; + + @Before + public void before() { + when(rule.dockerCompose()).thenReturn(mock(DockerCompose.class)); + when(rule.docker()).thenReturn(mockDocker); + } + + @Test + public void first_btrfs_error_should_be_caught_silently_and_retried() throws Exception { + doThrow(new DockerExecutionException(btrfs_message)) + .doNothing() + .when(mockDocker) + .rm(anyListOf(String.class)); + + ShutdownStrategy.AGGRESSIVE.shutdown(rule); + + verify(mockDocker, times(2)).rm(anyListOf(String.class)); + } + + @Test + public void after_two_btrfs_failures_we_should_just_log_and_continue() throws Exception { + doThrow(new DockerExecutionException(btrfs_message)) + .doThrow(new DockerExecutionException(btrfs_message)) + .when(mockDocker) + .rm(anyListOf(String.class)); + + ShutdownStrategy.AGGRESSIVE.shutdown(rule); + + verify(mockDocker, times(2)).rm(anyListOf(String.class)); + } + +} diff --git a/src/test/java/com/palantir/docker/compose/DockerComposeRuleShould.java b/src/test/java/com/palantir/docker/compose/DockerComposeRuleShould.java index 90580c478..5eff080c1 100644 --- a/src/test/java/com/palantir/docker/compose/DockerComposeRuleShould.java +++ b/src/test/java/com/palantir/docker/compose/DockerComposeRuleShould.java @@ -37,7 +37,6 @@ import com.palantir.docker.compose.configuration.MockDockerEnvironment; import com.palantir.docker.compose.configuration.ShutdownStrategy; import com.palantir.docker.compose.connection.Container; -import com.palantir.docker.compose.connection.ContainerNames; import com.palantir.docker.compose.connection.DockerMachine; import com.palantir.docker.compose.connection.DockerPort; import com.palantir.docker.compose.connection.waiting.HealthCheck; @@ -109,7 +108,7 @@ public void call_build_and_up_before_tests_are_run() throws IOException, Interru @Test public void calls_shutdownStrategy_in_after_method() throws IOException, InterruptedException { rule.after(); - verify(shutdownStrategy).shutdown(dockerCompose); + verify(shutdownStrategy).shutdown(rule); } @Test @@ -208,7 +207,7 @@ public void be_able_to_save_logs_to_a_directory_while_containers_are_running() throws IOException, InterruptedException { File logLocation = logFolder.newFolder(); DockerComposeRule loggingComposition = DockerComposeRule.builder().from(rule).saveLogsTo(logLocation.getAbsolutePath()).build(); - when(dockerCompose.ps()).thenReturn(new ContainerNames("db")); + when(dockerCompose.ps()).thenReturn(TestContainerNames.of("db")); CountDownLatch latch = new CountDownLatch(1); when(dockerCompose.writeLogs(eq("db"), any(OutputStream.class))).thenAnswer((args) -> { OutputStream outputStream = (OutputStream) args.getArguments()[1]; diff --git a/src/test/java/com/palantir/docker/compose/TestContainerNames.java b/src/test/java/com/palantir/docker/compose/TestContainerNames.java new file mode 100644 index 000000000..c539a4ff7 --- /dev/null +++ b/src/test/java/com/palantir/docker/compose/TestContainerNames.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Palantir Technologies, Inc. All rights reserved. + */ + +package com.palantir.docker.compose; + +import static java.util.stream.Collectors.toList; + +import com.palantir.docker.compose.connection.ContainerName; +import com.palantir.docker.compose.connection.ImmutableContainerName; +import java.util.Arrays; +import java.util.List; + +public class TestContainerNames { + + private TestContainerNames() {} + + public static List of(String... semanticNames) { + return Arrays.stream(semanticNames) + .map(TestContainerNames::testContainerName) + .collect(toList()); + } + + private static ContainerName testContainerName(String testName) { + return ImmutableContainerName.builder() + .semanticName(testName) + .rawName("123456_" + testName + "_1") + .build(); + } + +} diff --git a/src/test/java/com/palantir/docker/compose/connection/ContainerNameShould.java b/src/test/java/com/palantir/docker/compose/connection/ContainerNameShould.java new file mode 100644 index 000000000..c47829b2d --- /dev/null +++ b/src/test/java/com/palantir/docker/compose/connection/ContainerNameShould.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016 Palantir Technologies, Inc. All rights reserved. + */ + +package com.palantir.docker.compose.connection; + +import static java.util.Collections.emptyList; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +import java.util.List; +import org.junit.Test; + +public class ContainerNameShould { + + @Test + public void parse_a_semantic_and_raw_name_correctly_from_a_single_line() { + ContainerName actual = ContainerName.fromPsLine("dir_db_1 other line contents"); + + ContainerName expected = ImmutableContainerName.builder() + .rawName("dir_db_1") + .semanticName("db") + .build(); + + assertThat(actual, is(expected)); + } + + @Test + public void can_handle_custom_container_names() { + ContainerName name = ContainerName.fromPsLine("test-1.container.name /docker-entrypoint.sh postgres Up 5432/tcp"); + + ContainerName expected = ImmutableContainerName.builder() + .rawName("test-1.container.name") + .semanticName("test-1.container.name") + .build(); + + assertThat(name, is(expected)); + } + + @Test + public void result_in_no_container_names_when_ps_output_is_empty() { + List names = ContainerNames.parseFromDockerComposePs("\n----\n"); + assertThat(names, is(emptyList())); + } + + @Test + public void result_in_a_single_container_name_when_ps_output_has_a_single_container() { + List names = ContainerNames.parseFromDockerComposePs("\n----\ndir_db_1 other line contents"); + assertThat(names, contains(containerName("dir", "db", "1"))); + } + + @Test + public void allow_containers_with_underscores_in_their_name() { + List names = ContainerNames.parseFromDockerComposePs("\n----\ndir_left_right_1 other line contents"); + assertThat(names, contains(containerName("dir", "left_right", "1"))); + } + + @Test + public void result_in_two_container_names_when_ps_output_has_two_containers() { + List names = ContainerNames.parseFromDockerComposePs("\n----\ndir_db_1 other line contents\ndir_db2_1 other stuff"); + assertThat(names, contains(containerName("dir", "db", "1"), containerName("dir", "db2", "1"))); + } + + @Test + public void ignore_an_empty_line_in_ps_output() { + List names = ContainerNames.parseFromDockerComposePs("\n----\ndir_db_1 other line contents\n\n"); + assertThat(names, contains(containerName("dir", "db", "1"))); + } + + @Test + public void ignore_a_line_with_ony_spaces_in_ps_output() { + List names = ContainerNames.parseFromDockerComposePs("\n----\ndir_db_1 other line contents\n \n"); + assertThat(names, contains(containerName("dir", "db", "1"))); + } + + private static ContainerName containerName(String project, String semantic, String number) { + return ImmutableContainerName.builder() + .rawName(project + "_" + semantic + "_" + number) + .semanticName(semantic) + .build(); + } +} diff --git a/src/test/java/com/palantir/docker/compose/connection/ContainerNamesShould.java b/src/test/java/com/palantir/docker/compose/connection/ContainerNamesShould.java deleted file mode 100644 index 3595f92e3..000000000 --- a/src/test/java/com/palantir/docker/compose/connection/ContainerNamesShould.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2016 Palantir Technologies, Inc. All rights reserved. - * - * 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.palantir.docker.compose.connection; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; - -import org.junit.Test; - -public class ContainerNamesShould { - - @Test - public void result_in_no_container_names_when_ps_output_is_empty() { - ContainerNames names = ContainerNames.parseFromDockerComposePs("\n----\n"); - assertThat(names, is(new ContainerNames(emptyList()))); - } - - @Test - public void result_in_a_single_container_name_when_ps_output_has_a_single_container() { - ContainerNames names = ContainerNames.parseFromDockerComposePs("\n----\ndir_db_1 other line contents"); - assertThat(names, is(new ContainerNames("db"))); - } - - @Test - public void allow_containers_with_underscores_in_their_name() { - ContainerNames names = ContainerNames.parseFromDockerComposePs("\n----\ndir_left_right_1 other line contents"); - assertThat(names, is(new ContainerNames("left_right"))); - } - - @Test - public void result_in_two_container_names_when_ps_output_has_two_containers() { - ContainerNames names = ContainerNames.parseFromDockerComposePs("\n----\ndir_db_1 other line contents\ndir_db2_1 other stuff"); - assertThat(names, is(new ContainerNames(asList("db", "db2")))); - } - - @Test - public void ignore_an_empty_line_in_ps_output() { - ContainerNames names = ContainerNames.parseFromDockerComposePs("\n----\ndir_db_1 other line contents\n\n"); - assertThat(names, is(new ContainerNames("db"))); - } - - @Test - public void ignore_a_line_with_ony_spaces_in_ps_output() { - ContainerNames names = ContainerNames.parseFromDockerComposePs("\n----\ndir_db_1 other line contents\n \n"); - assertThat(names, is(new ContainerNames("db"))); - } - -} diff --git a/src/test/java/com/palantir/docker/compose/execution/DockerComposeShould.java b/src/test/java/com/palantir/docker/compose/execution/DockerComposeShould.java index acce4450c..933642a49 100644 --- a/src/test/java/com/palantir/docker/compose/execution/DockerComposeShould.java +++ b/src/test/java/com/palantir/docker/compose/execution/DockerComposeShould.java @@ -18,6 +18,7 @@ import static com.palantir.docker.compose.execution.DockerComposeExecArgument.arguments; import static com.palantir.docker.compose.execution.DockerComposeExecOption.options; import static org.apache.commons.io.IOUtils.toInputStream; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.anyVararg; @@ -27,13 +28,15 @@ import static org.mockito.Mockito.when; import com.palantir.docker.compose.connection.Container; -import com.palantir.docker.compose.connection.ContainerNames; +import com.palantir.docker.compose.connection.ContainerName; import com.palantir.docker.compose.connection.DockerMachine; import com.palantir.docker.compose.connection.DockerPort; +import com.palantir.docker.compose.connection.ImmutableContainerName; import com.palantir.docker.compose.connection.Ports; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.List; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -87,9 +90,9 @@ public void call_docker_compose_start_on_start() throws IOException, Interrupted @Test public void parse_and_returns_container_names_on_ps() throws IOException, InterruptedException { when(executedProcess.getInputStream()).thenReturn(toInputStream("ps\n----\ndir_db_1")); - ContainerNames containerNames = compose.ps(); + List containerNames = compose.ps(); verify(executor).execute("ps"); - assertThat(containerNames, is(new ContainerNames("db"))); + assertThat(containerNames, contains(ImmutableContainerName.builder().semanticName("db").rawName("dir_db_1").build())); } @Test diff --git a/src/test/java/com/palantir/docker/compose/execution/GracefulShutdownStrategyShould.java b/src/test/java/com/palantir/docker/compose/execution/GracefulShutdownStrategyShould.java index c6710c9ce..c282643ab 100644 --- a/src/test/java/com/palantir/docker/compose/execution/GracefulShutdownStrategyShould.java +++ b/src/test/java/com/palantir/docker/compose/execution/GracefulShutdownStrategyShould.java @@ -6,7 +6,9 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import com.palantir.docker.compose.DockerComposeRule; import com.palantir.docker.compose.configuration.ShutdownStrategy; import org.junit.Test; import org.mockito.InOrder; @@ -15,8 +17,10 @@ public class GracefulShutdownStrategyShould { @Test public void call_down_then_kill_then_rm() throws Exception { + DockerComposeRule rule = mock(DockerComposeRule.class); DockerCompose dockerCompose = mock(DockerCompose.class); - ShutdownStrategy.GRACEFUL.shutdown(dockerCompose); + when(rule.dockerCompose()).thenReturn(dockerCompose); + ShutdownStrategy.GRACEFUL.shutdown(rule); InOrder inOrder = inOrder(dockerCompose); inOrder.verify(dockerCompose).down(); diff --git a/src/test/java/com/palantir/docker/compose/execution/RetryingDockerComposeShould.java b/src/test/java/com/palantir/docker/compose/execution/RetryingDockerComposeShould.java index a4a91e79c..ebdfa2ffb 100644 --- a/src/test/java/com/palantir/docker/compose/execution/RetryingDockerComposeShould.java +++ b/src/test/java/com/palantir/docker/compose/execution/RetryingDockerComposeShould.java @@ -26,8 +26,10 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import com.palantir.docker.compose.connection.ContainerNames; +import com.palantir.docker.compose.TestContainerNames; +import com.palantir.docker.compose.connection.ContainerName; import java.io.IOException; +import java.util.List; import org.junit.Before; import org.junit.Test; @@ -35,7 +37,7 @@ public class RetryingDockerComposeShould { private final DockerCompose dockerCompose = mock(DockerCompose.class); private final Retryer retryer = mock(Retryer.class); private final RetryingDockerCompose retryingDockerCompose = new RetryingDockerCompose(retryer, dockerCompose); - private final ContainerNames someContainerNames = new ContainerNames("hey"); + private final List someContainerNames = TestContainerNames.of("hey"); private static final String CONTAINER_NAME = "container"; @Before diff --git a/src/test/java/com/palantir/docker/compose/logging/FileLogCollectorShould.java b/src/test/java/com/palantir/docker/compose/logging/FileLogCollectorShould.java index 32ab12d3a..1f075196b 100644 --- a/src/test/java/com/palantir/docker/compose/logging/FileLogCollectorShould.java +++ b/src/test/java/com/palantir/docker/compose/logging/FileLogCollectorShould.java @@ -17,8 +17,6 @@ import static com.palantir.docker.compose.matchers.IOMatchers.fileContainingString; import static com.palantir.docker.compose.matchers.IOMatchers.fileWithName; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; @@ -30,7 +28,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.palantir.docker.compose.connection.ContainerNames; +import com.palantir.docker.compose.TestContainerNames; import com.palantir.docker.compose.execution.DockerCompose; import java.io.File; import java.io.IOException; @@ -96,7 +94,7 @@ public void throw_exception_when_created_if_the_log_directory_does_not_exist_and @Test public void not_collect_any_logs_when_no_containers_are_running() throws IOException, InterruptedException { - when(compose.ps()).thenReturn(new ContainerNames(emptyList())); + when(compose.ps()).thenReturn(TestContainerNames.of()); logCollector.startCollecting(compose); logCollector.stopCollecting(); assertThat(logDirectory.list(), is(emptyArray())); @@ -105,7 +103,7 @@ public void not_collect_any_logs_when_no_containers_are_running() throws IOExcep @Test public void collect_logs_when_one_container_is_running_and_terminates_before_start_collecting_is_run() throws Exception { - when(compose.ps()).thenReturn(new ContainerNames("db")); + when(compose.ps()).thenReturn(TestContainerNames.of("db")); when(compose.writeLogs(eq("db"), any(OutputStream.class))).thenAnswer((args) -> { OutputStream outputStream = (OutputStream) args.getArguments()[1]; IOUtils.write("log", outputStream); @@ -120,7 +118,7 @@ public void collect_logs_when_one_container_is_running_and_terminates_before_sta @Test public void collect_logs_when_one_container_is_running_and_does_not_terminate_until_after_start_collecting_is_run() throws Exception { - when(compose.ps()).thenReturn(new ContainerNames("db")); + when(compose.ps()).thenReturn(TestContainerNames.of("db")); CountDownLatch latch = new CountDownLatch(1); when(compose.writeLogs(eq("db"), any(OutputStream.class))).thenAnswer((args) -> { if (!latch.await(1, TimeUnit.SECONDS)) { @@ -140,7 +138,7 @@ public void collect_logs_when_one_container_is_running_and_does_not_terminate_un @Test public void collect_logs_when_one_container_is_running_and_does_not_terminate() throws IOException, InterruptedException { - when(compose.ps()).thenReturn(new ContainerNames("db")); + when(compose.ps()).thenReturn(TestContainerNames.of("db")); CountDownLatch latch = new CountDownLatch(1); when(compose.writeLogs(eq("db"), any(OutputStream.class))).thenAnswer((args) -> { OutputStream outputStream = (OutputStream) args.getArguments()[1]; @@ -164,7 +162,7 @@ public void collect_logs_when_one_container_is_running_and_does_not_terminate() @Test public void collect_logs_in_parallel_for_two_containers() throws IOException, InterruptedException { - when(compose.ps()).thenReturn(new ContainerNames(asList("db", "db2"))); + when(compose.ps()).thenReturn(TestContainerNames.of("db", "db2")); CountDownLatch dbLatch = new CountDownLatch(1); when(compose.writeLogs(eq("db"), any(OutputStream.class))).thenAnswer((args) -> { OutputStream outputStream = (OutputStream) args.getArguments()[1]; @@ -194,7 +192,7 @@ public void collect_logs_in_parallel_for_two_containers() throws IOException, In @Test public void throw_exception_when_trying_to_start_a_started_collector_a_second_time() throws IOException, InterruptedException { - when(compose.ps()).thenReturn(new ContainerNames("db")); + when(compose.ps()).thenReturn(TestContainerNames.of("db")); logCollector.startCollecting(compose); exception.expect(RuntimeException.class); exception.expectMessage("Cannot start collecting the same logs twice"); @@ -208,5 +206,4 @@ private File cannotBeCreatedDirectory() { when(cannotBeCreatedDirectory.getAbsolutePath()).thenReturn("cannot/exist/directory"); return cannotBeCreatedDirectory; } - } diff --git a/src/test/resources/shutdown-strategy.yaml b/src/test/resources/shutdown-strategy.yaml new file mode 100644 index 000000000..46f81eb6c --- /dev/null +++ b/src/test/resources/shutdown-strategy.yaml @@ -0,0 +1,11 @@ +infinite-netcat-loop: + image: appropriate/nc + command: sh -c 'while true; do nc -l 8080 < ./entrypoint.sh; done' + ports: + - "8080" + +infinite-netcat-loop2: + image: appropriate/nc + command: sh -c 'while true; do nc -l 8080 < ./entrypoint.sh; done' + ports: + - "8080"