Skip to content

Commit

Permalink
Add support for Docker networks. (#372)
Browse files Browse the repository at this point in the history
Add support for Docker networks
  • Loading branch information
bsideup authored and rnorth committed Jun 25, 2017
1 parent 82d9d15 commit 3e513be
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions core/src/main/java/org/testcontainers/containers/Container.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>--network &lt;name&gt;</code>
* 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 <code>--network-alias &lt;my-service&gt;</code>
* 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ public class GenericContainer<SELF extends GenericContainer<SELF>>
@NonNull
private String networkMode;

@NonNull
private Network network;

@NonNull
private List<String> networkAliases = new ArrayList<>();

@NonNull
private Future<String> image;

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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}
*/
Expand Down
78 changes: 78 additions & 0 deletions core/src/main/java/org/testcontainers/containers/Network.java
Original file line number Diff line number Diff line change
@@ -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<Consumer<CreateNetworkCmd>> 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<CreateNetworkCmd> consumer : createNetworkCmdModifiers) {
consumer.accept(createNetworkCmd);
}

return createNetworkCmd.exec().getId();
}

@Override
protected void after() {
close();
}
}
}
43 changes: 26 additions & 17 deletions core/src/main/java/org/testcontainers/utility/ResourceReaper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -25,7 +22,7 @@ public final class ResourceReaper {
private static ResourceReaper instance;
private final DockerClient dockerClient;
private Map<String, String> registeredContainers = new ConcurrentHashMap<>();
private List<String> registeredNetworks = new ArrayList<>();
private Set<String> registeredNetworks = Collections.newSetFromMap(new ConcurrentHashMap<>());

private ResourceReaper() {
dockerClient = DockerClientFactory.instance().client();
Expand Down Expand Up @@ -145,22 +142,34 @@ public void removeNetworks(String identifier) {
}

private void removeNetwork(String networkName) {
List<Network> 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<Network> 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);
}
}
}
94 changes: 94 additions & 0 deletions core/src/test/java/org/testcontainers/containers/NetworkTest.java
Original file line number Diff line number Diff line change
@@ -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()
);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand All @@ -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 {
Expand Down

0 comments on commit 3e513be

Please sign in to comment.