From 2c7c3d1d1cd0c852c5e7ec6ab80129daf7bae351 Mon Sep 17 00:00:00 2001 From: buckwx Date: Mon, 22 Jul 2024 16:31:19 -0500 Subject: [PATCH 1/7] bug: fix UDP port mapping #1218 --- .../ContainerConfigurationConverter.cs | 2 +- .../Containers/DockerContainer.cs | 4 +- .../Unix/TestcontainersContainerTest.cs | 56 +++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/Testcontainers/Clients/ContainerConfigurationConverter.cs b/src/Testcontainers/Clients/ContainerConfigurationConverter.cs index 84f7dfb7c..4d3ec077c 100644 --- a/src/Testcontainers/Clients/ContainerConfigurationConverter.cs +++ b/src/Testcontainers/Clients/ContainerConfigurationConverter.cs @@ -48,7 +48,7 @@ public ContainerConfigurationConverter(IContainerConfiguration configuration) public IDictionary Networks { get; } - private static string GetQualifiedPort(string containerPort) + public static string GetQualifiedPort(string containerPort) { return Array.Exists(new[] { UdpPortSuffix, TcpPortSuffix, SctpPortSuffix }, portSuffix => containerPort.EndsWith(portSuffix, StringComparison.OrdinalIgnoreCase)) ? containerPort.ToLowerInvariant() : containerPort + TcpPortSuffix; } diff --git a/src/Testcontainers/Containers/DockerContainer.cs b/src/Testcontainers/Containers/DockerContainer.cs index 3a6fbdffb..08d5c2fdd 100644 --- a/src/Testcontainers/Containers/DockerContainer.cs +++ b/src/Testcontainers/Containers/DockerContainer.cs @@ -236,7 +236,9 @@ public ushort GetMappedPublicPort(string containerPort) { ThrowIfResourceNotFound(); - if (_container.NetworkSettings.Ports.TryGetValue($"{containerPort}/tcp", out var portBindings) && ushort.TryParse(portBindings[0].HostPort, out var publicPort)) + containerPort = ContainerConfigurationConverter.GetQualifiedPort(containerPort); + + if (_container.NetworkSettings.Ports.TryGetValue(containerPort, out var portBindings) && ushort.TryParse(portBindings[0].HostPort, out var publicPort)) { return publicPort; } diff --git a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs index 2d8bdd473..68eb84b16 100644 --- a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs +++ b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs @@ -209,6 +209,62 @@ await container.StartAsync() Assert.Throws(() => container.GetMappedPublicPort(443)); } + [Fact] + public async Task RandomUdpPortBinding() + { + // Given + const string containerUdpQualifiedPort = "4001/udp"; + const ushort containerUdpPort = 4001; + + // Simple Python-based echo server with UDP + // https://github.com/vhiribarren/docker-echo-server + await using var container = new ContainerBuilder() + .WithImage("vhiribarren/echo-server") + .WithPortBinding(containerUdpQualifiedPort, true) + .Build(); + + // When + await container.StartAsync() + .ConfigureAwait(true); + + // Then + var localMappedPort = container.GetMappedPublicPort(containerUdpQualifiedPort); + Assert.NotEqual(containerUdpPort, localMappedPort); + + var message = Guid.NewGuid().ToString("D"); + var response = CallUdpEchoServer(container.Hostname, localMappedPort, message); + Assert.Equal(message, response); + } + + private string CallUdpEchoServer(string serverIP, int serverPort, string message) + { + // Create a UDP client + UdpClient client = new UdpClient(serverPort); + + try + { + // Connect to the server + client.Connect(serverIP, serverPort); + + // Send data to the server + byte[] sendData = Encoding.ASCII.GetBytes(message); + client.Send(sendData, sendData.Length); + + // Receive the response from the server + IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); + byte[] receiveData = client.Receive(ref remoteEndPoint); + string receivedMessage = Encoding.ASCII.GetString(receiveData); + + return receivedMessage; + } + finally + { + // Close the client + client.Close(); + } + return string.Empty; + } + [Fact] public async Task BindMountAndCommand() { From 160bcb94d46456c24100678d67240435094d7eab Mon Sep 17 00:00:00 2001 From: buckwx Date: Tue, 23 Jul 2024 08:27:48 -0500 Subject: [PATCH 2/7] fix test on Linux --- .../Unit/Containers/Unix/TestcontainersContainerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs index 68eb84b16..08f699202 100644 --- a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs +++ b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs @@ -232,7 +232,7 @@ await container.StartAsync() Assert.NotEqual(containerUdpPort, localMappedPort); var message = Guid.NewGuid().ToString("D"); - var response = CallUdpEchoServer(container.Hostname, localMappedPort, message); + var response = CallUdpEchoServer("127.0.0.1", localMappedPort, message); Assert.Equal(message, response); } From cb3fc4bac2eda6f22b9591a5b627196b5c184e4e Mon Sep 17 00:00:00 2001 From: buckwx Date: Tue, 23 Jul 2024 10:36:43 -0500 Subject: [PATCH 3/7] fix? --- .github/workflows/cicd.yml | 3 ++- .../Unit/Containers/Unix/TestcontainersContainerTest.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 29deb471b..744ac4d2a 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -76,7 +76,8 @@ jobs: run: dotnet cake --target=Build - name: Run Tests - run: dotnet cake --target=Tests --test-filter=${{ startsWith(matrix.os, 'ubuntu') && 'FullyQualifiedName~Testcontainers' || 'DockerPlatform=Windows' }} + # run: dotnet cake --target=Tests --test-filter=${{ startsWith(matrix.os, 'ubuntu') && 'FullyQualifiedName~Testcontainers' || 'DockerPlatform=Windows' }} + run: dotnet cake --target=Tests --test-filter=FullyQualifiedName~RandomUdpPortBinding # The Test Reporter GH Action is not compatible with the recent # actions/upload-artifact@v4 updates: https://github.com/dorny/test-reporter/issues/363. diff --git a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs index 08f699202..e9b5ebda5 100644 --- a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs +++ b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs @@ -233,7 +233,7 @@ await container.StartAsync() var message = Guid.NewGuid().ToString("D"); var response = CallUdpEchoServer("127.0.0.1", localMappedPort, message); - Assert.Equal(message, response); + // Assert.Equal(message, response); } private string CallUdpEchoServer(string serverIP, int serverPort, string message) From ec2ce26dc052f3207e220a51b5fc91a3839bbcee Mon Sep 17 00:00:00 2001 From: buckwx Date: Tue, 23 Jul 2024 11:44:10 -0500 Subject: [PATCH 4/7] new port mapping --- .../Unit/Containers/Unix/TestcontainersContainerTest.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs index e9b5ebda5..a346b4397 100644 --- a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs +++ b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs @@ -219,6 +219,7 @@ public async Task RandomUdpPortBinding() // Simple Python-based echo server with UDP // https://github.com/vhiribarren/docker-echo-server await using var container = new ContainerBuilder() + // .WithImage("docker-hub.artifactory.mocca.yunextraffic.cloud/vhiribarren/echo-server") .WithImage("vhiribarren/echo-server") .WithPortBinding(containerUdpQualifiedPort, true) .Build(); @@ -233,13 +234,16 @@ await container.StartAsync() var message = Guid.NewGuid().ToString("D"); var response = CallUdpEchoServer("127.0.0.1", localMappedPort, message); - // Assert.Equal(message, response); + // var response = CallUdpEchoServer("172.19.212.239", localMappedPort, message); + // response will look like this: "UDP: fb15aa0b4d19 received: from ('', ) + Assert.Contains(message, response); } private string CallUdpEchoServer(string serverIP, int serverPort, string message) { // Create a UDP client - UdpClient client = new UdpClient(serverPort); + // UdpClient client = new UdpClient(0, AddressFamily.InterNetwork); + UdpClient client = new UdpClient(); try { From a94bca293c2cb0bf66fb8936485b88d0209a1384 Mon Sep 17 00:00:00 2001 From: buckwx Date: Tue, 23 Jul 2024 12:45:02 -0500 Subject: [PATCH 5/7] just check port mapping --- .github/workflows/cicd.yml | 2 +- .../Unix/TestcontainersContainerTest.cs | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 744ac4d2a..7f6f1eba5 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -77,7 +77,7 @@ jobs: - name: Run Tests # run: dotnet cake --target=Tests --test-filter=${{ startsWith(matrix.os, 'ubuntu') && 'FullyQualifiedName~Testcontainers' || 'DockerPlatform=Windows' }} - run: dotnet cake --target=Tests --test-filter=FullyQualifiedName~RandomUdpPortBinding + run: dotnet cake --target=Tests --test-filter='FullyQualifiedName~RandomUdpPortBinding' # The Test Reporter GH Action is not compatible with the recent # actions/upload-artifact@v4 updates: https://github.com/dorny/test-reporter/issues/363. diff --git a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs index a346b4397..bf62d2af7 100644 --- a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs +++ b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs @@ -211,6 +211,26 @@ await container.StartAsync() [Fact] public async Task RandomUdpPortBinding() + { + // Given + const ushort containerPort = 161; + + await using var container = new ContainerBuilder() + .WithImage(CommonImages.Alpine) + .WithEntrypoint(CommonCommands.SleepInfinity) + .WithPortBinding("161/udp", true) + .Build(); + + // When + await container.StartAsync() + .ConfigureAwait(true); + + // Then + Assert.NotEqual(containerPort, container.GetMappedPublicPort("161/udp")); + } + + [Fact(Skip = "UDP server doesn't work")] + public async Task RandomUdpPortBindingUdpServer() { // Given const string containerUdpQualifiedPort = "4001/udp"; @@ -266,7 +286,6 @@ private string CallUdpEchoServer(string serverIP, int serverPort, string message // Close the client client.Close(); } - return string.Empty; } [Fact] From 1771e16ea5e27ea950a796dd3527eb2b5d80e280 Mon Sep 17 00:00:00 2001 From: buckwx Date: Tue, 23 Jul 2024 13:18:08 -0500 Subject: [PATCH 6/7] fixed RandomUdpPortBinding test --- .github/workflows/cicd.yml | 3 +- .../Unix/TestcontainersContainerTest.cs | 59 ------------------- 2 files changed, 1 insertion(+), 61 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 7f6f1eba5..29deb471b 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -76,8 +76,7 @@ jobs: run: dotnet cake --target=Build - name: Run Tests - # run: dotnet cake --target=Tests --test-filter=${{ startsWith(matrix.os, 'ubuntu') && 'FullyQualifiedName~Testcontainers' || 'DockerPlatform=Windows' }} - run: dotnet cake --target=Tests --test-filter='FullyQualifiedName~RandomUdpPortBinding' + run: dotnet cake --target=Tests --test-filter=${{ startsWith(matrix.os, 'ubuntu') && 'FullyQualifiedName~Testcontainers' || 'DockerPlatform=Windows' }} # The Test Reporter GH Action is not compatible with the recent # actions/upload-artifact@v4 updates: https://github.com/dorny/test-reporter/issues/363. diff --git a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs index bf62d2af7..10535b658 100644 --- a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs +++ b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs @@ -229,65 +229,6 @@ await container.StartAsync() Assert.NotEqual(containerPort, container.GetMappedPublicPort("161/udp")); } - [Fact(Skip = "UDP server doesn't work")] - public async Task RandomUdpPortBindingUdpServer() - { - // Given - const string containerUdpQualifiedPort = "4001/udp"; - const ushort containerUdpPort = 4001; - - // Simple Python-based echo server with UDP - // https://github.com/vhiribarren/docker-echo-server - await using var container = new ContainerBuilder() - // .WithImage("docker-hub.artifactory.mocca.yunextraffic.cloud/vhiribarren/echo-server") - .WithImage("vhiribarren/echo-server") - .WithPortBinding(containerUdpQualifiedPort, true) - .Build(); - - // When - await container.StartAsync() - .ConfigureAwait(true); - - // Then - var localMappedPort = container.GetMappedPublicPort(containerUdpQualifiedPort); - Assert.NotEqual(containerUdpPort, localMappedPort); - - var message = Guid.NewGuid().ToString("D"); - var response = CallUdpEchoServer("127.0.0.1", localMappedPort, message); - // var response = CallUdpEchoServer("172.19.212.239", localMappedPort, message); - // response will look like this: "UDP: fb15aa0b4d19 received: from ('', ) - Assert.Contains(message, response); - } - - private string CallUdpEchoServer(string serverIP, int serverPort, string message) - { - // Create a UDP client - // UdpClient client = new UdpClient(0, AddressFamily.InterNetwork); - UdpClient client = new UdpClient(); - - try - { - // Connect to the server - client.Connect(serverIP, serverPort); - - // Send data to the server - byte[] sendData = Encoding.ASCII.GetBytes(message); - client.Send(sendData, sendData.Length); - - // Receive the response from the server - IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - byte[] receiveData = client.Receive(ref remoteEndPoint); - string receivedMessage = Encoding.ASCII.GetString(receiveData); - - return receivedMessage; - } - finally - { - // Close the client - client.Close(); - } - } - [Fact] public async Task BindMountAndCommand() { From 86018dacd286f599579e0c4c3706e9a140fc86e8 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Thu, 25 Jul 2024 09:51:36 +0200 Subject: [PATCH 7/7] docs: Add docs about getting port for protocol --- .../Containers/DockerContainer.cs | 6 ++-- src/Testcontainers/Containers/IContainer.cs | 6 ++++ .../Unix/TestcontainersContainerTest.cs | 36 ++++++++++--------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/Testcontainers/Containers/DockerContainer.cs b/src/Testcontainers/Containers/DockerContainer.cs index 08d5c2fdd..8cd148758 100644 --- a/src/Testcontainers/Containers/DockerContainer.cs +++ b/src/Testcontainers/Containers/DockerContainer.cs @@ -236,15 +236,15 @@ public ushort GetMappedPublicPort(string containerPort) { ThrowIfResourceNotFound(); - containerPort = ContainerConfigurationConverter.GetQualifiedPort(containerPort); + var qualifiedContainerPort = ContainerConfigurationConverter.GetQualifiedPort(containerPort); - if (_container.NetworkSettings.Ports.TryGetValue(containerPort, out var portBindings) && ushort.TryParse(portBindings[0].HostPort, out var publicPort)) + if (_container.NetworkSettings.Ports.TryGetValue(qualifiedContainerPort, out var portBindings) && ushort.TryParse(portBindings[0].HostPort, out var publicPort)) { return publicPort; } else { - throw new InvalidOperationException($"Exposed port {containerPort} is not mapped."); + throw new InvalidOperationException($"Exposed port {qualifiedContainerPort} is not mapped."); } } diff --git a/src/Testcontainers/Containers/IContainer.cs b/src/Testcontainers/Containers/IContainer.cs index d041f7822..7d222c0de 100644 --- a/src/Testcontainers/Containers/IContainer.cs +++ b/src/Testcontainers/Containers/IContainer.cs @@ -132,6 +132,9 @@ public interface IContainer : IAsyncDisposable /// /// Resolves the public assigned host port. /// + /// + /// Resolves the public assigned host port for the TCP protocol. To resolve a specific protocol, use . + /// /// The container port. /// Returns the public assigned host port. /// Container has not been created. @@ -140,6 +143,9 @@ public interface IContainer : IAsyncDisposable /// /// Resolves the public assigned host port. /// + /// + /// Append /tcp|udp|sctp to to resolve the public assigned host port for a specific protocol e.g. "53/udp". + /// /// The container port. /// Returns the public assigned host port. /// Container has not been created. diff --git a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs index 10535b658..bbcb8fea2 100644 --- a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs +++ b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs @@ -171,16 +171,17 @@ await container.StartAsync() } [Fact] - public async Task RandomPortBinding() + public async Task RandomUdpPortBinding() { // Given - const ushort containerPort = 80; + const ushort containerPort = 53; + + const string qualifiedContainerPort = "53/udp"; await using var container = new ContainerBuilder() - .WithImage(CommonImages.Nginx) - .WithPortBinding(containerPort, true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request => - request.ForPort(containerPort))) + .WithImage(CommonImages.Alpine) + .WithEntrypoint(CommonCommands.SleepInfinity) + .WithPortBinding(qualifiedContainerPort, true) .Build(); // When @@ -188,17 +189,20 @@ await container.StartAsync() .ConfigureAwait(true); // Then - Assert.NotEqual(containerPort, container.GetMappedPublicPort(containerPort)); + Assert.NotEqual(containerPort, container.GetMappedPublicPort(qualifiedContainerPort)); } [Fact] - public async Task UnboundPortBindingThrowsException() + public async Task RandomTcpPortBinding() { // Given + const ushort containerPort = 80; + await using var container = new ContainerBuilder() - .WithImage(CommonImages.Alpine) - .WithEntrypoint(CommonCommands.SleepInfinity) - .WithPortBinding(80, true) + .WithImage(CommonImages.Nginx) + .WithPortBinding(containerPort, true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request => + request.ForPort(containerPort))) .Build(); // When @@ -206,19 +210,17 @@ await container.StartAsync() .ConfigureAwait(true); // Then - Assert.Throws(() => container.GetMappedPublicPort(443)); + Assert.NotEqual(containerPort, container.GetMappedPublicPort(containerPort)); } [Fact] - public async Task RandomUdpPortBinding() + public async Task UnboundPortBindingThrowsException() { // Given - const ushort containerPort = 161; - await using var container = new ContainerBuilder() .WithImage(CommonImages.Alpine) .WithEntrypoint(CommonCommands.SleepInfinity) - .WithPortBinding("161/udp", true) + .WithPortBinding(80, true) .Build(); // When @@ -226,7 +228,7 @@ await container.StartAsync() .ConfigureAwait(true); // Then - Assert.NotEqual(containerPort, container.GetMappedPublicPort("161/udp")); + Assert.Throws(() => container.GetMappedPublicPort(443)); } [Fact]