diff --git a/CHANGELOG.md b/CHANGELOG.md
index a95fa857274..27aa66bb4f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
### Fixed
### Changed
+- Added support for Docker networks (#372)
## [1.3.1] - 2017-06-22
### Fixed
diff --git a/core/src/main/java/org/testcontainers/containers/Container.java b/core/src/main/java/org/testcontainers/containers/Container.java
index 35abe058168..2f2d9fa0917 100644
--- a/core/src/main/java/org/testcontainers/containers/Container.java
+++ b/core/src/main/java/org/testcontainers/containers/Container.java
@@ -209,6 +209,24 @@ default void addFileSystemBind(final String hostPath, final String containerPath
*/
SELF withNetworkMode(String networkMode);
+ /**
+ * Set the network for this container, similar to the --network <name>
+ * option on the docker CLI.
+ *
+ * @param network the instance of {@link Network}
+ * @return this
+ */
+ SELF withNetwork(Network network);
+
+ /**
+ * Set the network aliases for this container, similar to the --network-alias <my-service>
+ * option on the docker CLI.
+ *
+ * @param aliases the list of aliases
+ * @return this
+ */
+ SELF withNetworkAliases(String... aliases);
+
/**
* Map a resource (file or directory) on the classpath to a path inside the container.
* This will only work if you are running your tests outside a Docker container.
diff --git a/core/src/main/java/org/testcontainers/containers/GenericContainer.java b/core/src/main/java/org/testcontainers/containers/GenericContainer.java
index 09494c78610..6b055860256 100644
--- a/core/src/main/java/org/testcontainers/containers/GenericContainer.java
+++ b/core/src/main/java/org/testcontainers/containers/GenericContainer.java
@@ -75,6 +75,12 @@ public class GenericContainer>
@NonNull
private String networkMode;
+ @NonNull
+ private Network network;
+
+ @NonNull
+ private List networkAliases = new ArrayList<>();
+
@NonNull
private Future image;
@@ -412,7 +418,10 @@ private void applyConfiguration(CreateContainerCmd createCommand) {
.toArray(String[]::new);
createCommand.withExtraHosts(extraHostsArray);
- if (networkMode != null) {
+ if (network != null) {
+ createCommand.withNetworkMode(network.getId());
+ createCommand.withAliases(this.networkAliases);
+ } else if (networkMode != null) {
createCommand.withNetworkMode(networkMode);
}
@@ -615,6 +624,18 @@ public SELF withNetworkMode(String networkMode) {
return self();
}
+ @Override
+ public SELF withNetwork(Network network) {
+ this.network = network;
+ return self();
+ }
+
+ @Override
+ public SELF withNetworkAliases(String... aliases) {
+ Collections.addAll(this.networkAliases, aliases);
+ return self();
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/core/src/main/java/org/testcontainers/containers/Network.java b/core/src/main/java/org/testcontainers/containers/Network.java
new file mode 100644
index 00000000000..4d1da15cb69
--- /dev/null
+++ b/core/src/main/java/org/testcontainers/containers/Network.java
@@ -0,0 +1,78 @@
+package org.testcontainers.containers;
+
+import com.github.dockerjava.api.command.CreateNetworkCmd;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Singular;
+import org.junit.rules.ExternalResource;
+import org.junit.rules.TestRule;
+import org.testcontainers.DockerClientFactory;
+import org.testcontainers.utility.ResourceReaper;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Consumer;
+
+public interface Network extends AutoCloseable, TestRule {
+
+ String getId();
+
+ @Override
+ default void close() {
+ ResourceReaper.instance().removeNetworks(getId());
+ }
+
+ static Network newNetwork() {
+ return builder().build();
+ }
+
+ static NetworkImpl.NetworkImplBuilder builder() {
+ return NetworkImpl.builder();
+ }
+
+ @Builder
+ @Getter
+ class NetworkImpl extends ExternalResource implements Network {
+
+ private final String name = UUID.randomUUID().toString();
+
+ private Boolean enableIpv6;
+
+ private String driver;
+
+ @Singular
+ private Set> createNetworkCmdModifiers = new LinkedHashSet<>();
+
+ @Getter(lazy = true)
+ private final String id = create();
+
+ private String create() {
+ ResourceReaper.instance().registerNetworkForCleanup(name);
+
+ CreateNetworkCmd createNetworkCmd = DockerClientFactory.instance().client().createNetworkCmd();
+
+ createNetworkCmd.withName(name);
+ createNetworkCmd.withCheckDuplicate(true);
+
+ if (enableIpv6 != null) {
+ createNetworkCmd.withEnableIpv6(enableIpv6);
+ }
+
+ if (driver != null) {
+ createNetworkCmd.withDriver(driver);
+ }
+
+ for (Consumer consumer : createNetworkCmdModifiers) {
+ consumer.accept(createNetworkCmd);
+ }
+
+ return createNetworkCmd.exec().getId();
+ }
+
+ @Override
+ protected void after() {
+ close();
+ }
+ }
+}
diff --git a/core/src/main/java/org/testcontainers/utility/ResourceReaper.java b/core/src/main/java/org/testcontainers/utility/ResourceReaper.java
index e9f10b76b2c..a9ed8f1ba45 100644
--- a/core/src/main/java/org/testcontainers/utility/ResourceReaper.java
+++ b/core/src/main/java/org/testcontainers/utility/ResourceReaper.java
@@ -5,15 +5,12 @@
import com.github.dockerjava.api.exception.DockerException;
import com.github.dockerjava.api.exception.InternalServerErrorException;
import com.github.dockerjava.api.exception.NotFoundException;
-import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.Network;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.DockerClientFactory;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -25,7 +22,7 @@ public final class ResourceReaper {
private static ResourceReaper instance;
private final DockerClient dockerClient;
private Map registeredContainers = new ConcurrentHashMap<>();
- private List registeredNetworks = new ArrayList<>();
+ private Set registeredNetworks = Collections.newSetFromMap(new ConcurrentHashMap<>());
private ResourceReaper() {
dockerClient = DockerClientFactory.instance().client();
@@ -145,22 +142,34 @@ public void removeNetworks(String identifier) {
}
private void removeNetwork(String networkName) {
- List networks;
try {
- networks = dockerClient.listNetworksCmd().withNameFilter(networkName).exec();
- } catch (DockerException e) {
- LOGGER.trace("Error encountered when looking up network for removal (name: {}) - it may not have been removed", networkName);
- return;
- }
+ try {
+ // First try to remove by name
+ dockerClient.removeNetworkCmd(networkName).exec();
+ } catch (Exception e) {
+ LOGGER.trace("Error encountered removing network by name ({}) - it may not have been removed", networkName);
+ }
- for (Network network : networks) {
+ List networks;
try {
- dockerClient.removeNetworkCmd(network.getId()).exec();
- registeredNetworks.remove(network.getId());
- LOGGER.debug("Removed network: {}", networkName);
- } catch (DockerException e) {
- LOGGER.trace("Error encountered removing network (name: {}) - it may not have been removed", network.getName());
+ // Then try to list all networks with the same name
+ networks = dockerClient.listNetworksCmd().withNameFilter(networkName).exec();
+ } catch (Exception e) {
+ LOGGER.trace("Error encountered when looking up network for removal (name: {}) - it may not have been removed", networkName);
+ return;
+ }
+
+ for (Network network : networks) {
+ try {
+ dockerClient.removeNetworkCmd(network.getId()).exec();
+ registeredNetworks.remove(network.getId());
+ LOGGER.debug("Removed network: {}", networkName);
+ } catch (Exception e) {
+ LOGGER.trace("Error encountered removing network (name: {}) - it may not have been removed", network.getName());
+ }
}
+ } finally {
+ registeredNetworks.remove(networkName);
}
}
}
diff --git a/core/src/test/java/org/testcontainers/containers/NetworkTest.java b/core/src/test/java/org/testcontainers/containers/NetworkTest.java
new file mode 100644
index 00000000000..70b55c09928
--- /dev/null
+++ b/core/src/test/java/org/testcontainers/containers/NetworkTest.java
@@ -0,0 +1,94 @@
+package org.testcontainers.containers;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+import org.testcontainers.DockerClientFactory;
+
+import static org.rnorth.visibleassertions.VisibleAssertions.*;
+import static org.testcontainers.containers.Network.newNetwork;
+
+@RunWith(Enclosed.class)
+public class NetworkTest {
+
+ public static class WithRules {
+
+ @Rule
+ public Network network = newNetwork();
+
+ @Rule
+ public GenericContainer foo = new GenericContainer()
+ .withNetwork(network)
+ .withNetworkAliases("foo")
+ .withCommand("/bin/sh", "-c", "while true ; do printf 'HTTP/1.1 200 OK\\n\\nyay' | nc -l -p 8080; done");
+
+ @Rule
+ public GenericContainer bar = new GenericContainer()
+ .withNetwork(network)
+ .withCommand("top");
+
+ @Test
+ public void testNetworkSupport() throws Exception {
+ String response = bar.execInContainer("wget", "-O", "-", "http://foo:8080").getStdout();
+ assertEquals("received response", "yay", response);
+ }
+ }
+
+ public static class WithoutRules {
+
+ @Test
+ public void testNetworkSupport() throws Exception {
+ try (
+ Network network = newNetwork();
+
+ GenericContainer foo = new GenericContainer()
+ .withNetwork(network)
+ .withNetworkAliases("foo")
+ .withCommand("/bin/sh", "-c", "while true ; do printf 'HTTP/1.1 200 OK\\n\\nyay' | nc -l -p 8080; done");
+
+ GenericContainer bar = new GenericContainer()
+ .withNetwork(network)
+ .withCommand("top")
+ ) {
+ foo.start();
+ bar.start();
+
+ String response = bar.execInContainer("wget", "-O", "-", "http://foo:8080").getStdout();
+ assertEquals("received response", "yay", response);
+ }
+ }
+
+ @Test
+ public void testBuilder() throws Exception {
+ try (
+ Network network = Network.builder()
+ .driver("macvlan")
+ .build();
+ ) {
+ String id = network.getId();
+ assertEquals(
+ "Flag is set",
+ "macvlan",
+ DockerClientFactory.instance().client().inspectNetworkCmd().withNetworkId(id).exec().getDriver()
+ );
+ }
+ }
+
+ @Test
+ public void testModifiers() throws Exception {
+ try (
+ Network network = Network.builder()
+ .createNetworkCmdModifier(cmd -> cmd.withDriver("macvlan"))
+ .build();
+ ) {
+ String id = network.getId();
+ assertEquals(
+ "Flag is set",
+ "macvlan",
+ DockerClientFactory.instance().client().inspectNetworkCmd().withNetworkId(id).exec().getDriver()
+ );
+ }
+ }
+ }
+}
diff --git a/modules/selenium/src/test/java/org/testcontainers/junit/LinkedContainerTest.java b/modules/selenium/src/test/java/org/testcontainers/junit/LinkedContainerTest.java
index b135e92a754..862a985ad8f 100644
--- a/modules/selenium/src/test/java/org/testcontainers/junit/LinkedContainerTest.java
+++ b/modules/selenium/src/test/java/org/testcontainers/junit/LinkedContainerTest.java
@@ -8,6 +8,7 @@
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testcontainers.containers.BrowserWebDriverContainer;
+import org.testcontainers.containers.Network;
import org.testcontainers.containers.NginxContainer;
import java.io.*;
@@ -23,13 +24,18 @@ public class LinkedContainerTest {
private static File contentFolder = new File(System.getProperty("user.home") + "/.tmp-test-container");
@Rule
- public NginxContainer nginx = new NginxContainer()
+ public Network network = Network.newNetwork();
+
+ @Rule
+ public NginxContainer nginx = new NginxContainer<>()
+ .withNetwork(network)
+ .withNetworkAliases("nginx")
.withCustomContent(contentFolder.toString());
@Rule
- public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer()
- .withDesiredCapabilities(DesiredCapabilities.chrome())
- .withLinkToContainer(nginx, "nginx");
+ public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer<>()
+ .withNetwork(network)
+ .withDesiredCapabilities(DesiredCapabilities.chrome());
@BeforeClass
public static void setupContent() throws FileNotFoundException {