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 @@ -123,6 +123,14 @@ default void addFileSystemBind(final String hostPath, final String containerPath
*/
void addExposedPorts(int... ports);

/**
* Add an exposed port with the specified protocol.
*
* @param port the port to expose
* @param protocol the protocol (TCP or UDP)
*/
void addExposedPort(int port, InternetProtocol protocol);

/**
* Specify the {@link WaitStrategy} to use to determine if the container is ready.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,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()}.
* <p>
* Note: The returned port number might be outdated (for instance, after disconnecting from a network and reconnecting
Expand All @@ -156,8 +156,29 @@ default Integer getFirstMappedPort() {
* @return the port that the exposed port is mapped to, or null if it is not exposed
* @see #getContainerInfo()
* @see #getCurrentContainerInfo()
* @see #getMappedPort(int, InternetProtocol)
*/
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()}.
* <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) of the exposed port
* @return the port that the exposed port is mapped to
* @throws IllegalStateException if the container is not started
* @throws IllegalArgumentException if the requested port 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"
Expand All @@ -166,13 +187,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.name())
);
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
Expand Up @@ -1048,6 +1048,14 @@ public void addExposedPorts(int... ports) {
this.containerDef.addExposedTcpPorts(ports);
}

@Override
public void addExposedPort(int port, InternetProtocol protocol) {
this.containerDef.addExposedPort(
port,
com.github.dockerjava.api.model.InternetProtocol.parse(protocol.name())
);
}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package org.testcontainers.containers;

import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.model.ExposedPort;
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;

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.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -35,4 +43,112 @@ void test(String name, String testSet, List<Integer> expectedResult) {
List<Integer> result = containerState.getBoundPortNumbers();
assertThat(result).hasSameElementsAs(expectedResult);
}

@Test
void shouldGetMappedPortForUdpProtocol() {
ContainerState containerState = mock(ContainerState.class);
InspectContainerResponse containerInfo = mock(InspectContainerResponse.class);
NetworkSettings networkSettings = mock(NetworkSettings.class);
Ports ports = mock(Ports.class);

// Set up the mock port bindings for UDP port 5353
Map<ExposedPort, Ports.Binding[]> bindings = new HashMap<>();
ExposedPort udpPort = new ExposedPort(5353, com.github.dockerjava.api.model.InternetProtocol.UDP);
bindings.put(udpPort, new Ports.Binding[] { Ports.Binding.bindPort(12345) });

when(containerState.getContainerId()).thenReturn("test-container-id");
when(containerState.getContainerInfo()).thenReturn(containerInfo);
when(containerInfo.getNetworkSettings()).thenReturn(networkSettings);
when(networkSettings.getPorts()).thenReturn(ports);
when(ports.getBindings()).thenReturn(bindings);
doCallRealMethod().when(containerState).getMappedPort(5353, InternetProtocol.UDP);

Integer mappedPort = containerState.getMappedPort(5353, InternetProtocol.UDP);
assertThat(mappedPort).isEqualTo(12345);
}

@Test
void shouldGetMappedPortForTcpUsingProtocol() {
ContainerState containerState = mock(ContainerState.class);
InspectContainerResponse containerInfo = mock(InspectContainerResponse.class);
NetworkSettings networkSettings = mock(NetworkSettings.class);
Ports ports = mock(Ports.class);

// Set up the mock port bindings for TCP port 8080
Map<ExposedPort, Ports.Binding[]> bindings = new HashMap<>();
ExposedPort tcpPort = new ExposedPort(8080, com.github.dockerjava.api.model.InternetProtocol.TCP);
bindings.put(tcpPort, new Ports.Binding[] { Ports.Binding.bindPort(54321) });

when(containerState.getContainerId()).thenReturn("test-container-id");
when(containerState.getContainerInfo()).thenReturn(containerInfo);
when(containerInfo.getNetworkSettings()).thenReturn(networkSettings);
when(networkSettings.getPorts()).thenReturn(ports);
when(ports.getBindings()).thenReturn(bindings);
doCallRealMethod().when(containerState).getMappedPort(8080, InternetProtocol.TCP);
doCallRealMethod().when(containerState).getMappedPort(8080);

// Test with explicit TCP protocol
Integer mappedPort = containerState.getMappedPort(8080, InternetProtocol.TCP);
assertThat(mappedPort).isEqualTo(54321);

// Test default getMappedPort (should also return same value since it defaults to TCP)
Integer defaultMappedPort = containerState.getMappedPort(8080);
assertThat(defaultMappedPort).isEqualTo(54321);
}

@Test
void shouldThrowForUnmappedUdpPort() {
ContainerState containerState = mock(ContainerState.class);
InspectContainerResponse containerInfo = mock(InspectContainerResponse.class);
NetworkSettings networkSettings = mock(NetworkSettings.class);
Ports ports = mock(Ports.class);

// Set up the mock port bindings with only TCP port
Map<ExposedPort, Ports.Binding[]> bindings = new HashMap<>();
ExposedPort tcpPort = new ExposedPort(8080, com.github.dockerjava.api.model.InternetProtocol.TCP);
bindings.put(tcpPort, new Ports.Binding[] { Ports.Binding.bindPort(54321) });

when(containerState.getContainerId()).thenReturn("test-container-id");
when(containerState.getContainerInfo()).thenReturn(containerInfo);
when(containerInfo.getNetworkSettings()).thenReturn(networkSettings);
when(networkSettings.getPorts()).thenReturn(ports);
when(ports.getBindings()).thenReturn(bindings);
doCallRealMethod().when(containerState).getMappedPort(8080, InternetProtocol.UDP);

// Should throw when trying to get unmapped UDP port
assertThatThrownBy(() -> containerState.getMappedPort(8080, InternetProtocol.UDP))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("8080/udp")
.hasMessageContaining("is not mapped");
}

@Test
void shouldSupportBothTcpAndUdpOnSamePort() {
ContainerState containerState = mock(ContainerState.class);
InspectContainerResponse containerInfo = mock(InspectContainerResponse.class);
NetworkSettings networkSettings = mock(NetworkSettings.class);
Ports ports = mock(Ports.class);

// Set up the mock port bindings with both TCP and UDP on the same port
Map<ExposedPort, Ports.Binding[]> bindings = new HashMap<>();
ExposedPort tcpPort = new ExposedPort(5000, com.github.dockerjava.api.model.InternetProtocol.TCP);
ExposedPort udpPort = new ExposedPort(5000, com.github.dockerjava.api.model.InternetProtocol.UDP);
bindings.put(tcpPort, new Ports.Binding[] { Ports.Binding.bindPort(11111) });
bindings.put(udpPort, new Ports.Binding[] { Ports.Binding.bindPort(22222) });

when(containerState.getContainerId()).thenReturn("test-container-id");
when(containerState.getContainerInfo()).thenReturn(containerInfo);
when(containerInfo.getNetworkSettings()).thenReturn(networkSettings);
when(networkSettings.getPorts()).thenReturn(ports);
when(ports.getBindings()).thenReturn(bindings);
doCallRealMethod().when(containerState).getMappedPort(5000, InternetProtocol.TCP);
doCallRealMethod().when(containerState).getMappedPort(5000, InternetProtocol.UDP);

// Both should be mapped to different host ports
Integer tcpMappedPort = containerState.getMappedPort(5000, InternetProtocol.TCP);
Integer udpMappedPort = containerState.getMappedPort(5000, InternetProtocol.UDP);

assertThat(tcpMappedPort).isEqualTo(11111);
assertThat(udpMappedPort).isEqualTo(22222);
}
}