From 14d400ab49db321e45c5262e5a89901a05e2633a Mon Sep 17 00:00:00 2001 From: Richard North Date: Sat, 11 Jun 2016 06:59:44 +0900 Subject: [PATCH] [WIP] docker compose refactoring, to support scaling, better output logs, and eventually docker-compose v2 format. Refs #146, #147 --- core/pom.xml | 12 +- .../testcontainers/containers/Container.java | 4 - .../containers/DockerComposeContainer.java | 130 +++++++++++++----- .../containers/GenericContainer.java | 50 +++---- .../IsRunningStartupCheckStrategy.java | 24 ++++ ...umDurationRunningStartupCheckStrategy.java | 39 ++++++ .../OneShotStartupCheckStrategy.java | 27 ++++ .../startupcheck/StartupCheckStrategy.java | 51 +++++++ .../testcontainers/utility/DockerStatus.java | 5 + .../junit/BaseDockerComposeTest.java | 50 +++++++ .../DockerComposeContainerScalingTest.java | 65 +++++++++ .../junit/DockerComposeContainerTest.java | 35 +---- .../junit/DockerComposeV2FormatTest.java | 21 +++ core/src/test/resources/redis.conf | 0 .../test/resources/scaled-compose-test.yml | 2 + core/src/test/resources/v2-compose-test.yml | 4 + 16 files changed, 410 insertions(+), 109 deletions(-) create mode 100644 core/src/main/java/org/testcontainers/containers/startupcheck/IsRunningStartupCheckStrategy.java create mode 100644 core/src/main/java/org/testcontainers/containers/startupcheck/MinimumDurationRunningStartupCheckStrategy.java create mode 100644 core/src/main/java/org/testcontainers/containers/startupcheck/OneShotStartupCheckStrategy.java create mode 100644 core/src/main/java/org/testcontainers/containers/startupcheck/StartupCheckStrategy.java create mode 100644 core/src/test/java/org/testcontainers/junit/BaseDockerComposeTest.java create mode 100644 core/src/test/java/org/testcontainers/junit/DockerComposeContainerScalingTest.java create mode 100644 core/src/test/java/org/testcontainers/junit/DockerComposeV2FormatTest.java create mode 100644 core/src/test/resources/redis.conf create mode 100644 core/src/test/resources/scaled-compose-test.yml create mode 100644 core/src/test/resources/v2-compose-test.yml diff --git a/core/pom.xml b/core/pom.xml index 205b674c11b..4a6694070ba 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -51,12 +51,12 @@ - - - - - - + + org.redisson + redisson + 1.3.0 + test + redis.clients jedis diff --git a/core/src/main/java/org/testcontainers/containers/Container.java b/core/src/main/java/org/testcontainers/containers/Container.java index 37eb1ac51b3..adbab4b7524 100644 --- a/core/src/main/java/org/testcontainers/containers/Container.java +++ b/core/src/main/java/org/testcontainers/containers/Container.java @@ -303,8 +303,6 @@ ExecResult execInContainer(Charset outputCharset, String... command) Map getLinkedContainers(); - Duration getMinimumRunningDuration(); - DockerClient getDockerClient(); Info getDockerDaemonInfo(); @@ -331,8 +329,6 @@ ExecResult execInContainer(Charset outputCharset, String... command) void setLinkedContainers(Map linkedContainers); - void setMinimumRunningDuration(Duration minimumRunningDuration); - void setDockerClient(DockerClient dockerClient); void setDockerDaemonInfo(Info dockerDaemonInfo); diff --git a/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java b/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java index 8f6c69310ce..3765cb2f22d 100644 --- a/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java +++ b/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java @@ -1,13 +1,20 @@ package org.testcontainers.containers; +import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.exception.DockerException; import com.github.dockerjava.api.model.Container; import com.google.common.util.concurrent.Uninterruptibles; +import org.junit.runner.Description; +import org.rnorth.ducttape.ratelimits.RateLimiter; +import org.rnorth.ducttape.ratelimits.RateLimiterBuilder; import org.rnorth.ducttape.unreliables.Unreliables; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.slf4j.profiler.Profiler; +import org.testcontainers.DockerClientFactory; import org.testcontainers.containers.output.OutputFrame; import org.testcontainers.containers.output.Slf4jLogConsumer; -import org.testcontainers.containers.traits.LinkableContainer; +import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy; import org.testcontainers.utility.Base58; import org.testcontainers.utility.ContainerReaper; @@ -25,62 +32,103 @@ /** * Container which launches Docker Compose, for the purposes of launching a defined set of containers. */ -public class DockerComposeContainer> extends GenericContainer implements LinkableContainer { +public class DockerComposeContainer> extends FailureDetectingExternalResource { /** * Random identifier which will become part of spawned containers names, so we can shut them down */ private final String identifier; private final Map ambassadorContainers = new HashMap<>(); + private final File composeFile; private Set spawnedContainerIds; + private Map scalingPreferences = new HashMap<>(); + private DockerClient dockerClient; + + private static final RateLimiter AMBASSADOR_CREATION_RATE_LIMITER = RateLimiterBuilder + .newBuilder() + .withRate(1, TimeUnit.SECONDS) + .withConstantThroughput() + .build(); public DockerComposeContainer(File composeFile) { this(composeFile, "up -d"); } - @SuppressWarnings("WeakerAccess") public DockerComposeContainer(File composeFile, String command) { - super("dduportal/docker-compose:1.6.0"); - - // Create a unique identifier and tell compose - identifier = Base58.randomString(6).toLowerCase(); - addEnv("COMPOSE_PROJECT_NAME", identifier); + this(composeFile, command, Base58.randomString(6).toLowerCase()); + } - // Map the docker compose file into the container - addEnv("COMPOSE_FILE", "/compose/" + composeFile.getAbsoluteFile().getName()); - addFileSystemBind(composeFile.getAbsoluteFile().getParentFile().getAbsolutePath(), "/compose", READ_ONLY); + @SuppressWarnings("WeakerAccess") + public DockerComposeContainer(File composeFile, String command, String identifier) { + this.composeFile = composeFile; - // Ensure that compose can access docker. Since the container is assumed to be running on the same machine - // as the docker daemon, just mapping the docker control socket is OK. - // As there seems to be a problem with mapping to the /var/run directory in certain environments (e.g. CircleCI) - // we map the socket file outside of /var/run, as just /docker.sock - addFileSystemBind("/var/run/docker.sock", "/docker.sock", READ_WRITE); - addEnv("DOCKER_HOST", "unix:///docker.sock"); + // Use a unique identifier so that containers created for this compose environment can be identified + this.identifier = identifier; - if (command != null) { - setCommand(command); - } + this.dockerClient = DockerClientFactory.instance().client(); } @Override - public void start() { - + protected void starting(Description description) { final Profiler profiler = new Profiler("Docker compose container rule"); profiler.setLogger(logger()); profiler.start("Docker compose container startup"); + applyScaling(); // scale before up, so that all scaled instances are available first for linking + createServices(); + registerContainersForShutdown(); + startAmbassadorContainers(profiler); + + } + + private GenericContainer createComposeInstance() { + return new GenericContainer("dduportal/docker-compose:1.6.0") + .withEnv("COMPOSE_PROJECT_NAME", identifier) + // Map the docker compose file into the container + .withEnv("COMPOSE_FILE", "/compose/" + composeFile.getAbsoluteFile().getName()) + .withFileSystemBind(composeFile.getAbsoluteFile().getParentFile().getAbsolutePath(), "/compose", READ_ONLY) + // Ensure that compose can access docker. Since the container is assumed to be running on the same machine + // as the docker daemon, just mapping the docker control socket is OK. + // As there seems to be a problem with mapping to the /var/run directory in certain environments (e.g. CircleCI) + // we map the socket file outside of /var/run, as just /docker.sock + .withFileSystemBind("/var/run/docker.sock", "/docker.sock", READ_WRITE) + .withEnv("DOCKER_HOST", "unix:///docker.sock") + .withStartupCheckStrategy(new OneShotStartupCheckStrategy()); + } + + private void createServices() { // Start the docker-compose container, which starts up the services - super.start(); - followOutput(new Slf4jLogConsumer(logger()), OutputFrame.OutputType.STDERR); + GenericContainer composeInstance = createComposeInstance().withCommand("up -d"); + runCompose(composeInstance); + } + + private void runCompose(GenericContainer composeInstance) { + composeInstance.start(); + composeInstance.followOutput(new Slf4jLogConsumer(logger()), OutputFrame.OutputType.STDERR); // wait for the compose container to stop, which should only happen after it has spawned all the service containers logger().info("Docker compose container is running - service creation will start now"); - while (isRunning()) { + while (composeInstance.isRunning()) { logger().trace("Compose container is still running"); Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); } logger().info("Docker compose has finished running"); + } + private void applyScaling() { + // Apply scaling + if (!scalingPreferences.isEmpty()) { + StringBuffer sb = new StringBuffer("scale"); + for (Map.Entry scale : scalingPreferences.entrySet()) { + sb.append(" ").append(scale.getKey()).append("=").append(scale.getValue()); + } + + GenericContainer composeInstance = createComposeInstance().withCommand(sb.toString()); + runCompose(composeInstance); + } + } + + private void registerContainersForShutdown() { // Ensure that all service containers that were launched by compose will be killed at shutdown try { List containers = dockerClient.listContainersCmd() @@ -101,7 +149,9 @@ public void start() { } catch (DockerException e) { logger().debug("Failed to stop a service container with exception", e); } + } + private void startAmbassadorContainers(Profiler profiler) { for (final Map.Entry address : ambassadorContainers.entrySet()) { try { @@ -110,11 +160,14 @@ public void start() { final AmbassadorContainer ambassadorContainer = address.getValue(); Unreliables.retryUntilSuccess(120, TimeUnit.SECONDS, () -> { - Profiler localProfiler = profiler.startNested("Ambassador container: " + ambassadorContainer.getContainerName()); - localProfiler.start("Start ambassador container"); + AMBASSADOR_CREATION_RATE_LIMITER.doWhenReady(() -> { + Profiler localProfiler = profiler.startNested("Ambassador container: " + ambassadorContainer.getContainerName()); + + localProfiler.start("Start ambassador container"); - ambassadorContainer.start(); + ambassadorContainer.start(); + }); return null; }); @@ -126,10 +179,14 @@ public void start() { } } + private Logger logger() { + return LoggerFactory.getLogger(DockerComposeContainer.class); + } + @Override - public void stop() { + protected void finished(Description description) { // this, the compose container, should not be running, but just in case something has gone wrong - super.stop(); + createComposeInstance().stop(); // shut down all the ambassador containers ambassadorContainers.forEach((String address, AmbassadorContainer container) -> container.stop()); @@ -139,12 +196,6 @@ public void stop() { spawnedContainerIds.clear(); } - @Override - @Deprecated - public SELF withExposedPorts(Integer... ports) { - throw new UnsupportedOperationException("Use withExposedService instead"); - } - public SELF withExposedService(String serviceName, int servicePort) { /** @@ -162,7 +213,7 @@ public SELF withExposedService(String serviceName, int servicePort) { // Ambassador containers will all be started together after docker compose has started ambassadorContainers.put(serviceName + ":" + servicePort, ambassadorContainer); - return self(); + return (SELF) this; } /** @@ -192,4 +243,9 @@ public String getServiceHost(String serviceName, Integer servicePort) { public Integer getServicePort(String serviceName, Integer servicePort) { return ambassadorContainers.get(serviceName + ":" + servicePort).getMappedPort(servicePort); } + + public DockerComposeContainer withScaledService(String serviceBaseName, int numInstances) { + scalingPreferences.put(serviceBaseName, numInstances); + return this; + } } diff --git a/core/src/main/java/org/testcontainers/containers/GenericContainer.java b/core/src/main/java/org/testcontainers/containers/GenericContainer.java index fc9b347a3a9..1cfe2ee0d35 100644 --- a/core/src/main/java/org/testcontainers/containers/GenericContainer.java +++ b/core/src/main/java/org/testcontainers/containers/GenericContainer.java @@ -24,11 +24,17 @@ import org.testcontainers.containers.output.OutputFrame; import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.containers.output.ToStringConsumer; +import org.testcontainers.containers.startupcheck.IsRunningStartupCheckStrategy; +import org.testcontainers.containers.startupcheck.MinimumDurationRunningStartupCheckStrategy; +import org.testcontainers.containers.startupcheck.StartupCheckStrategy; import org.testcontainers.containers.traits.LinkableContainer; import org.testcontainers.containers.wait.Wait; import org.testcontainers.containers.wait.WaitStrategy; import org.testcontainers.images.RemoteDockerImage; -import org.testcontainers.utility.*; +import org.testcontainers.utility.ContainerReaper; +import org.testcontainers.utility.DockerLoggerFactory; +import org.testcontainers.utility.DockerMachineClient; +import org.testcontainers.utility.PathOperations; import java.io.File; import java.io.IOException; @@ -36,7 +42,6 @@ import java.nio.charset.Charset; import java.nio.file.Path; import java.time.Duration; -import java.time.Instant; import java.util.*; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -90,8 +95,7 @@ public class GenericContainer> @NonNull private Map linkedContainers = new HashMap<>(); - @NonNull - private Duration minimumRunningDuration = null; + private StartupCheckStrategy startupCheckStrategy = new IsRunningStartupCheckStrategy(); /* * Unique instance of DockerClient for use by this container object. @@ -189,31 +193,10 @@ private void tryStart(Profiler profiler) { containerIsStarting(containerInfo); // Wait until the container is running (may not be fully started) - profiler.start("Wait until container state=running, or there's evidence it failed to start."); - final Boolean[] startedOK = {null}; - Unreliables.retryUntilTrue(CONTAINER_RUNNING_TIMEOUT_SEC, TimeUnit.SECONDS, () -> { - //noinspection CodeBlock2Expr - return DOCKER_CLIENT_RATE_LIMITER.getWhenReady(() -> { - // record "now" before fetching status; otherwise the time to fetch the status - // will contribute to how long the container has been running. - Instant now = Instant.now(); - InspectContainerResponse inspectionResponse = dockerClient.inspectContainerCmd(containerId).exec(); - - if (DockerStatus.isContainerRunning( - inspectionResponse.getState(), - minimumRunningDuration, - now)) { - startedOK[0] = true; - return true; - } else if (DockerStatus.isContainerStopped(inspectionResponse.getState())) { - startedOK[0] = false; - return true; - } - return false; - }); - }); + profiler.start("Wait until container has started properly, or there's evidence it failed to start."); + boolean startedOK = this.startupCheckStrategy.waitUntilStartupSuccessful(dockerClient, containerId); - if (!startedOK[0]) { + if (!startedOK) { logger().error("Container did not start correctly; container log output (if any) will be fetched and logged shortly"); FrameConsumerResultCallback resultCallback = new FrameConsumerResultCallback(); @@ -223,10 +206,10 @@ private void tryStart(Profiler profiler) { // Bail out, don't wait for the port to start listening. // (Exception thrown here will be caught below and wrapped) - throw new IllegalStateException("Container has already stopped."); + throw new IllegalStateException("Container did not start correctly."); } - profiler.start("Wait until container started"); + profiler.start("Wait until container started properly"); waitUntilContainerStarted(); logger().info("Container {} started", dockerImageName); @@ -550,7 +533,12 @@ public String getContainerIpAddress() { */ @Override public SELF withMinimumRunningDuration(Duration minimumRunningDuration) { - this.setMinimumRunningDuration(minimumRunningDuration); + this.startupCheckStrategy = new MinimumDurationRunningStartupCheckStrategy(minimumRunningDuration); + return self(); + } + + public SELF withStartupCheckStrategy(StartupCheckStrategy strategy) { + this.startupCheckStrategy = strategy; return self(); } diff --git a/core/src/main/java/org/testcontainers/containers/startupcheck/IsRunningStartupCheckStrategy.java b/core/src/main/java/org/testcontainers/containers/startupcheck/IsRunningStartupCheckStrategy.java new file mode 100644 index 00000000000..6a296e0329c --- /dev/null +++ b/core/src/main/java/org/testcontainers/containers/startupcheck/IsRunningStartupCheckStrategy.java @@ -0,0 +1,24 @@ +package org.testcontainers.containers.startupcheck; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.InspectContainerResponse; +import org.testcontainers.utility.DockerStatus; + +/** + * Simplest possible implementation of {@link StartupCheckStrategy} - just check that the container + * has reached the running state and has not exited. + */ +public class IsRunningStartupCheckStrategy extends StartupCheckStrategy { + + @Override + public StartupStatus checkStartupState(DockerClient dockerClient, String containerId) { + InspectContainerResponse.ContainerState state = getCurrentState(dockerClient, containerId); + if (state.getRunning()) { + return StartupStatus.SUCCESSFUL; + } else if (!DockerStatus.isContainerExitCodeSuccess(state)) { + return StartupStatus.FAILED; + } else { + return StartupStatus.NOT_YET_KNOWN; + } + } +} diff --git a/core/src/main/java/org/testcontainers/containers/startupcheck/MinimumDurationRunningStartupCheckStrategy.java b/core/src/main/java/org/testcontainers/containers/startupcheck/MinimumDurationRunningStartupCheckStrategy.java new file mode 100644 index 00000000000..3ce50446b77 --- /dev/null +++ b/core/src/main/java/org/testcontainers/containers/startupcheck/MinimumDurationRunningStartupCheckStrategy.java @@ -0,0 +1,39 @@ +package org.testcontainers.containers.startupcheck; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.InspectContainerResponse; +import org.jetbrains.annotations.NotNull; +import org.testcontainers.utility.DockerStatus; + +import java.time.Duration; +import java.time.Instant; + +/** + * Implementation of {@link StartupCheckStrategy} that checks the container is running and has been running for + * a defined minimum period of time. + */ +public class MinimumDurationRunningStartupCheckStrategy extends StartupCheckStrategy { + + @NotNull + private final Duration minimumRunningDuration; + + public MinimumDurationRunningStartupCheckStrategy(@NotNull Duration minimumRunningDuration) { + this.minimumRunningDuration = minimumRunningDuration; + } + + @Override + public StartupStatus checkStartupState(DockerClient dockerClient, String containerId) { + // record "now" before fetching status; otherwise the time to fetch the status + // will contribute to how long the container has been running. + Instant now = Instant.now(); + InspectContainerResponse.ContainerState state = getCurrentState(dockerClient, containerId); + + if (DockerStatus.isContainerRunning(state, minimumRunningDuration, now)) { + return StartupStatus.SUCCESSFUL; + } else if (DockerStatus.isContainerStopped(state)) { + return StartupStatus.FAILED; + } + return StartupStatus.NOT_YET_KNOWN; + } + +} diff --git a/core/src/main/java/org/testcontainers/containers/startupcheck/OneShotStartupCheckStrategy.java b/core/src/main/java/org/testcontainers/containers/startupcheck/OneShotStartupCheckStrategy.java new file mode 100644 index 00000000000..427baf6c0a3 --- /dev/null +++ b/core/src/main/java/org/testcontainers/containers/startupcheck/OneShotStartupCheckStrategy.java @@ -0,0 +1,27 @@ +package org.testcontainers.containers.startupcheck; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.InspectContainerResponse; +import org.testcontainers.utility.DockerStatus; + +/** + * Implementation of {@link StartupCheckStrategy} intended for use with containers that only run briefly and + * exit of their own accord. As such, success is deemed to be when the container has stopped with exit code 0. + */ +public class OneShotStartupCheckStrategy extends StartupCheckStrategy { + + @Override + public StartupStatus checkStartupState(DockerClient dockerClient, String containerId) { + InspectContainerResponse.ContainerState state = getCurrentState(dockerClient, containerId); + + if (!DockerStatus.isContainerStopped(state)) { + return StartupStatus.NOT_YET_KNOWN; + } + + if (DockerStatus.isContainerStopped(state) && DockerStatus.isContainerExitCodeSuccess(state)) { + return StartupStatus.SUCCESSFUL; + } else { + return StartupStatus.FAILED; + } + } +} diff --git a/core/src/main/java/org/testcontainers/containers/startupcheck/StartupCheckStrategy.java b/core/src/main/java/org/testcontainers/containers/startupcheck/StartupCheckStrategy.java new file mode 100644 index 00000000000..f48f5958bd3 --- /dev/null +++ b/core/src/main/java/org/testcontainers/containers/startupcheck/StartupCheckStrategy.java @@ -0,0 +1,51 @@ +package org.testcontainers.containers.startupcheck; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.InspectContainerResponse; +import org.rnorth.ducttape.ratelimits.RateLimiter; +import org.rnorth.ducttape.ratelimits.RateLimiterBuilder; +import org.rnorth.ducttape.unreliables.Unreliables; + +import java.util.concurrent.TimeUnit; + +import static org.testcontainers.containers.GenericContainer.CONTAINER_RUNNING_TIMEOUT_SEC; + +/** + * Approach to determine whether a container has 'started up' correctly. + */ +public abstract class StartupCheckStrategy { + + private static final RateLimiter DOCKER_CLIENT_RATE_LIMITER = RateLimiterBuilder + .newBuilder() + .withRate(1, TimeUnit.SECONDS) + .withConstantThroughput() + .build(); + + public boolean waitUntilStartupSuccessful(DockerClient dockerClient, String containerId) { + final Boolean[] startedOK = {null}; + Unreliables.retryUntilTrue(CONTAINER_RUNNING_TIMEOUT_SEC, TimeUnit.SECONDS, () -> { + //noinspection CodeBlock2Expr + return DOCKER_CLIENT_RATE_LIMITER.getWhenReady(() -> { + StartupStatus state = checkStartupState(dockerClient, containerId); + switch (state) { + case SUCCESSFUL: startedOK[0] = true; + return true; + case FAILED: startedOK[0] = false; + return true; + default: return false; + } + }); + }); + return startedOK[0]; + } + + public abstract StartupStatus checkStartupState(DockerClient dockerClient, String containerId); + + protected InspectContainerResponse.ContainerState getCurrentState(DockerClient dockerClient, String containerId) { + return dockerClient.inspectContainerCmd(containerId).exec().getState(); + } + + enum StartupStatus { + NOT_YET_KNOWN, SUCCESSFUL, FAILED + } +} diff --git a/core/src/main/java/org/testcontainers/utility/DockerStatus.java b/core/src/main/java/org/testcontainers/utility/DockerStatus.java index cd4c0a2514a..7630d779c1d 100644 --- a/core/src/main/java/org/testcontainers/utility/DockerStatus.java +++ b/core/src/main/java/org/testcontainers/utility/DockerStatus.java @@ -79,4 +79,9 @@ public static boolean isDockerTimestampEmpty(String dockerTimestamp) { || DateTimeFormatter.ISO_INSTANT.parse(dockerTimestamp, Instant::from).getEpochSecond() < 0L; } + public static boolean isContainerExitCodeSuccess(InspectContainerResponse.ContainerState state) { + int exitCode = state.getExitCode(); + // 0 is the only exit code we can consider as success + return exitCode == 0; + } } diff --git a/core/src/test/java/org/testcontainers/junit/BaseDockerComposeTest.java b/core/src/test/java/org/testcontainers/junit/BaseDockerComposeTest.java new file mode 100644 index 00000000000..a67ba8ebcfb --- /dev/null +++ b/core/src/test/java/org/testcontainers/junit/BaseDockerComposeTest.java @@ -0,0 +1,50 @@ +package org.testcontainers.junit; + +import org.junit.Test; +import org.redisson.Config; +import org.redisson.Redisson; +import org.redisson.core.RAtomicLong; +import org.testcontainers.containers.DockerComposeContainer; + +import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; + +/** + * Created by rnorth on 21/05/2016. + */ +public abstract class BaseDockerComposeTest { + + protected static final int REDIS_PORT = 6379; + + protected abstract DockerComposeContainer getEnvironment(); + + @Test + public void simpleTest() { + Config config = new Config(); + config.useSingleServer().setAddress(getEnvironment().getServiceHost("redis_1", REDIS_PORT) + ":" + getEnvironment().getServicePort("redis_1", REDIS_PORT)); + Redisson redisson = Redisson.create(config); + + RAtomicLong test = redisson.getAtomicLong("test"); + test.incrementAndGet(); + test.incrementAndGet(); + test.incrementAndGet(); + + assertEquals("A redis instance defined in compose can be used in isolation", 3, (int) test.get()); + } + + @Test + public void secondTest() { + // used in manual checking for cleanup in between tests + Config config = new Config(); + config.useSingleServer().setAddress(getEnvironment().getServiceHost("redis_1", REDIS_PORT) + ":" + getEnvironment().getServicePort("redis_1", REDIS_PORT)); + Redisson redisson = Redisson.create(config); + + RAtomicLong test = redisson.getAtomicLong("test"); + test.incrementAndGet(); + test.incrementAndGet(); + test.incrementAndGet(); + + assertEquals("Tests use fresh container instances", 3, (int) test.get()); + // if these end up using the same container one of the test methods will fail. + // However, @Rule creates a separate DockerComposeContainer instance per test, so this just shouldn't happen + } +} diff --git a/core/src/test/java/org/testcontainers/junit/DockerComposeContainerScalingTest.java b/core/src/test/java/org/testcontainers/junit/DockerComposeContainerScalingTest.java new file mode 100644 index 00000000000..fb38085e5c6 --- /dev/null +++ b/core/src/test/java/org/testcontainers/junit/DockerComposeContainerScalingTest.java @@ -0,0 +1,65 @@ +package org.testcontainers.junit; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.redisson.Config; +import org.redisson.Redisson; +import org.redisson.core.RAtomicLong; +import org.testcontainers.containers.DockerComposeContainer; + +import java.io.File; + +import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; + +/** + * Created by rnorth on 08/08/2015. + */ +public class DockerComposeContainerScalingTest { + + private static final int REDIS_PORT = 6379; + + private Redisson[] clients = new Redisson[3]; + + @Rule + public DockerComposeContainer environment = new DockerComposeContainer(new File("src/test/resources/scaled-compose-test.yml")) + .withScaledService("redis", 3) + .withExposedService("redis_1", REDIS_PORT) + .withExposedService("redis_2", REDIS_PORT) + .withExposedService("redis_3", REDIS_PORT); + + @Before + public void setupClients() { + for (int i = 0; i < 3; i++) { + + String name = String.format("redis_%d", i + 1); + + Config config = new Config(); + config.useSingleServer().setAddress(environment.getServiceHost(name, REDIS_PORT) + ":" + environment.getServicePort(name, REDIS_PORT)); + + Redisson redisson = Redisson.create(config); + + clients[i] = redisson; + } +// Config config = new Config(); +// for (int i = 0; i < 3; i++) { +// String name = String.format("redis_%d", i + 1); +// config.useClusterServers().addNodeAddress(environment.getServiceHost(name, REDIS_PORT) + ":" + environment.getServicePort(name, REDIS_PORT)); +// } +// Redisson redisson = Redisson.create(config); +// +// System.out.println(); + } + + @Test + public void simpleTest() { + + for (int i = 0; i < 3; i++) { + RAtomicLong test = clients[i].getAtomicLong("test"); + + test.incrementAndGet(); + + assertEquals("Each redis instance is separate", 1, (int) test.get()); + } + } +} diff --git a/core/src/test/java/org/testcontainers/junit/DockerComposeContainerTest.java b/core/src/test/java/org/testcontainers/junit/DockerComposeContainerTest.java index a5e55fedcae..8d2addf7b5e 100644 --- a/core/src/test/java/org/testcontainers/junit/DockerComposeContainerTest.java +++ b/core/src/test/java/org/testcontainers/junit/DockerComposeContainerTest.java @@ -1,20 +1,14 @@ package org.testcontainers.junit; import org.junit.Rule; -import org.junit.Test; import org.testcontainers.containers.DockerComposeContainer; -import redis.clients.jedis.Jedis; import java.io.File; -import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; - /** * Created by rnorth on 08/08/2015. */ -public class DockerComposeContainerTest { - - private static final int REDIS_PORT = 6379; +public class DockerComposeContainerTest extends BaseDockerComposeTest { @Rule public DockerComposeContainer environment = new DockerComposeContainer(new File("src/test/resources/compose-test.yml")) @@ -22,29 +16,8 @@ public class DockerComposeContainerTest { .withExposedService("db_1", 3306) ; - @Test - public void simpleTest() { - - Jedis jedis = new Jedis(environment.getServiceHost("redis_1", REDIS_PORT), environment.getServicePort("redis_1", REDIS_PORT)); - - jedis.incr("test"); - jedis.incr("test"); - jedis.incr("test"); - - assertEquals("A redis instance defined in compose can be used in isolation", 3, Integer.valueOf(jedis.get("test"))); - } - - @Test - public void secondTest() { - // used in manual checking for cleanup in between tests - Jedis jedis = new Jedis(environment.getServiceHost("redis_1", REDIS_PORT), environment.getServicePort("redis_1", REDIS_PORT)); - - jedis.incr("test"); - jedis.incr("test"); - jedis.incr("test"); - - assertEquals("Tests use fresh container instances", 3, Integer.valueOf(jedis.get("test"))); - // if these end up using the same container one of the test methods will fail. - // However, @Rule creates a separate DockerComposeContainer instance per test, so this just shouldn't happen + @Override + protected DockerComposeContainer getEnvironment() { + return environment; } } diff --git a/core/src/test/java/org/testcontainers/junit/DockerComposeV2FormatTest.java b/core/src/test/java/org/testcontainers/junit/DockerComposeV2FormatTest.java new file mode 100644 index 00000000000..bed19bd0d09 --- /dev/null +++ b/core/src/test/java/org/testcontainers/junit/DockerComposeV2FormatTest.java @@ -0,0 +1,21 @@ +package org.testcontainers.junit; + +import org.junit.Rule; +import org.testcontainers.containers.DockerComposeContainer; + +import java.io.File; + +/** + * Created by rnorth on 21/05/2016. + */ +public class DockerComposeV2FormatTest extends BaseDockerComposeTest { + + @Rule + public DockerComposeContainer environment = new DockerComposeContainer(new File("src/test/resources/v2-compose-test.yml")) + .withExposedService("redis_1", REDIS_PORT); + + @Override + protected DockerComposeContainer getEnvironment() { + return environment; + } +} diff --git a/core/src/test/resources/redis.conf b/core/src/test/resources/redis.conf new file mode 100644 index 00000000000..e69de29bb2d diff --git a/core/src/test/resources/scaled-compose-test.yml b/core/src/test/resources/scaled-compose-test.yml new file mode 100644 index 00000000000..ee16210d31d --- /dev/null +++ b/core/src/test/resources/scaled-compose-test.yml @@ -0,0 +1,2 @@ +redis: + image: redis \ No newline at end of file diff --git a/core/src/test/resources/v2-compose-test.yml b/core/src/test/resources/v2-compose-test.yml new file mode 100644 index 00000000000..b264c61ebb6 --- /dev/null +++ b/core/src/test/resources/v2-compose-test.yml @@ -0,0 +1,4 @@ +version: '2' +services: + redis: + image: redis \ No newline at end of file