diff --git a/.github/workflows/sdk_build.yml b/.github/workflows/sdk_build.yml index 433a0735a..cdf2b241e 100644 --- a/.github/workflows/sdk_build.yml +++ b/.github/workflows/sdk_build.yml @@ -208,6 +208,7 @@ jobs: name: ${{ matrix.prefix }}-${{ matrix['dapr-runtime-versions'].version }}-${{ matrix.projectName }} needs: [ compute-integration-matrix ] runs-on: ${{ matrix.os }} + timeout-minutes: 30 strategy: fail-fast: false matrix: ${{ fromJson(needs.compute-integration-matrix.outputs.matrix) }} diff --git a/src/Dapr.Testcontainers/Containers/Dapr/DaprPlacementContainer.cs b/src/Dapr.Testcontainers/Containers/Dapr/DaprPlacementContainer.cs index 8be913db2..ae568a914 100644 --- a/src/Dapr.Testcontainers/Containers/Dapr/DaprPlacementContainer.cs +++ b/src/Dapr.Testcontainers/Containers/Dapr/DaprPlacementContainer.cs @@ -12,11 +12,14 @@ // ------------------------------------------------------------------------ using System; +using System.Net; using System.Threading; using System.Threading.Tasks; using Dapr.Testcontainers.Common; using Dapr.Testcontainers.Common.Options; +using Docker.DotNet.Models; using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Configurations; using DotNet.Testcontainers.Containers; using DotNet.Testcontainers.Networks; @@ -44,9 +47,13 @@ public sealed class DaprPlacementContainer : IAsyncStartable /// public int ExternalPort { get; private set; } /// - /// THe contains' internal port. + /// The container's internal port. /// public const int InternalPort = 50006; + /// + /// The container's internal health port. + /// + private const int HealthPort = 8080; /// /// Initializes a new instance of the . @@ -59,14 +66,24 @@ public DaprPlacementContainer(DaprRuntimeOptions options, INetwork network, stri _logAttachment = ContainerLogAttachment.TryCreate(logDirectory, "placement", _containerName); //Placement service runs via port 50006 - var containerBuilder = new ContainerBuilder() - .WithImage(options.PlacementImageTag) - .WithName(_containerName) + var containerBuilder = new ContainerBuilder() + .WithImage(options.PlacementImageTag) + .WithName(_containerName) .WithNetwork(network) - .WithCommand("./placement", "-port", InternalPort.ToString()) - .WithPortBinding(InternalPort, assignRandomHostPort: true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("placement server leadership acquired")) - ; + .WithCommand("./placement", "-port", InternalPort.ToString()) + .WithPortBinding(InternalPort, assignRandomHostPort: true) + .WithPortBinding(HealthPort, assignRandomHostPort: true) + .WithWaitStrategy(Wait.ForUnixContainer() + .UntilHttpRequestIsSucceeded(endpoint => + endpoint + .ForPort(HealthPort) + .ForPath("/healthz") + .ForStatusCodeMatching(code => (int)code >= 200 && (int)code < 300), + mod => + mod + .WithTimeout(TimeSpan.FromMinutes(2)) + .WithInterval(TimeSpan.FromSeconds(5)) + .WithMode(WaitStrategyMode.Running))); if (_logAttachment is not null) { diff --git a/src/Dapr.Testcontainers/Containers/Dapr/DaprSchedulerContainer.cs b/src/Dapr.Testcontainers/Containers/Dapr/DaprSchedulerContainer.cs index bb81003ec..c0499e9c9 100644 --- a/src/Dapr.Testcontainers/Containers/Dapr/DaprSchedulerContainer.cs +++ b/src/Dapr.Testcontainers/Containers/Dapr/DaprSchedulerContainer.cs @@ -13,6 +13,8 @@ using System; using System.Linq; +using System.Net; +using System.Text; using System.Threading; using System.Threading.Tasks; using Dapr.Testcontainers.Common; @@ -52,6 +54,10 @@ public sealed class DaprSchedulerContainer : IAsyncStartable /// The container's internal port. /// public const int InternalPort = 51005; + /// + /// The container's internal health port. + /// + private const int HealthPort = 8080; /// /// Creates a new instance of a . @@ -70,17 +76,27 @@ public DaprSchedulerContainer(DaprRuntimeOptions options, INetwork network, stri ]; _testDirectory = TestDirectoryManager.CreateTestDirectory("scheduler"); - + var containerBuilder = new ContainerBuilder() .WithImage(options.SchedulerImageTag) - .WithName(_containerName) + .WithName(_containerName) .WithNetwork(network) .WithCommand(cmd.ToArray()) - .WithPortBinding(InternalPort, assignRandomHostPort: true) + .WithPortBinding(InternalPort, assignRandomHostPort: true) + .WithPortBinding(HealthPort, assignRandomHostPort: true) // Allows probes to reach healthz // Mount an anonymous volume to /data to ensure the scheduler has write permissions .WithBindMount(_testDirectory, containerDataDir, AccessMode.ReadWrite) - .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("api is ready")) - ; + .WithWaitStrategy(Wait.ForUnixContainer() + .UntilHttpRequestIsSucceeded(endpoint => + endpoint + .ForPort(HealthPort) + .ForPath("/healthz") + .ForStatusCodeMatching(code => (int)code >= 200 && (int)code < 300), + mod => + mod + .WithTimeout(TimeSpan.FromMinutes(2)) + .WithInterval(TimeSpan.FromSeconds(5)) + .WithMode(WaitStrategyMode.Running))); if (_logAttachment is not null) { diff --git a/src/Dapr.Testcontainers/Containers/Dapr/DaprdContainer.cs b/src/Dapr.Testcontainers/Containers/Dapr/DaprdContainer.cs index 77018531b..b5965cb23 100644 --- a/src/Dapr.Testcontainers/Containers/Dapr/DaprdContainer.cs +++ b/src/Dapr.Testcontainers/Containers/Dapr/DaprdContainer.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; +using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; @@ -33,9 +34,10 @@ public sealed class DaprdContainer : IAsyncStartable { private const int InternalHttpPort = 3500; private const int InternalGrpcPort = 50001; + private const int InternalHealthPort = 8080; private readonly IContainer _container; private readonly ContainerLogAttachment? _logAttachment; - private string _containerName = $"dapr-{Guid.NewGuid():N}"; + private readonly string _containerName = $"dapr-{Guid.NewGuid():N}"; /// /// The internal network alias/name of the container. @@ -123,57 +125,74 @@ public DaprdContainer( cmd.Add("-scheduler-host-address"); cmd.Add(""); } - - var containerBuilder = new ContainerBuilder() - .WithImage(options.RuntimeImageTag) - .WithName(_containerName) + + var containerBuilder = new ContainerBuilder() + .WithImage(options.RuntimeImageTag) + .WithName(_containerName) .WithLogger(ConsoleLogger.Instance) - .WithCommand(cmd.ToArray()) + .WithCommand(cmd.ToArray()) .WithNetwork(network) .WithExtraHost(ContainerHostAlias, "host-gateway") - .WithBindMount(componentsHostFolder, componentsPath, AccessMode.ReadOnly) - .WithWaitStrategy(Wait.ForUnixContainer() + .WithBindMount(componentsHostFolder, componentsPath, AccessMode.ReadOnly) + .WithWaitStrategy(Wait.ForUnixContainer() .UntilMessageIsLogged("Internal gRPC server is running")); - //.UntilMessageIsLogged(@"^dapr initialized. Status: Running. Init Elapsed ")) + // .UntilHttpRequestIsSucceeded(endpoint => + // endpoint + // .ForPort(InternalHttpPort) + // .ForPath("/healthz") + // .ForStatusCodeMatching(code => (int)code >= 200 && (int)code < 300), + // mod => + // mod + // .WithTimeout(TimeSpan.FromMinutes(2)) + // .WithInterval(TimeSpan.FromSeconds(5)) + // .WithMode(WaitStrategyMode.Running))); if (_logAttachment is not null) { containerBuilder = containerBuilder.WithOutputConsumer(_logAttachment.OutputConsumer); } - containerBuilder = daprHttpPort is not null ? containerBuilder.WithPortBinding(containerPort: InternalHttpPort, hostPort: daprHttpPort.Value) : containerBuilder.WithPortBinding(port: InternalHttpPort, assignRandomHostPort: true); - containerBuilder = daprGrpcPort is not null ? containerBuilder.WithPortBinding(containerPort: InternalGrpcPort, hostPort: daprGrpcPort.Value) : containerBuilder.WithPortBinding(port: InternalGrpcPort, assignRandomHostPort: true); + containerBuilder = daprHttpPort is not null ? containerBuilder.WithPortBinding(containerPort: InternalHttpPort, hostPort: daprHttpPort.Value) : containerBuilder.WithPortBinding(port: InternalHttpPort, assignRandomHostPort: true); + containerBuilder = daprGrpcPort is not null ? containerBuilder.WithPortBinding(containerPort: InternalGrpcPort, hostPort: daprGrpcPort.Value) : containerBuilder.WithPortBinding(port: InternalGrpcPort, assignRandomHostPort: true); - _container = containerBuilder.Build(); + _container = containerBuilder.Build(); } /// public async Task StartAsync(CancellationToken cancellationToken = default) { - await _container.StartAsync(cancellationToken); + try + { + await _container.StartAsync(cancellationToken); - var mappedHttpPort = _container.GetMappedPublicPort(InternalHttpPort); - var mappedGrpcPort = _container.GetMappedPublicPort(InternalGrpcPort); + var mappedHttpPort = _container.GetMappedPublicPort(InternalHttpPort); + var mappedGrpcPort = _container.GetMappedPublicPort(InternalGrpcPort); - if (_requestedHttpPort is not null && mappedHttpPort != _requestedHttpPort.Value) - { - throw new InvalidOperationException( - $"Dapr HTTP port mapping mismatch. Requested {_requestedHttpPort.Value}, but Docker mapped {mappedHttpPort}"); - } + if (_requestedHttpPort is not null && mappedHttpPort != _requestedHttpPort.Value) + { + throw new InvalidOperationException( + $"Dapr HTTP port mapping mismatch. Requested {_requestedHttpPort.Value}, but Docker mapped {mappedHttpPort}"); + } - if (_requestedGrpcPort is not null && mappedGrpcPort != _requestedGrpcPort.Value) - { - throw new InvalidOperationException( - $"Dapr gRPC port mapping mismatch. Requested {_requestedGrpcPort.Value}, but Docker mapped {mappedGrpcPort}"); - } + if (_requestedGrpcPort is not null && mappedGrpcPort != _requestedGrpcPort.Value) + { + throw new InvalidOperationException( + $"Dapr gRPC port mapping mismatch. Requested {_requestedGrpcPort.Value}, but Docker mapped {mappedGrpcPort}"); + } - HttpPort = mappedHttpPort; - GrpcPort = mappedGrpcPort; + HttpPort = mappedHttpPort; + GrpcPort = mappedGrpcPort; - // The container log wait strategy can fire before the host port is actually accepting connections - // (especially on Windows). Ensure the ports are reachable from the test process. - await WaitForTcpPortAsync("127.0.0.1", HttpPort, TimeSpan.FromSeconds(30), cancellationToken); - await WaitForTcpPortAsync("127.0.0.1", GrpcPort, TimeSpan.FromSeconds(30), cancellationToken); + // The container log wait strategy can fire before the host port is actually accepting connections + // (especially on Windows). Ensure the ports are reachable from the test process. + await WaitForTcpPortAsync("127.0.0.1", HttpPort, TimeSpan.FromSeconds(30), cancellationToken); + await WaitForTcpPortAsync("127.0.0.1", GrpcPort, TimeSpan.FromSeconds(30), cancellationToken); + } + catch (Exception ex) + { + var msg = ex.Message; + throw; + } } private static async Task WaitForTcpPortAsync( diff --git a/src/Dapr.Testcontainers/Harnesses/BaseHarness.cs b/src/Dapr.Testcontainers/Harnesses/BaseHarness.cs index 93b6383af..240d6e463 100644 --- a/src/Dapr.Testcontainers/Harnesses/BaseHarness.cs +++ b/src/Dapr.Testcontainers/Harnesses/BaseHarness.cs @@ -229,7 +229,7 @@ DaprSchedulerExternalPort is null || DaprSchedulerAlias is null await _daprd!.StartAsync(cancellationToken); _sidecarPortsReady.TrySetResult(); }, cancellationToken); - + Task? appTask = null; if (startApp is not null) {