#region Copyright notice and license // Copyright 2015 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; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Grpc.Core; using Grpc.Core.Utils; using Grpc.Testing; using NUnit.Framework; namespace Grpc.IntegrationTesting { public class XdsInteropClientTest { const string Host = "localhost"; BackendServiceImpl backendService; Server backendServer; Server lbStatsServer; Channel lbStatsChannel; LoadBalancerStatsService.LoadBalancerStatsServiceClient lbStatsClient; XdsInteropClient xdsInteropClient; [OneTimeSetUp] public void Init() { backendService = new BackendServiceImpl(); // Disable SO_REUSEPORT to prevent https://github.com/grpc/grpc/issues/10755 backendServer = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) { Services = { TestService.BindService(backendService) }, Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } } }; backendServer.Start(); xdsInteropClient = new XdsInteropClient(new XdsInteropClient.ClientOptions { NumChannels = 1, Qps = 1, RpcTimeoutSec = 10, Rpc = "UnaryCall", Server = $"{Host}:{backendServer.Ports.Single().BoundPort}", }); // Disable SO_REUSEPORT to prevent https://github.com/grpc/grpc/issues/10755 lbStatsServer = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) { Services = { LoadBalancerStatsService.BindService(new LoadBalancerStatsServiceImpl(xdsInteropClient.StatsWatcher)) }, Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } } }; lbStatsServer.Start(); int port = lbStatsServer.Ports.Single().BoundPort; lbStatsChannel = new Channel(Host, port, ChannelCredentials.Insecure); lbStatsClient = new LoadBalancerStatsService.LoadBalancerStatsServiceClient(lbStatsChannel); } [OneTimeTearDown] public void Cleanup() { lbStatsChannel.ShutdownAsync().Wait(); lbStatsServer.ShutdownAsync().Wait(); backendServer.ShutdownAsync().Wait(); } [Test] public async Task SmokeTest() { string backendName = "backend1"; backendService.UnaryHandler = (request, context) => { return Task.FromResult(new SimpleResponse { Hostname = backendName }); }; var cancellationTokenSource = new CancellationTokenSource(); var runChannelsTask = xdsInteropClient.RunChannelsAsync(cancellationTokenSource.Token); var stats = await lbStatsClient.GetClientStatsAsync(new LoadBalancerStatsRequest { NumRpcs = 5, TimeoutSec = 10, }, deadline: DateTime.UtcNow.AddSeconds(30)); Assert.AreEqual(0, stats.NumFailures); Assert.AreEqual(backendName, stats.RpcsByPeer.Keys.Single()); Assert.AreEqual(5, stats.RpcsByPeer[backendName]); Assert.AreEqual("UnaryCall", stats.RpcsByMethod.Keys.Single()); Assert.AreEqual(backendName, stats.RpcsByMethod["UnaryCall"].RpcsByPeer_.Keys.Single()); Assert.AreEqual(5, stats.RpcsByMethod["UnaryCall"].RpcsByPeer_[backendName]); await Task.Delay(100); var stats2 = await lbStatsClient.GetClientStatsAsync(new LoadBalancerStatsRequest { NumRpcs = 3, TimeoutSec = 10, }, deadline: DateTime.UtcNow.AddSeconds(30)); Assert.AreEqual(0, stats2.NumFailures); Assert.AreEqual(backendName, stats2.RpcsByPeer.Keys.Single()); Assert.AreEqual(3, stats2.RpcsByPeer[backendName]); Assert.AreEqual("UnaryCall", stats2.RpcsByMethod.Keys.Single()); Assert.AreEqual(backendName, stats2.RpcsByMethod["UnaryCall"].RpcsByPeer_.Keys.Single()); Assert.AreEqual(3, stats2.RpcsByMethod["UnaryCall"].RpcsByPeer_[backendName]); cancellationTokenSource.Cancel(); await runChannelsTask; } [Test] public async Task HostnameReadFromResponseHeaders() { string correctBackendName = "backend1"; backendService.UnaryHandler = async (request, context) => { await context.WriteResponseHeadersAsync(new Metadata { {"hostname", correctBackendName} }); return new SimpleResponse { Hostname = "wrong_hostname" }; }; var cancellationTokenSource = new CancellationTokenSource(); var runChannelsTask = xdsInteropClient.RunChannelsAsync(cancellationTokenSource.Token); var stats = await lbStatsClient.GetClientStatsAsync(new LoadBalancerStatsRequest { NumRpcs = 3, TimeoutSec = 10, }, deadline: DateTime.UtcNow.AddSeconds(30)); Assert.AreEqual(0, stats.NumFailures); Assert.AreEqual(correctBackendName, stats.RpcsByPeer.Keys.Single()); Assert.AreEqual(3, stats.RpcsByPeer[correctBackendName]); cancellationTokenSource.Cancel(); await runChannelsTask; } public class BackendServiceImpl : TestService.TestServiceBase { public UnaryServerMethod UnaryHandler { get; set; } public UnaryServerMethod EmptyHandler { get; set; } public override Task UnaryCall(SimpleRequest request, ServerCallContext context) { return UnaryHandler(request, context); } public override Task EmptyCall(Empty request, ServerCallContext context) { return EmptyHandler(request, context); } } } }