public async Task TestAggregateHandlerDefaultRequestType() { var configWatcher = new MockConfigWatcher(true, new Dictionary <string, (string, IEnumerable <IMessage>)>()); using var server = DiscoveryServerBuilder .CreateFor(configWatcher) .ConfigureDiscoveryService <AggregatedDiscoveryService>() .Build(); await server.StartAsync(); var client = new AggregatedDiscoveryServiceClient(CreateGrpcChannel()); var duplex = client.StreamAggregatedResources(); var clientTask = HandleResponses(duplex.ResponseStream); // Leave off the type URL. For ADS requests it should fail because the type URL is required. await duplex.RequestStream.WriteAsync(new DiscoveryRequest { Node = NODE, }); await duplex.RequestStream.CompleteAsync(); var(responseErrors, responses, completed, error) = await clientTask; }
public async Task TestCallbacksOnError() { int streamClosesWithErrors = 0; var callbacks = Substitute.For <IDiscoveryServerCallbacks>(); callbacks .When(x => x.OnStreamCloseWithError(Arg.Any <long>(), Arg.Any <string>(), Arg.Any <Exception>())) .Do(_ => Interlocked.Increment(ref streamClosesWithErrors)); var configWatcher = new MockConfigWatcher(false, CreateResponses()); using var server = DiscoveryServerBuilder .CreateFor(configWatcher, callbacks) .ConfigureDiscoveryService <AggregatedDiscoveryService>() .Build(); await server.StartAsync(); var client = new AggregatedDiscoveryServiceClient(CreateGrpcChannel()); var ctx = new CancellationTokenSource(); var duplex = client.StreamAggregatedResources(cancellationToken: ctx.Token); await duplex.RequestStream.WriteAsync(new DiscoveryRequest()); ctx.Cancel(); WaitUntil(ref streamClosesWithErrors, 1, TimeSpan.FromSeconds(1)); streamClosesWithErrors.Should().Be(1); }
public async Task TestSendError() { var configWatcher = new MockConfigWatcher(false, CreateResponses()); using var server = DiscoveryServerBuilder .CreateFor(configWatcher) .ConfigureDiscoveryService <AggregatedDiscoveryService>() .Build(); await server.StartAsync(); var client = new AggregatedDiscoveryServiceClient(CreateGrpcChannel()); foreach (var typeUrl in Resources.TYPE_URLS) { var duplex = client.StreamAggregatedResources(); var clientTask = HandleResponses(duplex.ResponseStream, sendError: true); await duplex.RequestStream.WriteAsync(new DiscoveryRequest { Node = NODE, TypeUrl = typeUrl }); var(responseErrors, responses, completed, error) = await clientTask; completed.Should().BeFalse(); error.Should().BeTrue(); responseErrors.Should().BeEmpty(); } }
public async Task TestWatchClosed() { var configWatcher = new MockConfigWatcher(true, new Dictionary <string, (string, IEnumerable <IMessage>)>()); using var server = DiscoveryServerBuilder .CreateFor(configWatcher) .ConfigureDiscoveryService <AggregatedDiscoveryService>() .Build(); await server.StartAsync(); var client = new AggregatedDiscoveryServiceClient(CreateGrpcChannel()); foreach (var typeUrl in Resources.TYPE_URLS) { // MockDiscoveryResponseObserver responseObserver = new MockDiscoveryResponseObserver(); var ctx = new CancellationTokenSource(); var duplex = client.StreamAggregatedResources(cancellationToken: ctx.Token); var clientTask = HandleResponses(duplex.ResponseStream); await duplex.RequestStream.WriteAsync(new DiscoveryRequest { Node = NODE, TypeUrl = typeUrl }); ctx.Cancel(); var(responseErrors, responses, completed, error) = await clientTask; error.Should().BeTrue(); completed.Should().BeFalse(); responseErrors.Should().BeEmpty(); } }
public static void Main(string[] args) { // First create a cache object to hold the envoy configuration cache // the cache requires a node hash callback. It'll give you an envoy Node, and expects an Id. // this hard codes the id to "key", but it will change later on. var cache = new SimpleCache <string>(_ => "key"); cache.SetSnapshot("key", Data.CreateBaseSnapshot()); // Create a DiscoveryServer and give it the cache object // Select the type of server. https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol var discoveryServer = DiscoveryServerBuilder .CreateFor(cache, 6000) .UseHttps(false) .ConfigureDiscoveryService <ClusterDiscoveryService>() .ConfigureDiscoveryService <EndpointDiscoveryService>() .ConfigureDiscoveryService <RouteDiscoveryService>() .ConfigureDiscoveryService <ListenerDiscoveryService>() .ConfigureLogging(logging => { logging.AddConsole(); logging.SetMinimumLevel(LogLevel.Debug); }) .Build(); var restServer = WebHost.CreateDefaultBuilder(args) .Configure(_ => _.Run(async ctx => { var endpointRequest = await deserialize <AppEndpoint>(ctx.Response.Body); if (valid(endpointRequest)) { var result = Reconcile(cache, endpointRequest); await ctx.Response.WriteAsync(result); } else { ctx.Response.StatusCode = 400; await ctx.Response.WriteAsync("Invalid"); } bool valid(AppEndpoint e) => !string.IsNullOrEmpty(e.Ip) && !string.IsNullOrEmpty(e.Domain) && !string.IsNullOrEmpty(e.Name) && e.Port != 0; async Task <T> deserialize <T>(Stream stream) { var str = await new StreamReader(ctx.Request.Body).ReadToEndAsync(); return(JsonConvert.DeserializeObject <T>(str)); } })) .Build(); discoveryServer.RunAsync(); restServer.Run(); }
public async Task TestStaleNonce() { var configWatcher = new MockConfigWatcher(false, CreateResponses()); using var server = DiscoveryServerBuilder .CreateFor(configWatcher) .ConfigureDiscoveryService <AggregatedDiscoveryService>() .Build(); await server.StartAsync(); var client = new AggregatedDiscoveryServiceClient(CreateGrpcChannel()); foreach (var typeUrl in Resources.TYPE_URLS) { var duplex = client.StreamAggregatedResources(); var clientTask = HandleResponses(duplex.ResponseStream); await duplex.RequestStream.WriteAsync(new DiscoveryRequest { Node = NODE, TypeUrl = typeUrl, }); // Stale request, should not create a new watch. await duplex.RequestStream.WriteAsync(new DiscoveryRequest { Node = NODE, TypeUrl = typeUrl, ResponseNonce = "xyz" }); // Fresh request, should create a new watch. await duplex.RequestStream.WriteAsync(new DiscoveryRequest { Node = NODE, TypeUrl = typeUrl, ResponseNonce = "1", VersionInfo = "0" }); await duplex.RequestStream.CompleteAsync(); var(responseErrors, responses, completed, error) = await clientTask; // Assert that 2 watches have been created for this resource type. configWatcher.counts[typeUrl].Should().Be(2); } }
public async Task TestCallbacksSeparateHandlers() { var streamCloses = new ConcurrentDictionary <string, StrongBox <int> >(); var streamOpens = new ConcurrentDictionary <string, StrongBox <int> >(); var streamRequests = new ConcurrentDictionary <string, StrongBox <int> >(); var streamResponses = new ConcurrentDictionary <string, StrongBox <int> >(); Resources.TYPE_URLS.ForEach(typeUrl => { streamCloses[typeUrl] = new StrongBox <int>(0); streamOpens[typeUrl] = new StrongBox <int>(0); streamRequests[typeUrl] = new StrongBox <int>(0); streamResponses[typeUrl] = new StrongBox <int>(0); }); var assertionErrors = new List <string>(); var callbacks = Substitute.For <IDiscoveryServerCallbacks>(); void ValidateTypeUrl(string typeUrl, string caller) { if (!Resources.TYPE_URLS.Contains(typeUrl)) { assertionErrors.Add($"{caller}#typeUrl => expected {Resources.ANY_TYPE_URL}, got {typeUrl}"); } } callbacks .When(x => x.OnStreamClose(Arg.Any <long>(), Arg.Any <string>())) .Do(args => { var typeUrl = args.ArgAt <string>(1); ValidateTypeUrl(typeUrl, "OnStreamClose"); Interlocked.Increment(ref streamCloses[typeUrl].Value); }); callbacks .When(x => x.OnStreamOpen(Arg.Any <long>(), Arg.Any <string>())) .Do(args => { var typeUrl = args.ArgAt <string>(1); ValidateTypeUrl(typeUrl, "OnStreamOpen"); Interlocked.Increment(ref streamOpens[typeUrl].Value); }); callbacks .When(x => x.OnStreamRequest(Arg.Any <long>(), Arg.Any <DiscoveryRequest>())) .Do(args => Interlocked.Increment(ref streamRequests[args.ArgAt <DiscoveryRequest>(1).TypeUrl].Value)); callbacks .When(x => x.OnStreamResponse(Arg.Any <long>(), Arg.Any <DiscoveryRequest>(), Arg.Any <DiscoveryResponse>())) .Do(args => Interlocked.Increment(ref streamResponses[args.ArgAt <DiscoveryRequest>(1).TypeUrl].Value)); var configWatcher = new MockConfigWatcher(false, CreateResponses()); using var server = DiscoveryServerBuilder .CreateFor(configWatcher, callbacks) .ConfigureDiscoveryService <ClusterDiscoveryService>() .ConfigureDiscoveryService <EndpointDiscoveryService>() .ConfigureDiscoveryService <ListenerDiscoveryService>() .ConfigureDiscoveryService <RouteDiscoveryService>() .ConfigureDiscoveryService <SecretDiscoveryService>() .Build(); await server.StartAsync(); var channel = CreateGrpcChannel(); var clusterClient = new ClusterDiscoveryServiceClient(channel); var endpointClient = new EndpointDiscoveryServiceClient(channel); var listenerClient = new ListenerDiscoveryServiceClient(channel); var routeClient = new RouteDiscoveryServiceClient(channel); var secretClient = new SecretDiscoveryServiceClient(channel); foreach (var typeUrl in Resources.TYPE_URLS) { Task <(List <string>, List <DiscoveryResponse>, bool, bool)> clientTask = null; AsyncDuplexStreamingCall <DiscoveryRequest, DiscoveryResponse> duplex = null; switch (typeUrl) { case Resources.CLUSTER_TYPE_URL: duplex = clusterClient.StreamClusters(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); break; case Resources.ENDPOINT_TYPE_URL: duplex = endpointClient.StreamEndpoints(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); break; case Resources.LISTENER_TYPE_URL: duplex = listenerClient.StreamListeners(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); break; case Resources.ROUTE_TYPE_URL: duplex = routeClient.StreamRoutes(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); break; case Resources.SECRET_TYPE_URL: duplex = secretClient.StreamSecrets(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); break; default: Assert.True(false, "Unsupported resource type: " + typeUrl); break; } var discoveryRequest = new DiscoveryRequest { Node = NODE, TypeUrl = typeUrl }; await duplex.RequestStream.WriteAsync(discoveryRequest); WaitUntil(ref streamOpens[typeUrl].Value, 1); streamOpens[typeUrl].Value.Should().Be(1); WaitUntil(ref streamRequests[typeUrl].Value, 1); streamRequests[typeUrl].Value.Should().Be(1); await duplex.RequestStream.CompleteAsync(); WaitUntil(ref streamResponses[typeUrl].Value, 1); streamResponses[typeUrl].Value.Should().Be(1); WaitUntil(ref streamCloses[typeUrl].Value, 1); streamCloses[typeUrl].Value.Should().Be(1); } assertionErrors.Should().BeEmpty(); }
public async Task TestAggregatedHandler() { var configWatcher = new MockConfigWatcher(false, CreateResponses()); using var server = DiscoveryServerBuilder .CreateFor(configWatcher) .ConfigureDiscoveryService <AggregatedDiscoveryService>() .Build(); await server.StartAsync(); var client = new AggregatedDiscoveryServiceClient(CreateGrpcChannel()); var duplex = client.StreamAggregatedResources(); var clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); await duplex.RequestStream.WriteAsync(new DiscoveryRequest { Node = NODE, TypeUrl = Resources.LISTENER_TYPE_URL }); await duplex.RequestStream.WriteAsync(new DiscoveryRequest { Node = NODE, TypeUrl = Resources.CLUSTER_TYPE_URL, }); var clusterRequest = new DiscoveryRequest { Node = NODE, TypeUrl = Resources.ENDPOINT_TYPE_URL, }; clusterRequest.ResourceNames.Add(CLUSTER_NAME); await duplex.RequestStream.WriteAsync(clusterRequest); var routeRequest = new DiscoveryRequest { Node = NODE, TypeUrl = Resources.ROUTE_TYPE_URL, }; routeRequest.ResourceNames.Add(ROUTE_NAME); await duplex.RequestStream.WriteAsync(routeRequest); var secretRequest = new DiscoveryRequest { Node = NODE, TypeUrl = Resources.SECRET_TYPE_URL, }; secretRequest.ResourceNames.Add(SECRET_NAME); await duplex.RequestStream.WriteAsync(secretRequest); await duplex.RequestStream.CompleteAsync(); var(responseErrors, responses, completed, error) = await clientTask; completed.Should().BeTrue(); error.Should().BeFalse(); responseErrors.Should().BeEmpty(); foreach (var typeUrl in Resources.TYPE_URLS) { configWatcher.counts.Should().Contain(typeUrl, 1); } configWatcher.counts.Should().HaveCount(Resources.TYPE_URLS.Count()); foreach (var typeUrl in Resources.TYPE_URLS) { responses.Should().Contain(r => r.TypeUrl == typeUrl && r.VersionInfo == VERSION, "missing expected response of type %s", typeUrl); } }
public async Task TestCallbacksAggregateHandler() { var assertionErrors = new List <string>(); int streamCloses = 0, streamOpens = 0, streamRequests = 0, streamResponses = 0; var callbacks = Substitute.For <IDiscoveryServerCallbacks>(); void ValidateTypeUrl(string typeUrl, string caller) { if (typeUrl != Resources.ANY_TYPE_URL) { assertionErrors.Add($"{caller}#typeUrl => expected {Resources.ANY_TYPE_URL}, got {typeUrl}"); } } callbacks .When(x => x.OnStreamClose(Arg.Any <long>(), Arg.Any <string>())) .Do(args => { ValidateTypeUrl(args.ArgAt <string>(1), "OnStreamClose"); Interlocked.Increment(ref streamCloses); }); callbacks .When(x => x.OnStreamOpen(Arg.Any <long>(), Arg.Any <string>())) .Do(args => { ValidateTypeUrl(args.ArgAt <string>(1), "OnStreamOpen"); Interlocked.Increment(ref streamOpens); }); callbacks .When(x => x.OnStreamRequest(Arg.Any <long>(), Arg.Any <DiscoveryRequest>())) .Do(_ => Interlocked.Increment(ref streamRequests)); callbacks .When(x => x.OnStreamResponse(Arg.Any <long>(), Arg.Any <DiscoveryRequest>(), Arg.Any <DiscoveryResponse>())) .Do(_ => Interlocked.Increment(ref streamResponses)); var configWatcher = new MockConfigWatcher(false, CreateResponses()); using var server = DiscoveryServerBuilder .CreateFor(configWatcher, callbacks) .ConfigureDiscoveryService <AggregatedDiscoveryService>() .Build(); await server.StartAsync(); var client = new AggregatedDiscoveryServiceClient(CreateGrpcChannel()); var duplex = client.StreamAggregatedResources(); var clientTask = HandleResponses(duplex.ResponseStream); await duplex.RequestStream.WriteAsync(new DiscoveryRequest { Node = NODE, TypeUrl = Resources.LISTENER_TYPE_URL }); WaitUntil(ref streamOpens, 1); streamOpens.Should().Be(1); await duplex.RequestStream.WriteAsync(new DiscoveryRequest { Node = NODE, TypeUrl = Resources.CLUSTER_TYPE_URL }); var cluster = new DiscoveryRequest { Node = NODE, TypeUrl = Resources.ENDPOINT_TYPE_URL, }; cluster.ResourceNames.Add(CLUSTER_NAME); await duplex.RequestStream.WriteAsync(cluster); var route = new DiscoveryRequest { Node = NODE, TypeUrl = Resources.ROUTE_TYPE_URL }; route.ResourceNames.Add(ROUTE_NAME); await duplex.RequestStream.WriteAsync(route); var secret = new DiscoveryRequest { Node = NODE, TypeUrl = Resources.SECRET_TYPE_URL, }; secret.ResourceNames.Add(SECRET_NAME); await duplex.RequestStream.WriteAsync(secret); WaitUntil(ref streamRequests, Resources.TYPE_URLS.Count()); streamRequests.Should().Be(Resources.TYPE_URLS.Count()); WaitUntil(ref streamRequests, Resources.TYPE_URLS.Count()); streamResponses.Should().Be(Resources.TYPE_URLS.Count()); // Send another round of requests. These should not trigger any responses. streamResponses = 0; streamRequests = 0; await duplex.RequestStream.WriteAsync(new DiscoveryRequest { Node = NODE, ResponseNonce = "0", VersionInfo = VERSION, TypeUrl = Resources.LISTENER_TYPE_URL }); await duplex.RequestStream.WriteAsync(new DiscoveryRequest { Node = NODE, ResponseNonce = "1", TypeUrl = Resources.CLUSTER_TYPE_URL, VersionInfo = VERSION, }); var cluster2 = new DiscoveryRequest { Node = NODE, ResponseNonce = "2", TypeUrl = Resources.ENDPOINT_TYPE_URL, VersionInfo = VERSION }; cluster2.ResourceNames.Add(CLUSTER_NAME); await duplex.RequestStream.WriteAsync(cluster2); var route2 = new DiscoveryRequest { Node = NODE, ResponseNonce = "3", TypeUrl = Resources.ROUTE_TYPE_URL, VersionInfo = VERSION }; route2.ResourceNames.Add(ROUTE_NAME); await duplex.RequestStream.WriteAsync(route2); var secert2 = new DiscoveryRequest { Node = NODE, ResponseNonce = "4", TypeUrl = Resources.SECRET_TYPE_URL, VersionInfo = VERSION, }; secert2.ResourceNames.Add(SECRET_NAME); await duplex.RequestStream.WriteAsync(secert2); WaitUntil(ref streamRequests, Resources.TYPE_URLS.Count()); streamRequests.Should().Be(Resources.TYPE_URLS.Count()); streamResponses.Should().Be(0); await duplex.RequestStream.CompleteAsync(); WaitUntil(ref streamCloses, 1); streamCloses.Should().Be(1); assertionErrors.Should().BeEmpty(); }
public async Task TestSeparateHandlersDefaultRequestType() { var configWatcher = new MockConfigWatcher(false, CreateResponses()); using var server = DiscoveryServerBuilder .CreateFor(configWatcher) .ConfigureDiscoveryService <ClusterDiscoveryService>() .ConfigureDiscoveryService <EndpointDiscoveryService>() .ConfigureDiscoveryService <ListenerDiscoveryService>() .ConfigureDiscoveryService <RouteDiscoveryService>() .ConfigureDiscoveryService <SecretDiscoveryService>() .Build(); await server.StartAsync(); var channel = CreateGrpcChannel(); var clusterClient = new ClusterDiscoveryServiceClient(channel); var endpointClient = new EndpointDiscoveryServiceClient(channel); var listenerClient = new ListenerDiscoveryServiceClient(channel); var routeClient = new RouteDiscoveryServiceClient(channel); var secretClient = new SecretDiscoveryServiceClient(channel); foreach (var typeUrl in Resources.TYPE_URLS) { Task <(List <string>, List <DiscoveryResponse>, bool, bool)> clientTask = null; AsyncDuplexStreamingCall <DiscoveryRequest, DiscoveryResponse> duplex = null; switch (typeUrl) { case Resources.CLUSTER_TYPE_URL: duplex = clusterClient.StreamClusters(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); break; case Resources.ENDPOINT_TYPE_URL: duplex = endpointClient.StreamEndpoints(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); break; case Resources.LISTENER_TYPE_URL: duplex = listenerClient.StreamListeners(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); break; case Resources.ROUTE_TYPE_URL: duplex = routeClient.StreamRoutes(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); break; case Resources.SECRET_TYPE_URL: duplex = secretClient.StreamSecrets(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); break; default: Assert.True(false, "Unsupported resource type: " + typeUrl); break; } // Leave off the type URL. For xDS requests it should default to the value for that handler's type. var discoveryRequest = new DiscoveryRequest { Node = NODE, }; await duplex.RequestStream.WriteAsync(discoveryRequest); await duplex.RequestStream.CompleteAsync(); var(responseErrors, _, _, _) = await clientTask; responseErrors.Should().BeEmpty(); } }
public async Task TestSeparateHandlers() { var configWatcher = new MockConfigWatcher(false, CreateResponses()); using var server = DiscoveryServerBuilder .CreateFor(configWatcher) .ConfigureDiscoveryService <ClusterDiscoveryService>() .ConfigureDiscoveryService <EndpointDiscoveryService>() .ConfigureDiscoveryService <ListenerDiscoveryService>() .ConfigureDiscoveryService <RouteDiscoveryService>() .ConfigureDiscoveryService <SecretDiscoveryService>() .Build(); await server.StartAsync(); var channel = CreateGrpcChannel(); var clusterClient = new ClusterDiscoveryServiceClient(channel); var endpointClient = new EndpointDiscoveryServiceClient(channel); var listenerClient = new ListenerDiscoveryServiceClient(channel); var routeClient = new RouteDiscoveryServiceClient(channel); var secretClient = new SecretDiscoveryServiceClient(channel); foreach (var typeUrl in Resources.TYPE_URLS) { var discoveryRequest = new DiscoveryRequest { Node = NODE, TypeUrl = typeUrl }; Task <(List <string>, List <DiscoveryResponse>, bool, bool)> clientTask = null; AsyncDuplexStreamingCall <DiscoveryRequest, DiscoveryResponse> duplex = null; switch (typeUrl) { case Resources.CLUSTER_TYPE_URL: duplex = clusterClient.StreamClusters(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); break; case Resources.ENDPOINT_TYPE_URL: duplex = endpointClient.StreamEndpoints(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); discoveryRequest.ResourceNames.Add(CLUSTER_NAME); break; case Resources.LISTENER_TYPE_URL: duplex = listenerClient.StreamListeners(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); break; case Resources.ROUTE_TYPE_URL: duplex = routeClient.StreamRoutes(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); discoveryRequest.ResourceNames.Add(ROUTE_NAME); break; case Resources.SECRET_TYPE_URL: duplex = secretClient.StreamSecrets(); clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream)); discoveryRequest.ResourceNames.Add(SECRET_NAME); break; default: Assert.True(false, "Unsupported resource type: " + typeUrl); break; } await duplex.RequestStream.WriteAsync(discoveryRequest); await duplex.RequestStream.CompleteAsync(); var(responseErrors, responses, completed, error) = await clientTask; completed.Should().BeTrue(); error.Should().BeFalse(); responseErrors.Should().BeEmpty(); configWatcher.counts.Should().Contain(typeUrl, 1); responses.Should().Contain( r => r.TypeUrl == typeUrl && r.VersionInfo == VERSION, "missing expected response of type %s", typeUrl); } configWatcher.counts.Should().HaveCount(Resources.TYPE_URLS.Count()); }