Skip to content

Commit 5a2c5ed

Browse files
committed
Issue 554 added udp support for generic container and docker compose container
1 parent a5c2894 commit 5a2c5ed

20 files changed

+567
-119
lines changed

core/build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ task japicmp(type: me.champeau.gradle.japicmp.JapicmpTask) {
9898
"org.testcontainers.containers.wait.LogMessageWaitStrategy",
9999
"org.testcontainers.containers.wait.Wait",
100100
"org.testcontainers.containers.wait.WaitAllStrategy",
101+
"org.testcontainers.containers.ContainerState",
102+
"org.testcontainers.containers.wait.strategy.WaitStrategyTarget",
101103
"org.testcontainers.containers.wait.WaitStrategy",
102104
"org.testcontainers.dockerclient.AuditLoggingDockerClient",
103105
"org.testcontainers.dockerclient.LogToStringContainerCallback",

core/src/main/java/org/testcontainers/containers/ComposeServiceWaitStrategyTarget.java

+22-8
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
import org.testcontainers.DockerClientFactory;
99
import org.testcontainers.containers.wait.strategy.WaitStrategyTarget;
1010

11-
import java.util.ArrayList;
12-
import java.util.HashMap;
11+
import java.util.HashSet;
1312
import java.util.List;
1413
import java.util.Map;
14+
import java.util.Set;
15+
import java.util.stream.Collectors;
1516

1617

1718
/**
@@ -23,31 +24,44 @@ class ComposeServiceWaitStrategyTarget implements WaitStrategyTarget {
2324
private final Container container;
2425
private final GenericContainer proxyContainer;
2526
@NonNull
26-
private Map<Integer, Integer> mappedPorts;
27-
@Getter(lazy=true)
27+
private Map<Port, Integer> mappedPorts;
28+
@Getter(lazy = true)
2829
private final InspectContainerResponse containerInfo = DockerClientFactory.instance().client().inspectContainerCmd(getContainerId()).exec();
2930

3031
ComposeServiceWaitStrategyTarget(Container container, GenericContainer proxyContainer,
31-
@NonNull Map<Integer, Integer> mappedPorts) {
32+
@NonNull Map<Port, Integer> mappedPorts) {
3233
this.container = container;
3334
this.proxyContainer = proxyContainer;
34-
this.mappedPorts = new HashMap<>(mappedPorts);
35+
this.mappedPorts = mappedPorts;
3536
}
3637

3738
/**
3839
* {@inheritDoc}
3940
*/
4041
@Override
4142
public List<Integer> getExposedPorts() {
42-
return new ArrayList<>(this.mappedPorts.keySet());
43+
return this.mappedPorts.keySet()
44+
.stream()
45+
.map(Port::getValue)
46+
.collect(Collectors.toList());
47+
}
48+
49+
@Override
50+
public Set<Port> exposedPorts() {
51+
return new HashSet<>(mappedPorts.keySet());
4352
}
4453

4554
/**
4655
* {@inheritDoc}
4756
*/
4857
@Override
4958
public Integer getMappedPort(int originalPort) {
50-
return this.proxyContainer.getMappedPort(this.mappedPorts.get(originalPort));
59+
return this.getMappedPort(originalPort, InternetProtocol.TCP);
60+
}
61+
62+
@Override
63+
public Integer getMappedPort(int originalPort, InternetProtocol internetProtocol) {
64+
return this.proxyContainer.getMappedPort(this.mappedPorts.get(Port.of(originalPort, internetProtocol)), internetProtocol);
5165
}
5266

5367
/**

core/src/main/java/org/testcontainers/containers/Container.java

+26-14
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@
77
import lombok.NonNull;
88
import lombok.Value;
99
import org.testcontainers.DockerClientFactory;
10-
import org.testcontainers.images.ImagePullPolicy;
1110
import org.testcontainers.containers.output.OutputFrame;
1211
import org.testcontainers.containers.startupcheck.StartupCheckStrategy;
1312
import org.testcontainers.containers.traits.LinkableContainer;
1413
import org.testcontainers.containers.wait.strategy.WaitStrategy;
14+
import org.testcontainers.images.ImagePullPolicy;
1515
import org.testcontainers.utility.LogUtils;
1616
import org.testcontainers.utility.MountableFile;
1717

1818
import java.time.Duration;
1919
import java.util.List;
2020
import java.util.Map;
2121
import java.util.Optional;
22+
import java.util.Set;
2223
import java.util.concurrent.Future;
2324
import java.util.function.Consumer;
2425
import java.util.function.Function;
@@ -85,9 +86,9 @@ default void addFileSystemBind(final String hostPath, final String containerPath
8586
* Adds a file system binding. Consider using {@link #withFileSystemBind(String, String, BindMode)}
8687
* for building a container in a fluent style.
8788
*
88-
* @param hostPath the file system path on the host
89-
* @param containerPath the file system path inside the container
90-
* @param mode the bind mode
89+
* @param hostPath the file system path on the host
90+
* @param containerPath the file system path inside the container
91+
* @param mode the bind mode
9192
* @param selinuxContext selinux context argument to use for this file
9293
*/
9394
void addFileSystemBind(String hostPath, String containerPath, BindMode mode, SelinuxContext selinuxContext);
@@ -96,7 +97,7 @@ default void addFileSystemBind(final String hostPath, final String containerPath
9697
* Add a link to another container.
9798
*
9899
* @param otherContainer the other container object to link to
99-
* @param alias the alias (for the other container) that this container should be able to use
100+
* @param alias the alias (for the other container) that this container should be able to use
100101
* @deprecated Links are deprecated (see <a href="https://github.com/testcontainers/testcontainers-java/issues/465">#465</a>). Please use {@link Network} features instead.
101102
*/
102103
@Deprecated
@@ -110,6 +111,8 @@ default void addFileSystemBind(final String hostPath, final String containerPath
110111
*/
111112
void addExposedPort(Integer port);
112113

114+
void addExposedPort(Integer port, InternetProtocol internetProtocol);
115+
113116
/**
114117
* Add exposed ports. Consider using {@link #withExposedPorts(Integer...)}
115118
* for building a container in a fluent style.
@@ -118,19 +121,21 @@ default void addFileSystemBind(final String hostPath, final String containerPath
118121
*/
119122
void addExposedPorts(int... ports);
120123

124+
void addExposedPorts(Set<Integer> ports, InternetProtocol internetProtocol);
125+
121126
/**
122127
* Specify the {@link WaitStrategy} to use to determine if the container is ready.
123128
*
124-
* @see org.testcontainers.containers.wait.strategy.Wait#defaultWaitStrategy()
125129
* @param waitStrategy the WaitStrategy to use
126130
* @return this
131+
* @see org.testcontainers.containers.wait.strategy.Wait#defaultWaitStrategy()
127132
*/
128133
SELF waitingFor(@NonNull WaitStrategy waitStrategy);
129134

130135
/**
131136
* Adds a file system binding.
132137
*
133-
* @param hostPath the file system path on the host
138+
* @param hostPath the file system path on the host
134139
* @param containerPath the file system path inside the container
135140
* @return this
136141
*/
@@ -141,9 +146,9 @@ default SELF withFileSystemBind(String hostPath, String containerPath) {
141146
/**
142147
* Adds a file system binding.
143148
*
144-
* @param hostPath the file system path on the host
149+
* @param hostPath the file system path on the host
145150
* @param containerPath the file system path inside the container
146-
* @param mode the bind mode
151+
* @param mode the bind mode
147152
* @return this
148153
*/
149154
SELF withFileSystemBind(String hostPath, String containerPath, BindMode mode);
@@ -152,7 +157,7 @@ default SELF withFileSystemBind(String hostPath, String containerPath) {
152157
* Adds container volumes.
153158
*
154159
* @param container the container to add volumes from
155-
* @param mode the bind mode
160+
* @param mode the bind mode
156161
* @return this
157162
*/
158163
SELF withVolumesFrom(Container container, BindMode mode);
@@ -165,6 +170,8 @@ default SELF withFileSystemBind(String hostPath, String containerPath) {
165170
*/
166171
SELF withExposedPorts(Integer... ports);
167172

173+
SELF withExposedPorts(Set<Integer> ports, InternetProtocol internetProtocol);
174+
168175
/**
169176
* Set the file to be copied before starting a created container
170177
*
@@ -186,7 +193,7 @@ default SELF withFileSystemBind(String hostPath, String containerPath) {
186193
/**
187194
* Add an environment variable to be passed to the container.
188195
*
189-
* @param key environment variable key
196+
* @param key environment variable key
190197
* @param mapper environment variable value mapper, accepts old value as an argument
191198
* @return this
192199
*/
@@ -214,6 +221,7 @@ default SELF withEnv(String key, Function<Optional<String>, String> mapper) {
214221

215222
/**
216223
* Add labels to the container.
224+
*
217225
* @param labels map of labels
218226
* @return this
219227
*/
@@ -237,7 +245,8 @@ default SELF withEnv(String key, Function<Optional<String>, String> mapper) {
237245

238246
/**
239247
* Add an extra host entry to be passed to the container
240-
* @param hostname hostname to use for this hosts file entry
248+
*
249+
* @param hostname hostname to use for this hosts file entry
241250
* @param ipAddress IP address to use for this hosts file entry
242251
* @return this
243252
*/
@@ -272,6 +281,7 @@ default SELF withEnv(String key, Function<Optional<String>, String> mapper) {
272281

273282
/**
274283
* Set the image pull policy of the container
284+
*
275285
* @return
276286
*/
277287
SELF withImagePullPolicy(ImagePullPolicy policy);
@@ -304,15 +314,16 @@ default SELF withClasspathResourceMapping(final String resourcePath, final Strin
304314

305315
/**
306316
* Set the duration of waiting time until container treated as started.
307-
* @see WaitStrategy#waitUntilReady(org.testcontainers.containers.wait.strategy.WaitStrategyTarget)
308317
*
309318
* @param startupTimeout timeout
310319
* @return this
320+
* @see WaitStrategy#waitUntilReady(org.testcontainers.containers.wait.strategy.WaitStrategyTarget)
311321
*/
312322
SELF withStartupTimeout(Duration startupTimeout);
313323

314324
/**
315325
* Set the privilegedMode mode for the container
326+
*
316327
* @param mode boolean
317328
* @return this
318329
*/
@@ -406,7 +417,6 @@ default void followOutput(Consumer<OutputFrame> consumer, OutputFrame.OutputType
406417
Future<String> getImage();
407418

408419
/**
409-
*
410420
* @deprecated use getEnvMap
411421
*/
412422
@Deprecated
@@ -428,6 +438,8 @@ default void followOutput(Consumer<OutputFrame> consumer, OutputFrame.OutputType
428438

429439
void setExposedPorts(List<Integer> exposedPorts);
430440

441+
void setExposedPorts(Set<Port> exposedPorts);
442+
431443
void setPortBindings(List<String> portBindings);
432444

433445
void setExtraHosts(List<String> extraHosts);

core/src/main/java/org/testcontainers/containers/ContainerState.java

+26-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
1313
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
1414
import org.apache.commons.compress.utils.IOUtils;
15+
import org.apache.commons.lang.StringUtils;
1516
import org.slf4j.Logger;
1617
import org.slf4j.LoggerFactory;
1718
import org.testcontainers.DockerClientFactory;
@@ -30,9 +31,11 @@
3031
import java.nio.charset.Charset;
3132
import java.nio.charset.StandardCharsets;
3233
import java.util.ArrayList;
34+
import java.util.LinkedHashSet;
3335
import java.util.List;
3436
import java.util.Map;
3537
import java.util.Objects;
38+
import java.util.Set;
3639
import java.util.stream.Collectors;
3740

3841
public interface ContainerState {
@@ -138,12 +141,17 @@ default Integer getFirstMappedPort() {
138141
* @return the port that the exposed port is mapped to, or null if it is not exposed
139142
*/
140143
default Integer getMappedPort(int originalPort) {
144+
return getMappedPort(originalPort, InternetProtocol.TCP);
145+
}
146+
147+
default Integer getMappedPort(int originalPort, InternetProtocol internetProtocol) {
141148
Preconditions.checkState(this.getContainerId() != null, "Mapped port can only be obtained after the container is started");
142149

143150
Ports.Binding[] binding = new Ports.Binding[0];
144151
final InspectContainerResponse containerInfo = this.getContainerInfo();
145152
if (containerInfo != null) {
146-
binding = containerInfo.getNetworkSettings().getPorts().getBindings().get(new ExposedPort(originalPort));
153+
com.github.dockerjava.api.model.InternetProtocol internetProtocol1 = com.github.dockerjava.api.model.InternetProtocol.parse(internetProtocol.toDockerNotation());
154+
binding = containerInfo.getNetworkSettings().getPorts().getBindings().get(new ExposedPort(originalPort, internetProtocol1));
147155
}
148156

149157
if (binding != null && binding.length > 0 && binding[0] != null) {
@@ -158,6 +166,8 @@ default Integer getMappedPort(int originalPort) {
158166
*/
159167
List<Integer> getExposedPorts();
160168

169+
Set<Port> exposedPorts();
170+
161171
/**
162172
* @return the port bindings
163173
*/
@@ -185,6 +195,21 @@ default List<Integer> getBoundPortNumbers() {
185195
.collect(Collectors.toList());
186196
}
187197

198+
default Set<Port> getBoundPorts() {
199+
final Ports hostPortBindings = this.getContainerInfo().getHostConfig().getPortBindings();
200+
Set<Port> ports = new LinkedHashSet<>();
201+
for (Map.Entry<ExposedPort, Ports.Binding[]> binding : hostPortBindings.getBindings().entrySet()) {
202+
for (Ports.Binding portBinding : binding.getValue()) {
203+
String hostPortSpec = portBinding.getHostPortSpec();
204+
if(StringUtils.isNotEmpty(hostPortSpec)) {
205+
InternetProtocol internetProtocol = InternetProtocol.fromDockerNotation(binding.getKey().getProtocol().toString());
206+
ports.add(Port.of(Integer.parseInt(hostPortSpec), internetProtocol));
207+
}
208+
}
209+
}
210+
return ports;
211+
}
212+
188213

189214
/**
190215
* @return all log output from the container from start until the current instant (both stdout and stderr)

core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java

+22-10
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public class DockerComposeContainer<SELF extends DockerComposeContainer<SELF>> e
8686
private String project;
8787

8888
private final AtomicInteger nextAmbassadorPort = new AtomicInteger(2000);
89-
private final Map<String, Map<Integer, Integer>> ambassadorPortMappings = new ConcurrentHashMap<>();
89+
private final Map<String, Map<Port, Integer>> ambassadorPortMappings = new ConcurrentHashMap<>();
9090
private final Map<String, ComposeServiceWaitStrategyTarget> serviceInstanceMap = new ConcurrentHashMap<>();
9191
private final Map<String, WaitAllStrategy> waitStrategyMap = new ConcurrentHashMap<>();
9292
private final SocatContainer ambassadorContainer = new SocatContainer();
@@ -252,8 +252,11 @@ private void waitUntilServiceStarted() {
252252

253253
private void createServiceInstance(Container container) {
254254
String serviceName = getServiceNameFromContainer(container);
255-
final ComposeServiceWaitStrategyTarget containerInstance = new ComposeServiceWaitStrategyTarget(container,
256-
ambassadorContainer, ambassadorPortMappings.getOrDefault(serviceName, new HashMap<>()));
255+
final ComposeServiceWaitStrategyTarget containerInstance = new ComposeServiceWaitStrategyTarget(
256+
container,
257+
ambassadorContainer,
258+
ambassadorPortMappings.getOrDefault(serviceName, new HashMap<>())
259+
);
257260

258261
String containerId = containerInstance.getContainerId();
259262
if (tailChildContainers) {
@@ -348,7 +351,7 @@ public DockerComposeContainer withExposedService(String serviceName, int instanc
348351
return withExposedService(serviceName + "_" + instance, servicePort, waitStrategy);
349352
}
350353

351-
public SELF withExposedService(String serviceName, int servicePort, @NonNull WaitStrategy waitStrategy) {
354+
public SELF withExposedService(String serviceName, int servicePort, InternetProtocol internetProtocol,@NonNull WaitStrategy waitStrategy) {
352355

353356
String serviceInstanceName = getServiceInstanceName(serviceName);
354357

@@ -368,13 +371,17 @@ public SELF withExposedService(String serviceName, int servicePort, @NonNull Wai
368371

369372
// Ambassador container will be started together after docker compose has started
370373
int ambassadorPort = nextAmbassadorPort.getAndIncrement();
371-
ambassadorPortMappings.computeIfAbsent(serviceInstanceName, __ -> new ConcurrentHashMap<>()).put(servicePort, ambassadorPort);
372-
ambassadorContainer.withTarget(ambassadorPort, serviceInstanceName, servicePort);
374+
ambassadorPortMappings.computeIfAbsent(serviceInstanceName, __ -> new ConcurrentHashMap<>()).put(Port.of(servicePort, internetProtocol), ambassadorPort);
375+
ambassadorContainer.withTarget(ambassadorPort, serviceInstanceName, servicePort, internetProtocol);
373376
ambassadorContainer.addLink(new FutureContainer(this.project + "_" + serviceInstanceName), serviceInstanceName);
374377
addWaitStrategy(serviceInstanceName, waitStrategy);
375378
return self();
376379
}
377380

381+
public SELF withExposedService(String serviceName, int servicePort, @NonNull WaitStrategy waitStrategy) {
382+
return withExposedService(serviceName, servicePort, InternetProtocol.TCP, waitStrategy);
383+
}
384+
378385
private String getServiceInstanceName(String serviceName) {
379386
String serviceInstanceName = serviceName;
380387
if (!serviceInstanceName.matches(".*_[0-9]+")) {
@@ -433,14 +440,19 @@ public String getServiceHost(String serviceName, Integer servicePort) {
433440
* @return a port that can be used for accessing the service container.
434441
*/
435442
public Integer getServicePort(String serviceName, Integer servicePort) {
436-
Map<Integer, Integer> portMap = ambassadorPortMappings.get(getServiceInstanceName(serviceName));
443+
return getServicePort(serviceName, servicePort, InternetProtocol.TCP);
444+
}
445+
446+
public Integer getServicePort(String serviceName, Integer servicePort, InternetProtocol internetProtocol) {
447+
Map<Port, Integer> portMap = ambassadorPortMappings.get(getServiceInstanceName(serviceName));
437448

438449
if (portMap == null) {
439450
throw new IllegalArgumentException("Could not get a port for '" + serviceName + "'. " +
440-
"Testcontainers does not have an exposed port configured for '" + serviceName + "'. " +
441-
"To fix, please ensure that the service '" + serviceName + "' has ports exposed using .withExposedService(...)");
451+
"Testcontainers does not have an exposed port configured for '" + serviceName + "'. " +
452+
"To fix, please ensure that the service '" + serviceName + "' has ports exposed using .withExposedService(...)");
442453
} else {
443-
return ambassadorContainer.getMappedPort(portMap.get(servicePort));
454+
Port svcPort = Port.of(servicePort, internetProtocol);
455+
return ambassadorContainer.getMappedPort(portMap.get(svcPort), internetProtocol);
444456
}
445457
}
446458

0 commit comments

Comments
 (0)