Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,24 @@ default Integer getFirstMappedPort() {
* @see #getCurrentContainerInfo()
*/
default Integer getMappedPort(int originalPort) {
return getMappedPort(originalPort, InternetProtocol.TCP);
}

/**
* Get the actual mapped port for a given port exposed by the container, for the given protocol.
* It should be used in conjunction with {@link #getHost()}.
* <p>
* Note: The returned port number might be outdated (for instance, after disconnecting from a network and reconnecting
* again). If you always need up-to-date value, override the {@link #getContainerInfo()} to return the
* {@link #getCurrentContainerInfo()}.
*
* @param originalPort the original port that is exposed
* @param protocol the protocol (TCP or UDP) that the port is exposed with
* @return the port that the exposed port is mapped to, or null if it is not exposed
* @see #getContainerInfo()
* @see #getCurrentContainerInfo()
*/
default Integer getMappedPort(int originalPort, InternetProtocol protocol) {
Preconditions.checkState(
this.getContainerId() != null,
"Mapped port can only be obtained after the container is started"
Expand All @@ -166,13 +184,19 @@ default Integer getMappedPort(int originalPort) {
Ports.Binding[] binding = new Ports.Binding[0];
final InspectContainerResponse containerInfo = this.getContainerInfo();
if (containerInfo != null) {
binding = containerInfo.getNetworkSettings().getPorts().getBindings().get(new ExposedPort(originalPort));
ExposedPort exposedPort = new ExposedPort(
originalPort,
com.github.dockerjava.api.model.InternetProtocol.parse(protocol.toDockerNotation())
);
binding = containerInfo.getNetworkSettings().getPorts().getBindings().get(exposedPort);
}

if (binding != null && binding.length > 0 && binding[0] != null) {
return Integer.valueOf(binding[0].getHostPortSpec());
} else {
throw new IllegalArgumentException("Requested port (" + originalPort + ") is not mapped");
throw new IllegalArgumentException(
"Requested port (" + originalPort + "/" + protocol.toDockerNotation() + ") is not mapped"
);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
package org.testcontainers.containers;

import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.Ports;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Answers;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -35,4 +45,40 @@ void test(String name, String testSet, List<Integer> expectedResult) {
List<Integer> result = containerState.getBoundPortNumbers();
assertThat(result).hasSameElementsAs(expectedResult);
}

@Test
void getMappedPortDistinguishesTcpAndUdpBindingsForSamePort() {
ContainerState containerState = mock(ContainerState.class);
doCallRealMethod().when(containerState).getMappedPort(anyInt());
doCallRealMethod().when(containerState).getMappedPort(anyInt(), any());
when(containerState.getContainerId()).thenReturn("container-id");

InspectContainerResponse containerInfo = mock(InspectContainerResponse.class, Answers.RETURNS_DEEP_STUBS);
Map<ExposedPort, Ports.Binding[]> bindings = new HashMap<>();
bindings.put(ExposedPort.tcp(8080), new Ports.Binding[] { Ports.Binding.bindPort(12345) });
bindings.put(ExposedPort.udp(8080), new Ports.Binding[] { Ports.Binding.bindPort(54321) });
when(containerInfo.getNetworkSettings().getPorts().getBindings()).thenReturn(bindings);
when(containerState.getContainerInfo()).thenReturn(containerInfo);

assertThat(containerState.getMappedPort(8080)).isEqualTo(12345);
assertThat(containerState.getMappedPort(8080, InternetProtocol.TCP)).isEqualTo(12345);
assertThat(containerState.getMappedPort(8080, InternetProtocol.UDP)).isEqualTo(54321);
}

@Test
void getMappedPortThrowsWhenPortNotMappedForProtocol() {
ContainerState containerState = mock(ContainerState.class);
doCallRealMethod().when(containerState).getMappedPort(anyInt(), any());
when(containerState.getContainerId()).thenReturn("container-id");

InspectContainerResponse containerInfo = mock(InspectContainerResponse.class, Answers.RETURNS_DEEP_STUBS);
Map<ExposedPort, Ports.Binding[]> bindings = new HashMap<>();
bindings.put(ExposedPort.tcp(8080), new Ports.Binding[] { Ports.Binding.bindPort(12345) });
when(containerInfo.getNetworkSettings().getPorts().getBindings()).thenReturn(bindings);
when(containerState.getContainerInfo()).thenReturn(containerInfo);

assertThatThrownBy(() -> containerState.getMappedPort(8080, InternetProtocol.UDP))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("udp");
}
}
3 changes: 3 additions & 0 deletions docs/features/networking.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ This can be done using the `getMappedPort` method, which takes the original (con
Because the randomised port mapping happens during container startup, the container must be running at the time `getMappedPort` is called.
You may need to ensure that the startup order of components in your tests caters for this.

`getMappedPort` assumes a TCP port by default. If a container exposes the same port number for both TCP and UDP,
pass the desired `InternetProtocol` explicitly, e.g. `getMappedPort(originalPort, InternetProtocol.UDP)`.

There is also a `getFirstMappedPort` method for convenience, for the fairly common scenario of a container that only exposes one port:

<!--codeinclude-->
Expand Down