Skip to content

Commit

Permalink
Improve ToxiProxyContainer test and docs (#6065)
Browse files Browse the repository at this point in the history
Current implementation exposed utilities to handle `ToxiProxyClient`
and proxies. However, there are some issues which demonstrate that
the container class is coupled to the client. Tests and docs have
been updated in order to promote the usage of its own `ToxiProxyClient`.
Also, a note about the number of ports reserved by Testcontainers
is added.
  • Loading branch information
eddumelendez committed Nov 4, 2022
1 parent 665ec1c commit a25da3d
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 47 deletions.
9 changes: 3 additions & 6 deletions docs/modules/toxiproxy.md
Expand Up @@ -32,16 +32,13 @@ We do this as follows:
To establish a connection from the test code (on the host machine) to the target container via Toxiproxy, we obtain **Toxiproxy's** proxy host IP and port:

<!--codeinclude-->
[Obtaining proxied host and port for connections from the host machine](../../modules/toxiproxy/src/test/java/org/testcontainers/containers/ToxiproxyTest.java) inside_block:obtainProxiedHostAndPortForHostMachine
[Obtaining proxied host and port](../../modules/toxiproxy/src/test/java/org/testcontainers/containers/ToxiproxyTest.java) inside_block:obtainProxiedHostAndPortForHostMachine
<!--/codeinclude-->

Code under test should connect to this proxied host IP and port.

To establish a connection from a different container on the same network to the target container via Toxiproxy, we use **Toxiproxy's** network alias and original port:

<!--codeinclude-->
[Obtaining proxied host and port for connections from a different container](../../modules/toxiproxy/src/test/java/org/testcontainers/containers/ToxiproxyTest.java) inside_block:obtainProxiedHostAndPortForDifferentContainer
<!--/codeinclude-->
!!! note
Currently, `ToxiProxyContainer` will reserve 31 ports, starting at 8666.

Other containers should connect to this proxied host and port.

Expand Down
Expand Up @@ -85,7 +85,9 @@ public int getControlPort() {
* @param container target container
* @param port port number on the target service that should be proxied
* @return a {@link ContainerProxy} instance
* @deprecated {@link ToxiproxyContainer} will not build the client. Proxies should be provided manually.
*/
@Deprecated
public ContainerProxy getProxy(GenericContainer<?> container, int port) {
return this.getProxy(container.getNetworkAliases().get(0), port);
}
Expand All @@ -102,7 +104,9 @@ public ContainerProxy getProxy(GenericContainer<?> container, int port) {
* @param hostname hostname of target server to be proxied
* @param port port number on the target server that should be proxied
* @return a {@link ContainerProxy} instance
* @deprecated {@link ToxiproxyContainer} will not build the client. Proxies should be provided manually.
*/
@Deprecated
public ContainerProxy getProxy(String hostname, int port) {
String upstream = hostname + ":" + port;

Expand All @@ -126,6 +130,7 @@ public ContainerProxy getProxy(String hostname, int port) {
}

@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@Deprecated
public static class ContainerProxy {

private static final String CUT_CONNECTION_DOWNSTREAM = "CUT_CONNECTION_DOWNSTREAM";
Expand Down
@@ -1,9 +1,10 @@
package org.testcontainers.containers;

import eu.rekawek.toxiproxy.Proxy;
import eu.rekawek.toxiproxy.ToxiproxyClient;
import eu.rekawek.toxiproxy.model.ToxicDirection;
import org.junit.Rule;
import org.junit.Test;
import org.testcontainers.utility.DockerImageName;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisConnectionException;

Expand All @@ -19,30 +20,21 @@ public class ToxiproxyTest {

// spotless:off
// creatingProxy {
// An alias that can be used to resolve the Toxiproxy container by name in the network it is connected to.
// It can be used as a hostname of the Toxiproxy container by other containers in the same network.
private static final String TOXIPROXY_NETWORK_ALIAS = "toxiproxy";

// Create a common docker network so that containers can communicate
@Rule
public Network network = Network.newNetwork();

private static final DockerImageName REDIS_IMAGE = DockerImageName.parse("redis:5.0.4");

// The target container - this could be anything
@Rule
public GenericContainer<?> redis = new GenericContainer<>(REDIS_IMAGE)
public GenericContainer<?> redis = new GenericContainer<>("redis:5.0.4")
.withExposedPorts(6379)
.withNetwork(network);

private static final DockerImageName TOXIPROXY_IMAGE = DockerImageName.parse("ghcr.io/shopify/toxiproxy:2.5.0");
.withNetwork(network)
.withNetworkAliases("redis");

// Toxiproxy container, which will be used as a TCP proxy
@Rule
public ToxiproxyContainer toxiproxy = new ToxiproxyContainer(TOXIPROXY_IMAGE)
.withNetwork(network)
.withNetworkAliases(TOXIPROXY_NETWORK_ALIAS);

public ToxiproxyContainer toxiproxy = new ToxiproxyContainer("ghcr.io/shopify/toxiproxy:2.5.0")
.withNetwork(network);
// }
// spotless:on

Expand All @@ -58,12 +50,13 @@ public void testDirect() {
@Test
public void testLatencyViaProxy() throws IOException {
// obtainProxyObject {
final ToxiproxyContainer.ContainerProxy proxy = toxiproxy.getProxy(redis, 6379);
final ToxiproxyClient toxiproxyClient = new ToxiproxyClient(toxiproxy.getHost(), toxiproxy.getControlPort());
final Proxy proxy = toxiproxyClient.createProxy("redis", "0.0.0.0:8666", "redis:6379");
// }

// obtainProxiedHostAndPortForHostMachine {
final String ipAddressViaToxiproxy = proxy.getContainerIpAddress();
final int portViaToxiproxy = proxy.getProxyPort();
final String ipAddressViaToxiproxy = toxiproxy.getHost();
final int portViaToxiproxy = toxiproxy.getMappedPort(8666);
// }

final Jedis jedis = createJedis(ipAddressViaToxiproxy, portViaToxiproxy);
Expand All @@ -84,17 +77,19 @@ public void testLatencyViaProxy() throws IOException {
}

@Test
public void testConnectionCut() {
final ToxiproxyContainer.ContainerProxy proxy = toxiproxy.getProxy(redis, 6379);
final Jedis jedis = createJedis(proxy.getContainerIpAddress(), proxy.getProxyPort());
public void testConnectionCut() throws IOException {
final ToxiproxyClient toxiproxyClient = new ToxiproxyClient(toxiproxy.getHost(), toxiproxy.getControlPort());
final Proxy proxy = toxiproxyClient.createProxy("redis", "0.0.0.0:8666", "redis:6379");
final Jedis jedis = createJedis(toxiproxy.getHost(), toxiproxy.getMappedPort(8666));
jedis.set("somekey", "somevalue");

assertThat(jedis.get("somekey"))
.as("access to the container works OK before cutting the connection")
.isEqualTo("somevalue");

// disableProxy {
proxy.setConnectionCut(true);
proxy.toxics().bandwidth("CUT_CONNECTION_DOWNSTREAM", ToxicDirection.DOWNSTREAM, 0);
proxy.toxics().bandwidth("CUT_CONNECTION_UPSTREAM", ToxicDirection.UPSTREAM, 0);

// for example, expect failure when the connection is cut
assertThat(
Expand All @@ -105,7 +100,8 @@ public void testConnectionCut() {
.as("calls fail when the connection is cut")
.isInstanceOf(JedisConnectionException.class);

proxy.setConnectionCut(false);
proxy.toxics().get("CUT_CONNECTION_DOWNSTREAM").remove();
proxy.toxics().get("CUT_CONNECTION_UPSTREAM").remove();

// and with the connection re-established, expect success
assertThat(jedis.get("somekey"))
Expand All @@ -115,24 +111,30 @@ public void testConnectionCut() {
}

@Test
public void testMultipleProxiesCanBeCreated() {
public void testMultipleProxiesCanBeCreated() throws IOException {
try (
GenericContainer<?> secondRedis = new GenericContainer<>(REDIS_IMAGE)
GenericContainer<?> secondRedis = new GenericContainer<>("redis:5.0.4")
.withExposedPorts(6379)
.withNetwork(network)
.withNetworkAliases("redis2")
) {
secondRedis.start();

final ToxiproxyContainer.ContainerProxy firstProxy = toxiproxy.getProxy(redis, 6379);
final ToxiproxyContainer.ContainerProxy secondProxy = toxiproxy.getProxy(secondRedis, 6379);
final ToxiproxyClient toxiproxyClient = new ToxiproxyClient(
toxiproxy.getHost(),
toxiproxy.getControlPort()
);
final Proxy firstProxy = toxiproxyClient.createProxy("redis1", "0.0.0.0:8666", "redis:6379");
toxiproxyClient.createProxy("redis2", "0.0.0.0:8667", "redis2:6379");

final Jedis firstJedis = createJedis(firstProxy.getContainerIpAddress(), firstProxy.getProxyPort());
final Jedis secondJedis = createJedis(secondProxy.getContainerIpAddress(), secondProxy.getProxyPort());
final Jedis firstJedis = createJedis(toxiproxy.getHost(), toxiproxy.getMappedPort(8666));
final Jedis secondJedis = createJedis(toxiproxy.getHost(), toxiproxy.getMappedPort(8667));

firstJedis.set("somekey", "somevalue");
secondJedis.set("somekey", "somevalue");

firstProxy.setConnectionCut(true);
firstProxy.toxics().bandwidth("CUT_CONNECTION_DOWNSTREAM", ToxicDirection.DOWNSTREAM, 0);
firstProxy.toxics().bandwidth("CUT_CONNECTION_UPSTREAM", ToxicDirection.UPSTREAM, 0);

assertThat(
catchThrowable(() -> {
Expand All @@ -149,11 +151,8 @@ public void testMultipleProxiesCanBeCreated() {
@Test
public void testOriginalAndMappedPorts() {
final ToxiproxyContainer.ContainerProxy proxy = toxiproxy.getProxy("hostname", 7070);
// obtainProxiedHostAndPortForDifferentContainer {
final String hostViaToxiproxy = TOXIPROXY_NETWORK_ALIAS;

final int portViaToxiproxy = proxy.getOriginalProxyPort();
// }
assertThat(hostViaToxiproxy).as("host is correct").isEqualTo(TOXIPROXY_NETWORK_ALIAS);
assertThat(portViaToxiproxy).as("original port is correct").isEqualTo(8666);

final ToxiproxyContainer.ContainerProxy proxy1 = toxiproxy.getProxy("hostname1", 8080);
Expand All @@ -176,13 +175,6 @@ public void testProxyName() {
assertThat(proxy.getName()).as("proxy name is hostname and port").isEqualTo("hostname:7070");
}

@Test
public void testControlPort() {
final int controlPort = toxiproxy.getControlPort();

assertThat(controlPort).as("control port is mapped from port 8474").isEqualTo(toxiproxy.getMappedPort(8474));
}

private void checkCallWithLatency(
Jedis jedis,
final String description,
Expand Down

0 comments on commit a25da3d

Please sign in to comment.