diff --git a/core/src/main/java/org/testcontainers/containers/ContainerState.java b/core/src/main/java/org/testcontainers/containers/ContainerState.java index e19f7a85310..5b7940c4cdd 100644 --- a/core/src/main/java/org/testcontainers/containers/ContainerState.java +++ b/core/src/main/java/org/testcontainers/containers/ContainerState.java @@ -5,6 +5,7 @@ import com.github.dockerjava.api.command.InspectContainerResponse; import com.github.dockerjava.api.exception.DockerException; import com.github.dockerjava.api.model.ExposedPort; +import com.github.dockerjava.api.model.InternetProtocol; import com.github.dockerjava.api.model.PortBinding; import com.github.dockerjava.api.model.Ports; import com.google.common.base.Preconditions; @@ -145,7 +146,7 @@ default Integer getFirstMappedPort() { } /** - * Get the actual mapped port for a given port exposed by the container. + * Get the actual mapped port for a given TCP port exposed by the container. * It should be used in conjunction with {@link #getHost()}. *

* Note: The returned port number might be outdated (for instance, after disconnecting from a network and reconnecting @@ -158,6 +159,25 @@ default Integer getFirstMappedPort() { * @see #getCurrentContainerInfo() */ default Integer getMappedPort(int originalPort) { + return getMappedPort(originalPort, InternetProtocol.TCP); + } + + /** + * Get the actual mapped port for a given port and protocol exposed by the container. + * It should be used in conjunction with {@link #getHost()}. + *

+ * 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 of the exposed port ({@link InternetProtocol#TCP} or {@link InternetProtocol#UDP}) + * @return the port that the exposed port is mapped to + * @throws IllegalArgumentException if the requested port/protocol combination is not mapped + * @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" @@ -166,13 +186,16 @@ 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)); + binding = + containerInfo.getNetworkSettings().getPorts().getBindings().get(new ExposedPort(originalPort, protocol)); } 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.toString().toLowerCase() + ") is not mapped" + ); } } diff --git a/core/src/test/java/org/testcontainers/containers/ContainerStateTest.java b/core/src/test/java/org/testcontainers/containers/ContainerStateTest.java index 7b37bc1926f..db01a7390a0 100644 --- a/core/src/test/java/org/testcontainers/containers/ContainerStateTest.java +++ b/core/src/test/java/org/testcontainers/containers/ContainerStateTest.java @@ -1,5 +1,11 @@ package org.testcontainers.containers; +import com.github.dockerjava.api.command.InspectContainerResponse; +import com.github.dockerjava.api.model.ExposedPort; +import com.github.dockerjava.api.model.InternetProtocol; +import com.github.dockerjava.api.model.NetworkSettings; +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; @@ -7,6 +13,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -35,4 +42,64 @@ void test(String name, String testSet, List expectedResult) { List result = containerState.getBoundPortNumbers(); assertThat(result).hasSameElementsAs(expectedResult); } + + @Test + void getMappedPortWithTcpProtocolReturnsMappedPort() { + ContainerState containerState = mock(ContainerState.class); + doCallRealMethod().when(containerState).getMappedPort(8080); + doCallRealMethod().when(containerState).getMappedPort(8080, InternetProtocol.TCP); + when(containerState.getContainerId()).thenReturn("test-container-id"); + + Ports ports = new Ports(); + ports.bind(new ExposedPort(8080, InternetProtocol.TCP), Ports.Binding.bindPort(32768)); + + NetworkSettings networkSettings = mock(NetworkSettings.class); + when(networkSettings.getPorts()).thenReturn(ports); + + InspectContainerResponse containerInfo = mock(InspectContainerResponse.class); + when(containerInfo.getNetworkSettings()).thenReturn(networkSettings); + when(containerState.getContainerInfo()).thenReturn(containerInfo); + + assertThat(containerState.getMappedPort(8080)).isEqualTo(32768); + assertThat(containerState.getMappedPort(8080, InternetProtocol.TCP)).isEqualTo(32768); + } + + @Test + void getMappedPortWithUdpProtocolReturnsMappedPort() { + ContainerState containerState = mock(ContainerState.class); + doCallRealMethod().when(containerState).getMappedPort(53, InternetProtocol.UDP); + when(containerState.getContainerId()).thenReturn("test-container-id"); + + Ports ports = new Ports(); + ports.bind(new ExposedPort(53, InternetProtocol.UDP), Ports.Binding.bindPort(32769)); + + NetworkSettings networkSettings = mock(NetworkSettings.class); + when(networkSettings.getPorts()).thenReturn(ports); + + InspectContainerResponse containerInfo = mock(InspectContainerResponse.class); + when(containerInfo.getNetworkSettings()).thenReturn(networkSettings); + when(containerState.getContainerInfo()).thenReturn(containerInfo); + + assertThat(containerState.getMappedPort(53, InternetProtocol.UDP)).isEqualTo(32769); + } + + @Test + void getMappedPortThrowsWhenPortNotMapped() { + ContainerState containerState = mock(ContainerState.class); + doCallRealMethod().when(containerState).getMappedPort(9999, InternetProtocol.UDP); + when(containerState.getContainerId()).thenReturn("test-container-id"); + + Ports ports = new Ports(); + + NetworkSettings networkSettings = mock(NetworkSettings.class); + when(networkSettings.getPorts()).thenReturn(ports); + + InspectContainerResponse containerInfo = mock(InspectContainerResponse.class); + when(containerInfo.getNetworkSettings()).thenReturn(networkSettings); + when(containerState.getContainerInfo()).thenReturn(containerInfo); + + assertThatThrownBy(() -> containerState.getMappedPort(9999, InternetProtocol.UDP)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("9999/udp"); + } }