diff --git a/CHANGELOG.md b/CHANGELOG.md
index 006659a92d9..830bd5c4713 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
## UNRELEASED
### Changed
- Added `TC_DAEMON` JDBC URL flag to prevent `ContainerDatabaseDriver` from shutting down containers at the time all connections are closed. (#359, #360)
+- Added pre-flight checks (can be disabled with `checks.disable` configuration property) (#363)
- Removed unused Jersey dependencies (#361)
## [1.3.0] - 2017-06-05
diff --git a/core/pom.xml b/core/pom.xml
index e7e8935720e..cbf235a1960 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -64,6 +64,11 @@
slf4j-ext
1.7.25
+
+ org.rnorth.visible-assertions
+ visible-assertions
+ 1.0.5
+
diff --git a/core/src/main/java/org/testcontainers/DockerClientFactory.java b/core/src/main/java/org/testcontainers/DockerClientFactory.java
index 693571802ad..816c058dce6 100644
--- a/core/src/main/java/org/testcontainers/DockerClientFactory.java
+++ b/core/src/main/java/org/testcontainers/DockerClientFactory.java
@@ -2,18 +2,28 @@
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerCmd;
+import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.exception.InternalServerErrorException;
import com.github.dockerjava.api.exception.NotFoundException;
-import com.github.dockerjava.api.model.Image;
-import com.github.dockerjava.api.model.Info;
-import com.github.dockerjava.api.model.Version;
+import com.github.dockerjava.api.model.*;
+import com.github.dockerjava.core.command.ExecStartResultCallback;
import com.github.dockerjava.core.command.PullImageResultCallback;
-
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.rnorth.visibleassertions.VisibleAssertions;
import org.testcontainers.dockerclient.*;
+import org.testcontainers.utility.ComparableVersion;
+import org.testcontainers.utility.MountableFile;
import org.testcontainers.utility.TestcontainersConfiguration;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Socket;
+import java.nio.charset.Charset;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
@@ -82,7 +92,8 @@ public DockerClient client() {
strategy = DockerClientProviderStrategy.getFirstValidStrategy(CONFIGURATION_STRATEGIES);
- log.info("Docker host IP address is {}", strategy.getDockerHostIpAddress());
+ String hostIpAddress = strategy.getDockerHostIpAddress();
+ log.info("Docker host IP address is {}", hostIpAddress);
DockerClient client = strategy.getClient();
if (!preconditionsChecked) {
@@ -96,15 +107,93 @@ public DockerClient client() {
" Operating System: " + dockerInfo.getOperatingSystem() + "\n" +
" Total Memory: " + dockerInfo.getMemTotal() / (1024 * 1024) + " MB");
- checkVersion(version.getVersion());
- checkDiskSpaceAndHandleExceptions(client);
+ if (!TestcontainersConfiguration.getInstance().isDisableChecks()) {
+ VisibleAssertions.info("Checking the system...");
+
+ checkDockerVersion(version.getVersion());
+
+ MountableFile mountableFile = MountableFile.forClasspathResource(this.getClass().getName().replace(".", "/") + ".class");
+
+ runInsideDocker(
+ client,
+ cmd -> cmd
+ .withCmd("/bin/sh", "-c", "while true; do printf 'hello' | nc -l -p 80; done")
+ .withBinds(new Bind(mountableFile.getResolvedPath(), new Volume("/dummy"), AccessMode.ro))
+ .withExposedPorts(new ExposedPort(80))
+ .withPublishAllPorts(true),
+ (dockerClient, id) -> {
+
+ checkDiskSpace(dockerClient, id);
+ checkMountableFile(dockerClient, id);
+ checkExposedPort(hostIpAddress, dockerClient, id);
+
+ return null;
+ });
+ }
preconditionsChecked = true;
}
return client;
}
- /**
+ private void checkDockerVersion(String dockerVersion) {
+ VisibleAssertions.assertThat("Docker version", dockerVersion, new BaseMatcher() {
+ @Override
+ public boolean matches(Object o) {
+ return new ComparableVersion(o.toString()).compareTo(new ComparableVersion("1.6.0")) >= 0;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("is newer than 1.6.0");
+ }
+ });
+ }
+
+ private void checkDiskSpace(DockerClient dockerClient, String id) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+ try {
+ dockerClient
+ .execStartCmd(dockerClient.execCreateCmd(id).withAttachStdout(true).withCmd("df", "-P").exec().getId())
+ .exec(new ExecStartResultCallback(outputStream, null))
+ .awaitCompletion();
+ } catch (Exception e) {
+ log.debug("Can't exec disk checking command", e);
+ }
+
+ DiskSpaceUsage df = parseAvailableDiskSpace(outputStream.toString());
+
+ VisibleAssertions.assertTrue(
+ "Docker environment has more than 2GB free",
+ df.availableMB.map(it -> it >= 2048).orElse(true)
+ );
+ }
+
+ private void checkMountableFile(DockerClient dockerClient, String id) {
+ try (InputStream stream = dockerClient.copyArchiveFromContainerCmd(id, "/dummy").exec()) {
+ stream.read();
+ VisibleAssertions.pass("File should be mountable");
+ } catch (Exception e) {
+ VisibleAssertions.fail("File should be mountable but fails with " + e.getMessage());
+ }
+ }
+
+ private void checkExposedPort(String hostIpAddress, DockerClient dockerClient, String id) {
+ InspectContainerResponse inspectedContainer = dockerClient.inspectContainerCmd(id).exec();
+
+ String portSpec = inspectedContainer.getNetworkSettings().getPorts().getBindings().values().iterator().next()[0].getHostPortSpec();
+
+ String response;
+ try (Socket socket = new Socket(hostIpAddress, Integer.parseInt(portSpec))) {
+ response = IOUtils.toString(socket.getInputStream(), Charset.defaultCharset());
+ } catch (IOException e) {
+ response = e.getMessage();
+ }
+ VisibleAssertions.assertEquals("Exposed port is accessible", "hello", response);
+ }
+
+ /**
* Check whether the image is available locally and pull it otherwise
*/
private void checkAndPullImage(DockerClient client, String image) {
@@ -121,47 +210,6 @@ public String dockerHostIpAddress() {
return strategy.getDockerHostIpAddress();
}
- private void checkVersion(String version) {
- String[] splitVersion = version.split("\\.");
- if (Integer.valueOf(splitVersion[0]) <= 1 && Integer.valueOf(splitVersion[1]) < 6) {
- throw new IllegalStateException("Docker version 1.6.0+ is required, but version " + version + " was found");
- }
- }
-
- private void checkDiskSpaceAndHandleExceptions(DockerClient client) {
- try {
- checkDiskSpace(client);
- } catch (NotEnoughDiskSpaceException e) {
- throw e;
- } catch (Exception e) {
- log.warn("Encountered and ignored error while checking disk space", e);
- }
- }
-
- /**
- * Check whether this docker installation is likely to have disk space problems
- * @param client an active Docker client
- */
- private void checkDiskSpace(DockerClient client) {
- DiskSpaceUsage df = runInsideDocker(client, cmd -> cmd.withCmd("df", "-P"), (dockerClient, id) -> {
- String logResults = dockerClient.logContainerCmd(id)
- .withStdOut(true)
- .exec(new LogToStringContainerCallback())
- .toString();
-
- return parseAvailableDiskSpace(logResults);
- });
-
- log.info("Disk utilization in Docker environment is {} ({} )",
- df.usedPercent.map(x -> x + "%").orElse("unknown"),
- df.availableMB.map(x -> x + " MB available").orElse("unknown available"));
-
- if (df.availableMB.map(it -> it < 2048).orElse(false)) {
- log.error("Docker environment has less than 2GB free - execution is unlikely to succeed so will be aborted.");
- throw new NotEnoughDiskSpaceException("Not enough disk space in Docker environment");
- }
- }
-
public T runInsideDocker(Consumer createContainerCmdConsumer, BiFunction block) {
if (strategy == null) {
client();
@@ -176,9 +224,8 @@ private T runInsideDocker(DockerClient client, Consumer
createContainerCmdConsumer.accept(createContainerCmd);
String id = createContainerCmd.exec().getId();
- client.startContainerCmd(id).exec();
-
try {
+ client.startContainerCmd(id).exec();
return block.apply(client, id);
} finally {
try {
@@ -199,7 +246,7 @@ private DiskSpaceUsage parseAvailableDiskSpace(String dfOutput) {
String[] lines = dfOutput.split("\n");
for (String line : lines) {
String[] fields = line.split("\\s+");
- if (fields[5].equals("/")) {
+ if (fields.length > 5 && fields[5].equals("/")) {
int availableKB = Integer.valueOf(fields[3]);
df.availableMB = Optional.of(availableKB / 1024);
df.usedPercent = Optional.of(Integer.valueOf(fields[4].replace("%", "")));
diff --git a/core/src/main/java/org/testcontainers/containers/GenericContainer.java b/core/src/main/java/org/testcontainers/containers/GenericContainer.java
index 49a020b0889..859def3d3ba 100644
--- a/core/src/main/java/org/testcontainers/containers/GenericContainer.java
+++ b/core/src/main/java/org/testcontainers/containers/GenericContainer.java
@@ -147,7 +147,7 @@ public class GenericContainer>
public GenericContainer() {
- this("alpine:3.2");
+ this(TestcontainersConfiguration.getInstance().getTinyImage());
}
public GenericContainer(@NonNull final String dockerImageName) {
diff --git a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java
index c969b08d4f6..1c90a1fabae 100644
--- a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java
+++ b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java
@@ -24,7 +24,8 @@ public class TestcontainersConfiguration {
private String ambassadorContainerImage = "richnorth/ambassador:latest";
private String vncRecordedContainerImage = "richnorth/vnc-recorder:latest";
- private String tinyImage = "alpine:3.2";
+ private String tinyImage = "alpine:3.5";
+ private boolean disableChecks = false;
private static TestcontainersConfiguration loadConfiguration() {
final TestcontainersConfiguration config = new TestcontainersConfiguration();
@@ -44,6 +45,7 @@ private static TestcontainersConfiguration loadConfiguration() {
config.ambassadorContainerImage = properties.getProperty("ambassador.container.image", config.ambassadorContainerImage);
config.vncRecordedContainerImage = properties.getProperty("vncrecorder.container.image", config.vncRecordedContainerImage);
config.tinyImage = properties.getProperty("tinyimage.container.image", config.tinyImage);
+ config.disableChecks = Boolean.parseBoolean(properties.getProperty("checks.disable", config.disableChecks + ""));
log.debug("Testcontainers configuration overrides loaded from {}: {}", configOverrides, config);
diff --git a/pom.xml b/pom.xml
index 8a3719927b4..8a395548335 100644
--- a/pom.xml
+++ b/pom.xml
@@ -95,12 +95,6 @@
1.2.3
test
-
- org.rnorth.visible-assertions
- visible-assertions
- 1.0.5
- test
-