From 5e6821cbe3d9032f6dc692ebecce84fa8f79bee3 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 7 Jun 2024 06:29:20 +0800 Subject: [PATCH] Change functional tests to use dynamic ports (#2458) --- .../Balancer/BalancerHelpers.cs | 20 ++-- .../Balancer/ConnectionTests.cs | 37 +++--- .../Balancer/LeastUsedBalancerTests.cs | 14 +-- .../Balancer/PickFirstBalancerTests.cs | 57 ++++----- .../Balancer/RoundRobinBalancerTests.cs | 56 ++++----- test/FunctionalTests/FunctionalTestBase.cs | 15 ++- .../Infrastructure/GrpcTestFixture.cs | 101 ++++++++++++---- .../Infrastructure/InProcessTestServer.cs | 16 +-- .../Infrastructure/ServerRetryHelper.cs | 113 ++++++++++++++++++ 9 files changed, 306 insertions(+), 123 deletions(-) create mode 100644 test/FunctionalTests/Infrastructure/ServerRetryHelper.cs diff --git a/test/FunctionalTests/Balancer/BalancerHelpers.cs b/test/FunctionalTests/Balancer/BalancerHelpers.cs index 0a64757ed..9fc61edf1 100644 --- a/test/FunctionalTests/Balancer/BalancerHelpers.cs +++ b/test/FunctionalTests/Balancer/BalancerHelpers.cs @@ -48,18 +48,18 @@ namespace Grpc.AspNetCore.FunctionalTests.Balancer; internal static class BalancerHelpers { public static EndpointContext CreateGrpcEndpoint( - int port, UnaryServerMethod callHandler, string methodName, HttpProtocols? protocols = null, bool? isHttps = null, X509Certificate2? certificate = null, ILoggerFactory? loggerFactory = null, - Action? configureServer = null) + Action? configureServer = null, + int? explicitPort = null) where TRequest : class, IMessage, new() where TResponse : class, IMessage, new() { - var server = CreateServer(port, protocols, isHttps, certificate, loggerFactory, configureServer); + var server = CreateServer(protocols, isHttps, certificate, loggerFactory, configureServer, explicitPort); var method = server.DynamicGrpc.AddUnaryMethod(callHandler, methodName); var url = server.GetUrl(isHttps.GetValueOrDefault(false) ? TestServerEndpointName.Http2WithTls : TestServerEndpointName.Http2); @@ -90,12 +90,12 @@ public void Dispose() } public static GrpcTestFixture CreateServer( - int port, HttpProtocols? protocols = null, bool? isHttps = null, X509Certificate2? certificate = null, ILoggerFactory? loggerFactory = null, - Action? configureServer = null) + Action? configureServer = null, + int? explicitPort = null) { var endpointName = isHttps.GetValueOrDefault(false) ? TestServerEndpointName.Http2WithTls : TestServerEndpointName.Http2; @@ -107,14 +107,10 @@ public static GrpcTestFixture CreateServer( services.AddSingleton(loggerFactory); } }, - (options, urls) => + (context, options, urls) => { configureServer?.Invoke(options); - - urls[endpointName] = isHttps.GetValueOrDefault(false) - ? $"https://127.0.0.1:{port}" - : $"http://127.0.0.1:{port}"; - options.ListenLocalhost(port, listenOptions => + options.Listen(IPAddress.Loopback, explicitPort ?? 0, listenOptions => { listenOptions.Protocols = protocols ?? HttpProtocols.Http2; @@ -131,6 +127,8 @@ public static GrpcTestFixture CreateServer( listenOptions.UseHttps(certificate); } } + + urls[endpointName] = IPEndpointInfoContainer.Create(listenOptions, isHttps.GetValueOrDefault(false)); }); }, endpointName); diff --git a/test/FunctionalTests/Balancer/ConnectionTests.cs b/test/FunctionalTests/Balancer/ConnectionTests.cs index 1d82786a2..72cb7dbdf 100644 --- a/test/FunctionalTests/Balancer/ConnectionTests.cs +++ b/test/FunctionalTests/Balancer/ConnectionTests.cs @@ -75,7 +75,7 @@ async Task UnaryMethod(HelloRequest request, ServerCallContext conte } // Arrange - var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + var endpoint = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); endpoint.Dispose(); var connectTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -123,7 +123,7 @@ async Task UnaryMethod(HelloRequest request, ServerCallContext conte } // Arrange - using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpoint = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); // Dispose endpoint so that channel pauses while attempting to connect to the port. endpoint.Dispose(); @@ -160,7 +160,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod), loggerFactory: LoggerFactory); + using var endpoint = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod), loggerFactory: LoggerFactory); var connectionIdleTimeout = TimeSpan.FromMilliseconds(milliseconds); var channel = await BalancerHelpers.CreateChannel( @@ -198,7 +198,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod), loggerFactory: LoggerFactory); + using var endpoint = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod), loggerFactory: LoggerFactory); var channel = await BalancerHelpers.CreateChannel( LoggerFactory, @@ -237,7 +237,6 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) // Arrange using var endpoint = BalancerHelpers.CreateGrpcEndpoint( - 50051, UnaryMethod, nameof(UnaryMethod), loggerFactory: LoggerFactory, @@ -290,8 +289,8 @@ async Task UnaryMethod(HelloRequest request, ServerCallContext conte } // Arrange - using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod), HttpProtocols.Http1AndHttp2, isHttps: true); - using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(50052, UnaryMethod, nameof(UnaryMethod), HttpProtocols.Http1AndHttp2, isHttps: true); + using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod), HttpProtocols.Http1AndHttp2, isHttps: true); + using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod), HttpProtocols.Http1AndHttp2, isHttps: true); var services = new ServiceCollection(); services.AddSingleton(new StaticResolverFactory(_ => new[] @@ -349,7 +348,7 @@ await TestHelpers.AssertIsTrueRetryAsync(() => }, "Wait for connections to start."); foreach (var t in activeStreams) { - Assert.AreEqual(new DnsEndPoint("127.0.0.1", 50051), t.EndPoint); + Assert.AreEqual(new DnsEndPoint("127.0.0.1", endpoint1.Address.Port), t.EndPoint); } // Act @@ -370,7 +369,7 @@ await TestHelpers.AssertIsTrueRetryAsync(() => activeStreams = transport.GetActiveStreams(); return activeStreams.Count == 11; }, "Wait for connections to start."); - Assert.AreEqual(new DnsEndPoint("127.0.0.1", 50051), activeStreams[activeStreams.Count - 1].EndPoint); + Assert.AreEqual(new DnsEndPoint("127.0.0.1", endpoint1.Address.Port), activeStreams[activeStreams.Count - 1].EndPoint); tcs.SetResult(null); @@ -406,11 +405,11 @@ await TestHelpers.AssertIsTrueRetryAsync(() => Logger.LogInformation($"Next call goes to fallback address."); var reply = await client.UnaryCall(new HelloRequest { Name = "Balancer" }).ResponseAsync.TimeoutAfter(TimeSpan.FromSeconds(20)); Assert.AreEqual("Balancer", reply.Message); - Assert.AreEqual("127.0.0.1:50052", host); + Assert.AreEqual($"127.0.0.1:{endpoint2.Address.Port}", host); activeStreams = transport.GetActiveStreams(); Assert.AreEqual(1, activeStreams.Count); - Assert.AreEqual(new DnsEndPoint("127.0.0.1", 50052), activeStreams[0].EndPoint); + Assert.AreEqual(new DnsEndPoint("127.0.0.1", endpoint2.Address.Port), activeStreams[0].EndPoint); } #if NET7_0_OR_GREATER @@ -436,8 +435,8 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) var cert = new X509Certificate2(certPath, "11111"); // Arrange - using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod), HttpProtocols.Http1AndHttp2, isHttps: true, certificate: cert); - using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(50052, UnaryMethod, nameof(UnaryMethod), HttpProtocols.Http1AndHttp2, isHttps: true, certificate: cert); + using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod), HttpProtocols.Http1AndHttp2, isHttps: true, certificate: cert); + using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod), HttpProtocols.Http1AndHttp2, isHttps: true, certificate: cert); var services = new ServiceCollection(); services.AddSingleton((ResolverFactory)new StaticResolverFactory(_ => (new[] @@ -491,13 +490,13 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) Assert.AreEqual("localhost", host); Assert.AreEqual(SslPolicyErrors.None, callbackPolicyErrors); Assert.AreEqual(IPAddress.Parse("127.0.0.1"), ipAddress); - Assert.IsTrue(port == 50051 || port == 50052); + Assert.IsTrue(port == endpoint1.Address.Port || port == endpoint2.Address.Port); ports.Add(port!.Value); } - Assert.IsTrue(ports.Contains(50051), "Has 50051"); - Assert.IsTrue(ports.Contains(50052), "Has 50052"); + Assert.IsTrue(ports.Contains(endpoint1.Address.Port), $"Has {endpoint1.Address.Port}"); + Assert.IsTrue(ports.Contains(endpoint2.Address.Port), $"Has {endpoint2.Address.Port}"); static BalancerAddress CreateAddress(Uri address, string hostOverride) { @@ -523,8 +522,8 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) metadata.Add("Authorization", $"Bearer TEST"); return Task.CompletedTask; }); - using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod), HttpProtocols.Http1AndHttp2, isHttps: true); - using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(50052, UnaryMethod, nameof(UnaryMethod), HttpProtocols.Http1AndHttp2, isHttps: true); + using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod), HttpProtocols.Http1AndHttp2, isHttps: true); + using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod), HttpProtocols.Http1AndHttp2, isHttps: true); var services = new ServiceCollection(); services.AddSingleton(new StaticResolverFactory(_ => new[] @@ -706,7 +705,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpoint = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var channel = await BalancerHelpers.CreateChannel( LoggerFactory, diff --git a/test/FunctionalTests/Balancer/LeastUsedBalancerTests.cs b/test/FunctionalTests/Balancer/LeastUsedBalancerTests.cs index a77db1ff7..da764d1f2 100644 --- a/test/FunctionalTests/Balancer/LeastUsedBalancerTests.cs +++ b/test/FunctionalTests/Balancer/LeastUsedBalancerTests.cs @@ -62,8 +62,8 @@ async Task UnaryMethod(HelloRequest request, ServerCallContext conte } // Arrange - using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); - using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(50052, UnaryMethod, nameof(UnaryMethod)); + using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); + using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var channel = await BalancerHelpers.CreateChannel(LoggerFactory, new LoadBalancingConfig("least_used"), new[] { endpoint1.Address, endpoint2.Address }, connect: true); @@ -79,34 +79,34 @@ await BalancerWaitHelpers.WaitForSubchannelsToBeReadyAsync( var reply = await client.UnaryCall(new HelloRequest { Name = "Balancer" }).ResponseAsync.DefaultTimeout(); // Assert Assert.AreEqual("Balancer", reply.Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpoint1.Address.Port}", host); // Act reply = await client.UnaryCall(new HelloRequest { Name = "Balancer" }).ResponseAsync.DefaultTimeout(); // Assert Assert.AreEqual("Balancer", reply.Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpoint1.Address.Port}", host); // Act var sp1 = syncPoint = new SyncPoint(runContinuationsAsynchronously: true); var pendingCall1 = client.UnaryCall(new HelloRequest { Name = "Balancer" }); // Assert await syncPoint.WaitForSyncPoint().DefaultTimeout(); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpoint1.Address.Port}", host); // Act var sp2 = syncPoint = new SyncPoint(runContinuationsAsynchronously: true); var pendingCall2 = client.UnaryCall(new HelloRequest { Name = "Balancer" }); // Assert await syncPoint.WaitForSyncPoint().DefaultTimeout(); - Assert.AreEqual("127.0.0.1:50052", host); + Assert.AreEqual($"127.0.0.1:{endpoint2.Address.Port}", host); // Act var sp3 = syncPoint = new SyncPoint(runContinuationsAsynchronously: true); var pendingCall3 = client.UnaryCall(new HelloRequest { Name = "Balancer" }); // Assert await syncPoint.WaitForSyncPoint().DefaultTimeout(); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpoint1.Address.Port}", host); sp1.Continue(); sp2.Continue(); diff --git a/test/FunctionalTests/Balancer/PickFirstBalancerTests.cs b/test/FunctionalTests/Balancer/PickFirstBalancerTests.cs index 438133060..23c542491 100644 --- a/test/FunctionalTests/Balancer/PickFirstBalancerTests.cs +++ b/test/FunctionalTests/Balancer/PickFirstBalancerTests.cs @@ -37,6 +37,7 @@ using Grpc.Net.Client.Configuration; using Grpc.Net.Client.Web; using Grpc.Tests.Shared; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Logging; using NUnit.Framework; @@ -78,7 +79,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpoint = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var connectCount = 0; var channel = await BalancerHelpers.CreateChannel(LoggerFactory, new PickFirstConfig(), new[] { endpoint.Address }, connectTimeout: TimeSpan.FromMilliseconds(200), socketConnect: @@ -117,7 +118,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpoint = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var channel = await BalancerHelpers.CreateChannel(LoggerFactory, new PickFirstConfig(), new[] { endpoint.Address }).DefaultTimeout(); var client = TestClientFactory.Create(channel, endpoint.Method); @@ -134,14 +135,14 @@ await ExceptionAssert.ThrowsAsync(() => client.UnaryCall( new CallOptions(cancellationToken: cts.Token)).ResponseAsync).DefaultTimeout(); // Restart endpoint. - using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod), explicitPort: endpoint.Address.Port); // Act var reply = await client.UnaryCall(new HelloRequest { Name = "Balancer" }, new CallOptions().WithWaitForReady(true)).ResponseAsync.DefaultTimeout(); // Assert Assert.AreEqual("Balancer", reply.Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpoint1.Address.Port}", host); } [Test] @@ -161,7 +162,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpoint = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var channel = await BalancerHelpers.CreateChannel(LoggerFactory, new PickFirstConfig(), new[] { endpoint.Address }).DefaultTimeout(); @@ -172,7 +173,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) // Assert Assert.AreEqual("Balancer", reply.Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpoint.Address.Port}", host); Logger.LogInformation("Ending " + endpoint.Address); endpoint.Dispose(); @@ -181,12 +182,12 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) await BalancerWaitHelpers.WaitForChannelStateAsync(Logger, channel, ConnectivityState.Idle).DefaultTimeout(); Logger.LogInformation("Restarting"); - using var endpointNew = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpointNew = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod), explicitPort: endpoint.Address.Port); reply = await client.UnaryCall(new HelloRequest { Name = "Balancer" }, new CallOptions().WithWaitForReady()).ResponseAsync.DefaultTimeout(); Assert.AreEqual("Balancer", reply.Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpointNew.Address.Port}", host); } [Test] @@ -206,7 +207,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpoint = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var channel = await BalancerHelpers.CreateChannel(LoggerFactory, new PickFirstConfig(), new[] { endpoint.Address }).DefaultTimeout(); @@ -217,7 +218,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) // Assert Assert.AreEqual("Balancer", reply.Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpoint.Address.Port}", host); Logger.LogInformation("Ending " + endpoint.Address); endpoint.Dispose(); @@ -266,8 +267,8 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); - using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(50052, UnaryMethod, nameof(UnaryMethod)); + using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); + using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var channel = await BalancerHelpers.CreateChannel(LoggerFactory, new PickFirstConfig(), new[] { endpoint1.Address, endpoint2.Address }).DefaultTimeout(); @@ -278,7 +279,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) // Assert Assert.AreEqual("Balancer", reply.Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpoint1.Address.Port}", host); Logger.LogInformation("Ending " + endpoint1.Address); endpoint1.Dispose(); @@ -301,7 +302,7 @@ await BalancerWaitHelpers.WaitForSubchannelsToBeReadyAsync(Logger, channel, expe reply = await client.UnaryCall(new HelloRequest { Name = "Balancer" }).ResponseAsync.DefaultTimeout(); Assert.AreEqual("Balancer", reply.Message); - Assert.AreEqual("127.0.0.1:50052", host); + Assert.AreEqual($"127.0.0.1:{endpoint2.Address.Port}", host); } [Test] @@ -321,8 +322,8 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); - using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(50052, UnaryMethod, nameof(UnaryMethod)); + using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); + using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var socketsHttpHandler = new SocketsHttpHandler { PooledConnectionIdleTimeout = TimeSpan.FromSeconds(1) }; var channel = await BalancerHelpers.CreateChannel(LoggerFactory, new PickFirstConfig(), new[] { endpoint1.Address, endpoint2.Address }, socketsHttpHandler).DefaultTimeout(); @@ -334,7 +335,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) // Assert Assert.AreEqual("Balancer", reply.Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpoint1.Address.Port}", host); Assert.AreEqual(ConnectivityState.Ready, channel.State); // Wait for pooled connection to timeout and return to idle @@ -342,7 +343,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) reply = await client.UnaryCall(new HelloRequest { Name = "Balancer" }).ResponseAsync.DefaultTimeout(); Assert.AreEqual("Balancer", reply.Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpoint1.Address.Port}", host); } [Test] @@ -377,8 +378,8 @@ async Task UnaryMethod(HelloRequest request, ServerCallContext conte } // Arrange - using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); - using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(50052, UnaryMethod, nameof(UnaryMethod)); + using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); + using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var channel = await BalancerHelpers.CreateChannel(LoggerFactory, new PickFirstConfig(), new[] { endpoint1.Address, endpoint2.Address }).DefaultTimeout(); @@ -410,7 +411,7 @@ async Task UnaryMethod(HelloRequest request, ServerCallContext conte Assert.GreaterOrEqual(activeStreams.Count, 2); foreach (var stream in activeStreams) { - Assert.AreEqual(new DnsEndPoint("127.0.0.1", 50051), stream.EndPoint); + Assert.AreEqual(new DnsEndPoint("127.0.0.1", endpoint1.Address.Port), stream.EndPoint); } tcs.SetResult(null); @@ -430,11 +431,11 @@ await TestHelpers.AssertIsTrueRetryAsync(() => var reply = await client.UnaryCall(new HelloRequest { Name = "Balancer" }).ResponseAsync.DefaultTimeout(); Assert.AreEqual("Balancer", reply.Message); - Assert.AreEqual("127.0.0.1:50052", host); + Assert.AreEqual($"127.0.0.1:{endpoint2.Address.Port}", host); activeStreams = transport.GetActiveStreams(); Assert.AreEqual(1, activeStreams.Count); - Assert.AreEqual(new DnsEndPoint("127.0.0.1", 50052), activeStreams[0].EndPoint); + Assert.AreEqual(new DnsEndPoint("127.0.0.1", endpoint2.Address.Port), activeStreams[0].EndPoint); } [Test] @@ -454,7 +455,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpoint = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); endpoint.Dispose(); var channel = await BalancerHelpers.CreateChannel(LoggerFactory, new PickFirstConfig(), new[] { endpoint.Address }, connect: false).DefaultTimeout(); @@ -491,7 +492,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) // Arrange Logger.LogInformation("Starting server"); - using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpoint = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); Logger.LogInformation("Creating clients"); var socketsHttpHandler = new SocketsHttpHandler(); @@ -510,7 +511,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) Logger.LogInformation("Client waiting for replies"); Assert.AreEqual("Balancer", (await reply1Task).Message); Assert.AreEqual("Balancer", (await reply2Task).Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpoint.Address.Port}", host); Logger.LogInformation("Ending " + endpoint.Address); endpoint.Dispose(); @@ -520,14 +521,14 @@ await Task.WhenAll( BalancerWaitHelpers.WaitForChannelStateAsync(Logger, channel2, ConnectivityState.Idle, channelId: 2)).DefaultTimeout(); Logger.LogInformation("Restarting"); - using var endpointNew = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpointNew = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod), explicitPort: endpoint.Address.Port); reply1Task = client1.UnaryCall(new HelloRequest { Name = "Balancer" }, new CallOptions().WithWaitForReady()).ResponseAsync.DefaultTimeout(); reply2Task = client2.UnaryCall(new HelloRequest { Name = "Balancer" }, new CallOptions().WithWaitForReady()).ResponseAsync.DefaultTimeout(); Assert.AreEqual("Balancer", (await reply1Task).Message); Assert.AreEqual("Balancer", (await reply2Task).Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpointNew.Address.Port}", host); } } #endif diff --git a/test/FunctionalTests/Balancer/RoundRobinBalancerTests.cs b/test/FunctionalTests/Balancer/RoundRobinBalancerTests.cs index 815e19409..9f79cf7e2 100644 --- a/test/FunctionalTests/Balancer/RoundRobinBalancerTests.cs +++ b/test/FunctionalTests/Balancer/RoundRobinBalancerTests.cs @@ -62,7 +62,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) // Arrange Logger.LogInformation("Creating server."); - using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod), loggerFactory: LoggerFactory); + using var endpoint = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod), loggerFactory: LoggerFactory); var channel = await BalancerHelpers.CreateChannel(LoggerFactory, new RoundRobinConfig(), new[] { endpoint.Address }); @@ -100,7 +100,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpoint = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var channel = await BalancerHelpers.CreateChannel(LoggerFactory, new RoundRobinConfig(), new[] { endpoint.Address }); @@ -111,20 +111,20 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) // Assert Assert.AreEqual("Balancer", reply.Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpoint.Address.Port}", host); Logger.LogInformation("Ending " + endpoint.Address); endpoint.Dispose(); Logger.LogInformation("Restarting"); - using var endpointNew = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpointNew = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod), explicitPort: endpoint.Address.Port); // Act reply = await client.UnaryCall(new HelloRequest { Name = "Balancer" }, new CallOptions().WithWaitForReady()).ResponseAsync.DefaultTimeout(); // Assert Assert.AreEqual("Balancer", reply.Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpoint.Address.Port}", host); } [Test] @@ -144,7 +144,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpoint = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var socketsHttpHandler = new SocketsHttpHandler(); var channel1 = await BalancerHelpers.CreateChannel(LoggerFactory, new RoundRobinConfig(), new[] { endpoint.Address }, socketsHttpHandler); @@ -160,7 +160,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) // Assert Assert.AreEqual("Balancer", (await reply1Task).Message); Assert.AreEqual("Balancer", (await reply2Task).Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpoint.Address.Port}", host); // Wait for connecting or failure. // Connecting is faster to wait for, but the status could change so quickly that wait for state change is not triggered. @@ -176,7 +176,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) await waitForConnectingTask.DefaultTimeout(); Logger.LogInformation("Restarting"); - using var endpointNew = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); + using var endpointNew = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod), explicitPort: endpoint.Address.Port); // Act reply1Task = client1.UnaryCall(new HelloRequest { Name = "Balancer" }, new CallOptions().WithWaitForReady()).ResponseAsync.DefaultTimeout(); @@ -185,7 +185,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) // Assert Assert.AreEqual("Balancer", (await reply1Task).Message); Assert.AreEqual("Balancer", (await reply2Task).Message); - Assert.AreEqual("127.0.0.1:50051", host); + Assert.AreEqual($"127.0.0.1:{endpointNew.Address.Port}", host); } [Test] @@ -205,8 +205,8 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); - using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(50052, UnaryMethod, nameof(UnaryMethod)); + using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); + using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var channel = await BalancerHelpers.CreateChannel(LoggerFactory, new RoundRobinConfig(), new[] { endpoint1.Address, endpoint2.Address }, connect: true); @@ -235,7 +235,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) string GetNextHost(string host) { - return host == "127.0.0.1:50051" ? "127.0.0.1:50052" : "127.0.0.1:50051"; + return host == $"127.0.0.1:{endpoint1.Address.Port}" ? $"127.0.0.1:{endpoint2.Address.Port}" : $"127.0.0.1:{endpoint1.Address.Port}"; } } @@ -256,8 +256,8 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); - using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(50052, UnaryMethod, nameof(UnaryMethod)); + using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); + using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var channel = await BalancerHelpers.CreateChannel(LoggerFactory, new RoundRobinConfig(), new[] { endpoint1.Address, endpoint2.Address }, connect: true); @@ -273,17 +273,17 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) Assert.AreEqual("Balancer2", reply2.Message); var host2 = host; - Assert.Contains("127.0.0.1:50051", new[] { host1, host2 }); - Assert.Contains("127.0.0.1:50052", new[] { host1, host2 }); + Assert.Contains($"127.0.0.1:{endpoint1.Address.Port}", new[] { host1, host2 }); + Assert.Contains($"127.0.0.1:{endpoint2.Address.Port}", new[] { host1, host2 }); endpoint1.Dispose(); var subChannel = await BalancerWaitHelpers.WaitForSubchannelToBeReadyAsync(Logger, channel).DefaultTimeout(); - Assert.AreEqual(50052, subChannel.CurrentAddress?.EndPoint.Port); + Assert.AreEqual(endpoint2.Address.Port, subChannel.CurrentAddress?.EndPoint.Port); reply1 = await client.UnaryCall(new HelloRequest { Name = "Balancer" }); Assert.AreEqual("Balancer", reply1.Message); - Assert.AreEqual("127.0.0.1:50052", host); + Assert.AreEqual($"127.0.0.1:{endpoint2.Address.Port}", host); } [Test] @@ -301,8 +301,8 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); - using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(50052, UnaryMethod, nameof(UnaryMethod)); + using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); + using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); SyncPoint? syncPoint = new SyncPoint(runContinuationsAsynchronously: true); syncPoint.Continue(); @@ -355,8 +355,8 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); - using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(50052, UnaryMethod, nameof(UnaryMethod)); + using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); + using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var resolver = new TestResolver(LoggerFactory); resolver.UpdateAddresses(new List @@ -372,7 +372,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) var reply1 = await client.UnaryCall(new HelloRequest { Name = "Balancer1" }); Assert.AreEqual("Balancer1", reply1.Message); - Assert.AreEqual("127.0.0.1:50051", host!); + Assert.AreEqual($"127.0.0.1:{endpoint1.Address.Port}", host!); resolver.UpdateAddresses(new List { @@ -382,7 +382,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) var activeStreams = ((SocketConnectivitySubchannelTransport)disposedSubchannel.Transport).GetActiveStreams(); Assert.AreEqual(1, activeStreams.Count); Assert.AreEqual("127.0.0.1", activeStreams[0].EndPoint.Host); - Assert.AreEqual(50051, activeStreams[0].EndPoint.Port); + Assert.AreEqual(endpoint1.Address.Port, activeStreams[0].EndPoint.Port); // Wait until connected to new endpoint Subchannel? newSubchannel = null; @@ -403,7 +403,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) var reply2 = await client.UnaryCall(new HelloRequest { Name = "Balancer2" }); Assert.AreEqual("Balancer2", reply2.Message); - Assert.AreEqual("127.0.0.1:50052", host!); + Assert.AreEqual($"127.0.0.1:{endpoint2.Address.Port}", host!); // Disposed subchannel stream removed when endpoint disposed. await TestHelpers.AssertIsTrueRetryAsync(() => @@ -416,7 +416,7 @@ await TestHelpers.AssertIsTrueRetryAsync(() => activeStreams = ((SocketConnectivitySubchannelTransport)newSubchannel.Transport).GetActiveStreams(); Assert.AreEqual(1, activeStreams.Count); Assert.AreEqual("127.0.0.1", activeStreams[0].EndPoint.Host); - Assert.AreEqual(50052, activeStreams[0].EndPoint.Port); + Assert.AreEqual(endpoint2.Address.Port, activeStreams[0].EndPoint.Port); Assert.IsNull(((SocketConnectivitySubchannelTransport)disposedSubchannel.Transport)._initialSocket); } @@ -437,8 +437,8 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) } // Arrange - using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod)); - using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(50052, UnaryMethod, nameof(UnaryMethod)); + using var endpoint1 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); + using var endpoint2 = BalancerHelpers.CreateGrpcEndpoint(UnaryMethod, nameof(UnaryMethod)); var resolver = new TestResolver(LoggerFactory); resolver.UpdateAddresses(new List diff --git a/test/FunctionalTests/FunctionalTestBase.cs b/test/FunctionalTests/FunctionalTestBase.cs index e4fe0acc6..01cd654cb 100644 --- a/test/FunctionalTests/FunctionalTestBase.cs +++ b/test/FunctionalTests/FunctionalTestBase.cs @@ -16,12 +16,15 @@ #endregion +using System.Globalization; using Grpc.AspNetCore.FunctionalTests.Infrastructure; using Grpc.Core; using Grpc.Net.Client; using Grpc.Net.Client.Configuration; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using NUnit.Framework; namespace Grpc.AspNetCore.FunctionalTests; @@ -82,7 +85,17 @@ protected virtual void ConfigureServices(IServiceCollection services) { } [OneTimeSetUp] public void OneTimeSetUp() { - Fixture = new GrpcTestFixture(ConfigureServices); + ServerRetryHelper.BindPortsWithRetry(port => + { + Fixture = new GrpcTestFixture( + ConfigureServices, + addConfiguration: configuration => + { + // An explicit (non-dynamic port) is required for HTTP/3 because the port will be shared + // between TCP and UDP. Dynamic ports don't support binding to both transports at the same time. + configuration["Http3Port"] = port.ToString(CultureInfo.InvariantCulture); + }); + }, NullLogger.Instance); } [OneTimeTearDown] diff --git a/test/FunctionalTests/Infrastructure/GrpcTestFixture.cs b/test/FunctionalTests/Infrastructure/GrpcTestFixture.cs index 730cd7cc1..64d1af7dc 100644 --- a/test/FunctionalTests/Infrastructure/GrpcTestFixture.cs +++ b/test/FunctionalTests/Infrastructure/GrpcTestFixture.cs @@ -16,13 +16,61 @@ #endregion +using System.Globalization; +using System.Net; using System.Net.Sockets; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Grpc.AspNetCore.FunctionalTests.Infrastructure; +public sealed record EndpointInfo(string Address, IPEndPoint EndPoint, bool isHttps); + +public abstract class EndpointInfoContainerBase +{ + public abstract string Address { get; } +} + +public sealed class SocketsEndpointInfoContainer(string address) : EndpointInfoContainerBase +{ + public override string Address => address; +} + +public sealed class IPEndpointInfoContainer(Func accessor) : EndpointInfoContainerBase +{ + private readonly Func _accessor = accessor; + + public override string Address + { + get + { + var accessor = _accessor ?? throw new InvalidOperationException("WebApplication not started yet."); + + return accessor().Address; + } + } + + public static IPEndpointInfoContainer Create(ListenOptions listenOptions, bool isHttps) + { + var scheme = isHttps ? "https" : "http"; + var address = BindingAddress.Parse($"{scheme}://127.0.0.1"); + + // The endpoint on listen options is updated from dynamic port placeholder (0) to a real port + // when the server starts up. The func keeps a reference to the listen options and uses them to + // create the address when the test requests it. + return new IPEndpointInfoContainer(() => + { + var endpoint = listenOptions.IPEndPoint!; + var resolvedAddress = address.Scheme.ToLowerInvariant() + Uri.SchemeDelimiter + address.Host.ToLowerInvariant() + ":" + endpoint.Port.ToString(CultureInfo.InvariantCulture); + return new EndpointInfo(resolvedAddress, endpoint, isHttps); + }); + } +} + public class GrpcTestFixture : IDisposable where TStartup : class { private readonly string _socketPath = Path.Combine(Path.GetTempPath(), "grpc-transporter.tmp"); @@ -30,8 +78,9 @@ public class GrpcTestFixture : IDisposable where TStartup : class public GrpcTestFixture( Action? initialConfigureServices = null, - Action>? configureKestrel = null, - TestServerEndpointName? defaultClientEndpointName = null) + Action>? configureKestrel = null, + TestServerEndpointName? defaultClientEndpointName = null, + Action? addConfiguration = null) { Action configureServices = services => { @@ -45,44 +94,51 @@ public GrpcTestFixture( initialConfigureServices?.Invoke(services); configureServices(services); }, - (options, urls) => + (context, options, urls) => { + if (addConfiguration != null) + { + addConfiguration(context.Configuration); + } + if (configureKestrel != null) { - configureKestrel(options, urls); + configureKestrel(context, options, urls); return; } - urls[TestServerEndpointName.Http2] = "http://127.0.0.1:50050"; - options.ListenLocalhost(50050, listenOptions => + options.Listen(IPAddress.Loopback, 0, listenOptions => { listenOptions.Protocols = HttpProtocols.Http2; + urls[TestServerEndpointName.Http2] = IPEndpointInfoContainer.Create(listenOptions, isHttps: false); }); - urls[TestServerEndpointName.Http1] = "http://127.0.0.1:50040"; - options.ListenLocalhost(50040, listenOptions => + options.Listen(IPAddress.Loopback, 0, listenOptions => { listenOptions.Protocols = HttpProtocols.Http1; + urls[TestServerEndpointName.Http1] = IPEndpointInfoContainer.Create(listenOptions, isHttps: false); }); - urls[TestServerEndpointName.Http2WithTls] = "https://127.0.0.1:50030"; - options.ListenLocalhost(50030, listenOptions => + options.Listen(IPAddress.Loopback, 0, listenOptions => { listenOptions.Protocols = HttpProtocols.Http2; var basePath = Path.GetDirectoryName(typeof(InProcessTestServer).Assembly.Location); var certPath = Path.Combine(basePath!, "server1.pfx"); listenOptions.UseHttps(certPath, "1111"); + + urls[TestServerEndpointName.Http2WithTls] = IPEndpointInfoContainer.Create(listenOptions, isHttps: true); }); - urls[TestServerEndpointName.Http1WithTls] = "https://127.0.0.1:50020"; - options.ListenLocalhost(50020, listenOptions => + options.Listen(IPAddress.Loopback, 0, listenOptions => { listenOptions.Protocols = HttpProtocols.Http1; var basePath = Path.GetDirectoryName(typeof(InProcessTestServer).Assembly.Location); var certPath = Path.Combine(basePath!, "server1.pfx"); listenOptions.UseHttps(certPath, "1111"); + + urls[TestServerEndpointName.Http1WithTls] = IPEndpointInfoContainer.Create(listenOptions, isHttps: true); }); #if NET5_0_OR_GREATER @@ -91,18 +147,19 @@ public GrpcTestFixture( File.Delete(_socketPath); } - urls[TestServerEndpointName.UnixDomainSocket] = _socketPath; options.ListenUnixSocket(_socketPath, listenOptions => { listenOptions.Protocols = HttpProtocols.Http2; + + urls[TestServerEndpointName.UnixDomainSocket] = new SocketsEndpointInfoContainer(_socketPath); }); #endif #if NET6_0_OR_GREATER if (RequireHttp3Attribute.IsSupported(out _)) { - urls[TestServerEndpointName.Http3WithTls] = "https://127.0.0.1:55019"; - options.ListenLocalhost(55019, listenOptions => + var http3Port = Convert.ToInt32(context.Configuration["Http3Port"], CultureInfo.InvariantCulture); + options.Listen(IPAddress.Loopback, http3Port, listenOptions => { #pragma warning disable CA2252 // This API requires opting into preview features // Support HTTP/2 for connectivity health in load balancing to work. @@ -112,6 +169,8 @@ public GrpcTestFixture( var basePath = Path.GetDirectoryName(typeof(InProcessTestServer).Assembly.Location); var certPath = Path.Combine(basePath!, "server1.pfx"); listenOptions.UseHttps(certPath, "1111"); + + urls[TestServerEndpointName.Http3WithTls] = IPEndpointInfoContainer.Create(listenOptions, isHttps: true); }); } #endif @@ -133,18 +192,18 @@ public GrpcTestFixture( public HttpMessageHandler Handler { get; } public HttpClient Client { get; } - public HttpClient CreateClient(TestServerEndpointName? endpointName = null, DelegatingHandler? messageHandler = null, Action? configureHandler = null) + public HttpClient CreateClient(TestServerEndpointName? endpointName = null, DelegatingHandler? messageHandler = null, Action? configureHandler = null) { - return CreateHttpCore(endpointName, messageHandler, configureHandler).client; + return CreateHttpCore(endpointName, messageHandler, configureHandler).client; } - public (HttpMessageHandler handler, Uri address) CreateHandler(TestServerEndpointName? endpointName = null, DelegatingHandler? messageHandler = null, Action? configureHandler = null) + public (HttpMessageHandler handler, Uri address) CreateHandler(TestServerEndpointName? endpointName = null, DelegatingHandler? messageHandler = null, Action? configureHandler = null) { - var result = CreateHttpCore(endpointName, messageHandler, configureHandler); + var result = CreateHttpCore(endpointName, messageHandler, configureHandler); return (result.handler, result.client.BaseAddress!); } - private (HttpClient client, HttpMessageHandler handler) CreateHttpCore(TestServerEndpointName? endpointName = null, DelegatingHandler? messageHandler = null, Action? configureHandler = null) + private (HttpClient client, HttpMessageHandler handler) CreateHttpCore(TestServerEndpointName? endpointName = null, DelegatingHandler? messageHandler = null, Action? configureHandler = null) { #if HTTP3_TESTING endpointName ??= TestServerEndpointName.Http3WithTls; @@ -158,7 +217,7 @@ public HttpClient CreateClient(TestServerEndpointName? endpointName = null, Dele RemoteCertificateValidationCallback = (_, __, ___, ____) => true }; - configureHandler?.Invoke(socketsHttpHandler); + configureHandler?.Invoke(socketsHttpHandler); #if NET5_0_OR_GREATER if (endpointName == TestServerEndpointName.UnixDomainSocket) diff --git a/test/FunctionalTests/Infrastructure/InProcessTestServer.cs b/test/FunctionalTests/Infrastructure/InProcessTestServer.cs index 19120a5bb..4d8c62c6e 100644 --- a/test/FunctionalTests/Infrastructure/InProcessTestServer.cs +++ b/test/FunctionalTests/Infrastructure/InProcessTestServer.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -46,10 +46,10 @@ public class InProcessTestServer : InProcessTestServer private readonly ILogger _logger; private readonly LogSinkProvider _logSinkProvider; private readonly Action _initialConfigureServices; - private readonly Action> _configureKestrel; + private readonly Action> _configureKestrel; private IWebHost? _host; private IHostApplicationLifetime? _lifetime; - private Dictionary? _urls; + private Dictionary? _urls; internal override event Action ServerLogged { @@ -64,12 +64,12 @@ public override string GetUrl(TestServerEndpointName endpointName) throw new InvalidOperationException(); } - return _urls[endpointName]; + return _urls[endpointName].Address; } public override IWebHost? Host => _host; - public InProcessTestServer(Action initialConfigureServices, Action> configureKestrel) + public InProcessTestServer(Action initialConfigureServices, Action> configureKestrel) { _logSinkProvider = new LogSinkProvider(); _loggerFactory = new LoggerFactory(); @@ -82,7 +82,7 @@ public InProcessTestServer(Action initialConfigureServices, public override void StartServer() { - _urls = new Dictionary(); + _urls = new Dictionary(); var builder = new WebHostBuilder() .ConfigureLogging(builder => builder @@ -93,9 +93,9 @@ public override void StartServer() _initialConfigureServices?.Invoke(services); }) .UseStartup(typeof(TStartup)) - .UseKestrel(options => + .UseKestrel((context, options) => { - _configureKestrel(options, _urls); + _configureKestrel(context, options, _urls); }) .UseContentRoot(Directory.GetCurrentDirectory()); diff --git a/test/FunctionalTests/Infrastructure/ServerRetryHelper.cs b/test/FunctionalTests/Infrastructure/ServerRetryHelper.cs new file mode 100644 index 000000000..7aed95ef6 --- /dev/null +++ b/test/FunctionalTests/Infrastructure/ServerRetryHelper.cs @@ -0,0 +1,113 @@ +#region Copyright notice and license + +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.Net; +using System.Net.NetworkInformation; +using Microsoft.Extensions.Logging; + +namespace Grpc.AspNetCore.FunctionalTests.Infrastructure; + +// Copied with permission from https://github.com/dotnet/aspnetcore/blob/3612a9f261c696447844eb748a545bd062beeab4/src/Servers/Kestrel/shared/test/ServerRetryHelper.cs +public static class ServerRetryHelper +{ + private const int RetryCount = 20; + + /// + /// Retry a func. Useful when a test needs an explicit port and you want to avoid port conflicts. + /// + public static void BindPortsWithRetry(Action retryFunc, ILogger logger) + { + var retryCount = 0; + + // Add a random number to starting port to reduce chance of conflicts because of multiple tests using this retry. + var nextPortAttempt = 30000 + Random.Shared.Next(10000); + + while (true) + { + // Find a port that's available for TCP and UDP. Start with the given port search upwards from there. + var port = GetAvailablePort(nextPortAttempt, logger); + + try + { + retryFunc(port); + break; + } + catch (Exception ex) + { + retryCount++; + nextPortAttempt = port + Random.Shared.Next(100); + + if (retryCount >= RetryCount) + { + throw; + } + else + { + logger.LogError(ex, "Error running test {RetryCount}. Retrying.", retryCount); + } + } + } + } + + private static int GetAvailablePort(int startingPort, ILogger logger) + { + logger.LogInformation("Searching for free port starting at {startingPort}.", startingPort); + + var unavailableEndpoints = new List(); + + var properties = IPGlobalProperties.GetIPGlobalProperties(); + + // Ignore active connections + AddEndpoints(startingPort, unavailableEndpoints, properties.GetActiveTcpConnections().Select(c => c.LocalEndPoint)); + + // Ignore active tcp listners + AddEndpoints(startingPort, unavailableEndpoints, properties.GetActiveTcpListeners()); + + // Ignore active UDP listeners + AddEndpoints(startingPort, unavailableEndpoints, properties.GetActiveUdpListeners()); + + logger.LogInformation("Found {count} unavailable endpoints.", unavailableEndpoints.Count); + + for (var i = startingPort; i < ushort.MaxValue; i++) + { + var match = unavailableEndpoints.FirstOrDefault(ep => ep.Port == i); + if (match == null) + { + logger.LogInformation("Port {i} free.", i); + return i; + } + else + { + logger.LogInformation("Port {i} in use. End point: {match}", i, match); + } + } + + throw new Exception($"Couldn't find a free port after {startingPort}."); + + static void AddEndpoints(int startingPort, List endpoints, IEnumerable activeEndpoints) + { + foreach (IPEndPoint endpoint in activeEndpoints) + { + if (endpoint.Port >= startingPort) + { + endpoints.Add(endpoint); + } + } + } + } +}