public void BuildCluster_MissingBackendId_UsesServiceName() { var labels = new Dictionary <string, string>() { { "YARP.Backend.Quota.Burst", "2.3" }, { "YARP.Backend.Partitioning.Count", "5" }, { "YARP.Backend.Partitioning.KeyExtractor", "Header('x-ms-organization-id')" }, { "YARP.Backend.Partitioning.Algorithm", "SHA256" }, { "YARP.Backend.HealthCheck.Active.Interval", "00:00:5" }, }; var cluster = LabelsParser.BuildCluster(_testServiceName, labels, null); cluster.Id.Should().Be(_testServiceName.ToString()); }
public void BuildCluster_CompleteLabels_Works() { // Arrange var labels = new Dictionary <string, string>() { { "YARP.Enable", "true" }, { "YARP.Backend.BackendId", "MyCoolClusterId" }, { "YARP.Backend.CircuitBreaker.MaxConcurrentRequests", "42" }, { "YARP.Backend.CircuitBreaker.MaxConcurrentRetries", "5" }, { "YARP.Backend.Quota.Average", "1.2" }, { "YARP.Backend.Quota.Burst", "2.3" }, { "YARP.Backend.Partitioning.Count", "5" }, { "YARP.Backend.Partitioning.KeyExtractor", "Header('x-ms-organization-id')" }, { "YARP.Backend.Partitioning.Algorithm", "SHA256" }, { "YARP.Backend.Healthcheck.Active.Enabled", "true" }, { "YARP.Backend.Healthcheck.Active.Interval", "5" }, { "YARP.Backend.Healthcheck.Active.Timeout", "5" }, { "YARP.Backend.Healthcheck.Active.Path", "/api/health" }, { "YARP.Backend.Metadata.Foo", "Bar" }, }; // Act var cluster = LabelsParser.BuildCluster(_testServiceName, labels); // Assert var expectedCluster = new Cluster { Id = "MyCoolClusterId", LoadBalancing = new LoadBalancingOptions(), HealthCheck = new HealthCheckOptions { Active = new ActiveHealthCheckOptions { Enabled = true, Interval = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5), Path = "/api/health", }, }, Metadata = new Dictionary <string, string> { { "Foo", "Bar" }, }, }; cluster.Should().BeEquivalentTo(expectedCluster); }
public async void ExecuteAsync_SingleServiceWithGatewayEnabled_OneClusterFound() { // Setup _scenarioOptions = new ServiceFabricDiscoveryOptions { ReportReplicasHealth = true }; const string TestClusterId = "MyService123"; var labels = SFTestHelpers.DummyLabels(TestClusterId); ApplicationWrapper application, anotherApplication; Mock_AppsResponse( application = CreateApp_1StatelessService_2Partition_2ReplicasEach("MyApp", "MYService", out var service, out var replicas), anotherApplication = CreateApp_1StatelessService_2Partition_2ReplicasEach("AnotherApp", "AnotherService", out var anotherService, out var otherReplicas)); Mock_ServiceLabels(application, service, labels); Mock_ServiceLabels(anotherApplication, anotherService, new Dictionary <string, string>()); // Act var(routes, clusters) = await RunScenarioAsync(); // Assert var expectedClusters = new[] { ClusterWithDestinations( LabelsParser.BuildCluster(_testServiceName, labels), SFTestHelpers.BuildDestinationFromReplica(replicas[0]), SFTestHelpers.BuildDestinationFromReplica(replicas[1]), SFTestHelpers.BuildDestinationFromReplica(replicas[2]), SFTestHelpers.BuildDestinationFromReplica(replicas[3])), }; var expectedRoutes = LabelsParser.BuildRoutes(_testServiceName, labels); routes.Should().BeEquivalentTo(expectedRoutes); clusters.Should().BeEquivalentTo(expectedClusters); AssertServiceHealthReported(service, HealthState.Ok); foreach (var replica in replicas) { AssertStatelessServiceInstanceHealthReported(replica, HealthState.Ok); } _healthReports.Should().HaveCount(5); }
public void BuildCluster_CompleteLabels_Works() { // Arrange var labels = new Dictionary <string, string>() { { "YARP.Enable", "true" }, { "YARP.Backend.BackendId", "MyCoolClusterId" }, { "YARP.Backend.Healthcheck.Active.Enabled", "true" }, { "YARP.Backend.Healthcheck.Active.Interval", "5" }, { "YARP.Backend.Healthcheck.Active.Timeout", "5" }, { "YARP.Backend.Healthcheck.Active.Path", "/api/health" }, { "YARP.Backend.Metadata.Foo", "Bar" }, }; // Act var cluster = LabelsParser.BuildCluster(_testServiceName, labels); // Assert var expectedCluster = new Cluster { Id = "MyCoolClusterId", LoadBalancing = new LoadBalancingOptions(), HealthCheck = new HealthCheckOptions { Active = new ActiveHealthCheckOptions { Enabled = true, Interval = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5), Path = "/api/health", }, }, Metadata = new Dictionary <string, string> { { "Foo", "Bar" }, }, }; cluster.Should().BeEquivalentTo(expectedCluster); }
public async void ExecuteAsync_OneServiceWithGatewayEnabledAndOneNotEnabled_OnlyTheOneEnabledFound() { // Setup _scenarioOptions = new ServiceFabricDiscoveryOptions { ReportReplicasHealth = true }; const string TestClusterIdApp1Sv1 = "MyService123"; const string TestClusterIdApp2Sv2 = "MyService234"; var gatewayEnabledLabels = SFTestHelpers.DummyLabels(TestClusterIdApp1Sv1); var gatewayNotEnabledLabels = SFTestHelpers.DummyLabels(TestClusterIdApp2Sv2, false); ApplicationWrapper application1, application2; Mock_AppsResponse( application1 = CreateApp_1Service_SingletonPartition_1Replica("MyApp", "MyService1", out var service1, out var replica1), application2 = CreateApp_1Service_SingletonPartition_1Replica("MyApp2", "MyService2", out var service2, out var replica2)); Mock_ServiceLabels(application1, service1, gatewayEnabledLabels); Mock_ServiceLabels(application2, service2, gatewayNotEnabledLabels); // Act var(routes, clusters) = await RunScenarioAsync(); // Assert var expectedClusters = new[] { ClusterWithDestinations( LabelsParser.BuildCluster(_testServiceName, gatewayEnabledLabels), SFTestHelpers.BuildDestinationFromReplica(replica1)), }; var expectedRoutes = new List <ProxyRoute>(); expectedRoutes.AddRange(LabelsParser.BuildRoutes(_testServiceName, gatewayEnabledLabels)); clusters.Should().BeEquivalentTo(expectedClusters); routes.Should().BeEquivalentTo(expectedRoutes); AssertServiceHealthReported(service1, HealthState.Ok); AssertStatelessServiceInstanceHealthReported(replica1, HealthState.Ok); _healthReports.Should().HaveCount(2); }
public void BuildCluster_IncompleteLabels_UsesDefaultValues() { var labels = new Dictionary <string, string>() { { "YARP.Backend.BackendId", "MyCoolClusterId" }, }; var cluster = LabelsParser.BuildCluster(_testServiceName, labels, null); var expectedCluster = new Cluster { Id = "MyCoolClusterId", SessionAffinity = new SessionAffinityOptions { Enabled = false, }, HttpRequest = new RequestProxyOptions(), HealthCheck = new HealthCheckOptions { Active = new ActiveHealthCheckOptions { Enabled = false, }, Passive = new PassiveHealthCheckOptions { Enabled = false, } }, Metadata = new Dictionary <string, string>(), HttpClient = new ProxyHttpClientOptions { WebProxy = new WebProxyOptions { } } }; cluster.Should().BeEquivalentTo(expectedCluster); }
public async void ExecuteAsync_NotHttpsSchemeForStatelessService_NoEndpointsAndBadHealthReported() { // Setup _scenarioOptions = new ServiceFabricDiscoveryOptions { ReportReplicasHealth = true }; const string TestClusterId = "MyService123"; const string ServiceName = "fabric:/MyApp/MyService"; var labels = SFTestHelpers.DummyLabels(TestClusterId); labels["YARP.Backend.ServiceFabric.ListenerName"] = "ExampleTeamEndpoint"; ApplicationWrapper application; Mock_AppsResponse( application = CreateApp_1Service_SingletonPartition_1Replica("MyApp", "MyService", out var service, out var replica, serviceKind: ServiceKind.Stateless)); var nonHttpAddress = $"http://127.0.0.1/{ServiceName}/0"; replica.ReplicaAddress = $"{{'Endpoints': {{'ExampleTeamEndpoint': '{nonHttpAddress}' }} }}".Replace("'", "\""); Mock_ServiceLabels(application, service, labels); // Act var(routes, clusters) = await RunScenarioAsync(); // Assert var expectedClusters = new[] { LabelsParser.BuildCluster(_testServiceName, labels), }; var expectedRoutes = LabelsParser.BuildRoutes(_testServiceName, labels); clusters.Should().BeEquivalentTo(expectedClusters); routes.Should().BeEquivalentTo(expectedRoutes); AssertServiceHealthReported(service, HealthState.Ok); AssertStatelessServiceInstanceHealthReported(replica, HealthState.Warning, (description) => description.StartsWith("Could not build service endpoint") && description.Contains("ExampleTeamEndpoint")); _healthReports.Should().HaveCount(2); }
public async void ExecuteAsync_InvalidRouteOrder_NoRoutesAndBadHealthReported() { // Setup _scenarioOptions = new ServiceFabricDiscoveryOptions { ReportReplicasHealth = true }; var labels = new Dictionary <string, string>() { { "YARP.Enable", "true" }, { "YARP.Backend.BackendId", "SomeClusterId" }, { "YARP.Routes.MyRoute.Hosts", "example.com" }, { "YARP.Routes.MyRoute.Order", "not a number" }, }; ApplicationWrapper application; Mock_AppsResponse( application = CreateApp_1Service_SingletonPartition_1Replica("MyApp", "MyService", out var service, out var replica)); Mock_ServiceLabels(application, service, labels); // Act var(routes, clusters) = await RunScenarioAsync(); // Assert var expectedClusters = new[] { ClusterWithDestinations( LabelsParser.BuildCluster(_testServiceName, labels), SFTestHelpers.BuildDestinationFromReplica(replica)), }; var expectedRoutes = new List <ProxyRoute>(); clusters.Should().BeEquivalentTo(expectedClusters); routes.Should().BeEmpty(); AssertServiceHealthReported(service, HealthState.Warning, (description) => description.Contains("Order")); // Check that the invalid key is mentioned in the description _healthReports.Should().HaveCount(2); }
public async void ExecuteAsync_ValidListenerNameForStatelessService_Work() { // Setup _scenarioOptions = new ServiceFabricDiscoveryOptions { ReportReplicasHealth = true }; const string TestClusterId = "MyService123"; var labels = SFTestHelpers.DummyLabels(TestClusterId); labels["YARP.Backend.ServiceFabric.ListenerName"] = "ExampleTeamEndpoint"; labels["YARP.Backend.Healthcheck.Active.ServiceFabric.ListenerName"] = "ExampleTeamHealthEndpoint"; ApplicationWrapper application; Mock_AppsResponse( application = CreateApp_1Service_SingletonPartition_1Replica("MyApp", "MyService", out var service, out var replica, serviceKind: ServiceKind.Stateless)); replica.ReplicaAddress = MockReplicaAdressWithListenerName("MyApp", "MyService", new string[] { "ExampleTeamEndpoint", "ExampleTeamHealthEndpoint" }); Mock_ServiceLabels(application, service, labels); // Act var(routes, clusters) = await RunScenarioAsync(); // Assert var expectedClusters = new[] { ClusterWithDestinations( LabelsParser.BuildCluster(_testServiceName, labels), SFTestHelpers.BuildDestinationFromReplica(replica, "ExampleTeamHealthEndpoint")), }; var expectedRoutes = LabelsParser.BuildRoutes(_testServiceName, labels); clusters.Should().BeEquivalentTo(expectedClusters); routes.Should().BeEquivalentTo(expectedRoutes); AssertServiceHealthReported(service, HealthState.Ok); AssertStatelessServiceInstanceHealthReported(replica, HealthState.Ok, (description) => description.StartsWith("Successfully built")); _healthReports.Should().HaveCount(2); }
public void BuildCluster_CompleteLabels_Works() { var labels = new Dictionary <string, string>() { { "YARP.Enable", "true" }, { "YARP.Backend.BackendId", "MyCoolClusterId" }, { "YARP.Backend.LoadBalancingPolicy", "LeastRequests" }, { "YARP.Backend.SessionAffinity.Enabled", "true" }, { "YARP.Backend.SessionAffinity.Policy", "Cookie" }, { "YARP.Backend.SessionAffinity.FailurePolicy", "Return503Error" }, { "YARP.Backend.SessionAffinity.AffinityKeyName", "Key1" }, { "YARP.Backend.SessionAffinity.Cookie.Domain", "localhost" }, { "YARP.Backend.SessionAffinity.Cookie.Expiration", "03:00:00" }, { "YARP.Backend.SessionAffinity.Cookie.HttpOnly", "true" }, { "YARP.Backend.SessionAffinity.Cookie.IsEssential", "true" }, { "YARP.Backend.SessionAffinity.Cookie.MaxAge", "1.00:00:00" }, { "YARP.Backend.SessionAffinity.Cookie.Path", "mypath" }, { "YARP.Backend.SessionAffinity.Cookie.SameSite", "Strict" }, { "YARP.Backend.SessionAffinity.Cookie.SecurePolicy", "SameAsRequest" }, { "YARP.Backend.HttpRequest.ActivityTimeout", "00:00:17" }, { "YARP.Backend.HttpRequest.AllowResponseBuffering", "true" }, { "YARP.Backend.HttpRequest.Version", "1.1" }, #if NET { "YARP.Backend.HttpRequest.VersionPolicy", "RequestVersionExact" }, #endif { "YARP.Backend.HealthCheck.Active.Enabled", "true" }, { "YARP.Backend.HealthCheck.Active.Interval", "00:00:05" }, { "YARP.Backend.HealthCheck.Active.Timeout", "00:00:06" }, { "YARP.Backend.HealthCheck.Active.Policy", "MyActiveHealthPolicy" }, { "YARP.Backend.HealthCheck.Active.Path", "/api/health" }, { "YARP.Backend.HealthCheck.Passive.Enabled", "true" }, { "YARP.Backend.HealthCheck.Passive.Policy", "MyPassiveHealthPolicy" }, { "YARP.Backend.HealthCheck.Passive.ReactivationPeriod", "00:00:07" }, { "YARP.Backend.Metadata.Foo", "Bar" }, { "YARP.Backend.HttpClient.DangerousAcceptAnyServerCertificate", "true" }, { "YARP.Backend.HttpClient.MaxConnectionsPerServer", "1000" }, { "YARP.Backend.HttpClient.SslProtocols", "Tls12" }, { "YARP.Backend.HttpClient.ActivityContextHeaders", "BaggageAndCorrelationContext" }, #if NET { "YARP.Backend.HttpClient.EnableMultipleHttp2Connections", "false" }, { "YARP.Backend.HttpClient.RequestHeaderEncoding", "utf-8" }, #endif { "YARP.Backend.HttpClient.WebProxy.Address", "https://10.20.30.40" }, { "YARP.Backend.HttpClient.WebProxy.BypassOnLocal", "true" }, { "YARP.Backend.HttpClient.WebProxy.UseDefaultCredentials", "false" }, }; var cluster = LabelsParser.BuildCluster(_testServiceName, labels, null); var expectedCluster = new ClusterConfig { ClusterId = "MyCoolClusterId", LoadBalancingPolicy = LoadBalancingPolicies.LeastRequests, SessionAffinity = new SessionAffinityConfig { Enabled = true, Policy = SessionAffinityConstants.Policies.Cookie, FailurePolicy = SessionAffinityConstants.FailurePolicies.Return503Error, AffinityKeyName = "Key1", Cookie = new SessionAffinityCookieConfig { Domain = "localhost", Expiration = TimeSpan.FromHours(3), HttpOnly = true, IsEssential = true, MaxAge = TimeSpan.FromDays(1), Path = "mypath", SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict, SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.SameAsRequest } }, HttpRequest = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(17), Version = new Version(1, 1), AllowResponseBuffering = true, #if NET VersionPolicy = System.Net.Http.HttpVersionPolicy.RequestVersionExact #endif }, HealthCheck = new HealthCheckConfig { Active = new ActiveHealthCheckConfig { Enabled = true, Interval = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(6), Path = "/api/health", Policy = "MyActiveHealthPolicy" }, Passive = new PassiveHealthCheckConfig { Enabled = true, Policy = "MyPassiveHealthPolicy", ReactivationPeriod = TimeSpan.FromSeconds(7) } }, Metadata = new Dictionary <string, string> { { "Foo", "Bar" }, }, HttpClient = new HttpClientConfig { ActivityContextHeaders = ActivityContextHeaders.BaggageAndCorrelationContext, DangerousAcceptAnyServerCertificate = true, #if NET EnableMultipleHttp2Connections = false, RequestHeaderEncoding = "utf-8", #endif MaxConnectionsPerServer = 1000, SslProtocols = SslProtocols.Tls12, WebProxy = new WebProxyConfig { Address = new Uri("https://10.20.30.40"), BypassOnLocal = true, UseDefaultCredentials = false, } } }; cluster.Should().BeEquivalentTo(expectedCluster); }
public void BuildCluster_CompleteLabels_Works() { var labels = new Dictionary <string, string>() { { "YARP.Enable", "true" }, { "YARP.Backend.BackendId", "MyCoolClusterId" }, { "YARP.Backend.LoadBalancing.Mode", "LeastRequests" }, { "YARP.Backend.SessionAffinity.Enabled", "true" }, { "YARP.Backend.SessionAffinity.Mode", "Cookie" }, { "YARP.Backend.SessionAffinity.FailurePolicy", "Return503Error" }, { "YARP.Backend.SessionAffinity.Settings.ParameterA", "ValueA" }, { "YARP.Backend.SessionAffinity.Settings.ParameterB", "ValueB" }, { "YARP.Backend.HttpRequest.Timeout", "17" }, { "YARP.Backend.HttpRequest.Version", "1.1" }, #if NET { "YARP.Backend.HttpRequest.VersionPolicy", "RequestVersionExact" }, #endif { "YARP.Backend.HealthCheck.Active.Enabled", "true" }, { "YARP.Backend.HealthCheck.Active.Interval", "5" }, { "YARP.Backend.HealthCheck.Active.Timeout", "6" }, { "YARP.Backend.HealthCheck.Active.Policy", "MyActiveHealthPolicy" }, { "YARP.Backend.HealthCheck.Active.Path", "/api/health" }, { "YARP.Backend.HealthCheck.Passive.Enabled", "true" }, { "YARP.Backend.HealthCheck.Passive.Policy", "MyPassiveHealthPolicy" }, { "YARP.Backend.HealthCheck.Passive.ReactivationPeriod", "7" }, { "YARP.Backend.Metadata.Foo", "Bar" }, }; var cluster = LabelsParser.BuildCluster(_testServiceName, labels); var expectedCluster = new Cluster { Id = "MyCoolClusterId", LoadBalancing = new LoadBalancingOptions { Mode = LoadBalancingMode.LeastRequests }, SessionAffinity = new SessionAffinityOptions { Enabled = true, Mode = SessionAffinityConstants.Modes.Cookie, FailurePolicy = SessionAffinityConstants.AffinityFailurePolicies.Return503Error, Settings = new Dictionary <string, string> { { "ParameterA", "ValueA" }, { "ParameterB", "ValueB" } } }, HttpRequest = new ProxyHttpRequestOptions { Timeout = TimeSpan.FromSeconds(17), Version = new Version(1, 1), #if NET VersionPolicy = System.Net.Http.HttpVersionPolicy.RequestVersionExact #endif }, HealthCheck = new HealthCheckOptions { Active = new ActiveHealthCheckOptions { Enabled = true, Interval = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(6), Path = "/api/health", Policy = "MyActiveHealthPolicy" }, Passive = new PassiveHealthCheckOptions { Enabled = true, Policy = "MyPassiveHealthPolicy", ReactivationPeriod = TimeSpan.FromSeconds(7) } }, Metadata = new Dictionary <string, string> { { "Foo", "Bar" }, }, }; cluster.Should().BeEquivalentTo(expectedCluster); }
public void BuildCluster_CompleteLabels_Works() { var labels = new Dictionary <string, string>() { { "YARP.Enable", "true" }, { "YARP.Backend.BackendId", "MyCoolClusterId" }, { "YARP.Backend.LoadBalancingPolicy", "LeastRequests" }, { "YARP.Backend.SessionAffinity.Enabled", "true" }, { "YARP.Backend.SessionAffinity.Mode", "Cookie" }, { "YARP.Backend.SessionAffinity.FailurePolicy", "Return503Error" }, { "YARP.Backend.SessionAffinity.Settings.ParameterA", "ValueA" }, { "YARP.Backend.SessionAffinity.Settings.ParameterB", "ValueB" }, { "YARP.Backend.HttpRequest.Timeout", "00:00:17" }, { "YARP.Backend.HttpRequest.Version", "1.1" }, #if NET { "YARP.Backend.HttpRequest.VersionPolicy", "RequestVersionExact" }, #endif { "YARP.Backend.HealthCheck.Active.Enabled", "true" }, { "YARP.Backend.HealthCheck.Active.Interval", "00:00:05" }, { "YARP.Backend.HealthCheck.Active.Timeout", "00:00:06" }, { "YARP.Backend.HealthCheck.Active.Policy", "MyActiveHealthPolicy" }, { "YARP.Backend.HealthCheck.Active.Path", "/api/health" }, { "YARP.Backend.HealthCheck.Passive.Enabled", "true" }, { "YARP.Backend.HealthCheck.Passive.Policy", "MyPassiveHealthPolicy" }, { "YARP.Backend.HealthCheck.Passive.ReactivationPeriod", "00:00:07" }, { "YARP.Backend.Metadata.Foo", "Bar" }, { "YARP.Backend.HttpClient.DangerousAcceptAnyServerCertificate", "true" }, { "YARP.Backend.HttpClient.MaxConnectionsPerServer", "1000" }, { "YARP.Backend.HttpClient.SslProtocols", "Tls12" }, { "YARP.Backend.HttpClient.ActivityContextHeaders", "BaggageAndCorrelationContext" }, #if NET { "YARP.Backend.HttpClient.EnableMultipleHttp2Connections", "false" }, { "YARP.Backend.HttpClient.RequestHeaderEncoding", "utf-8" }, #endif }; var cluster = LabelsParser.BuildCluster(_testServiceName, labels, null); var expectedCluster = new Cluster { Id = "MyCoolClusterId", LoadBalancingPolicy = LoadBalancingPolicies.LeastRequests, SessionAffinity = new SessionAffinityOptions { Enabled = true, Mode = SessionAffinityConstants.Modes.Cookie, FailurePolicy = SessionAffinityConstants.AffinityFailurePolicies.Return503Error, Settings = new Dictionary <string, string> { { "ParameterA", "ValueA" }, { "ParameterB", "ValueB" } } }, HttpRequest = new RequestProxyOptions { Timeout = TimeSpan.FromSeconds(17), Version = new Version(1, 1), #if NET VersionPolicy = System.Net.Http.HttpVersionPolicy.RequestVersionExact #endif }, HealthCheck = new HealthCheckOptions { Active = new ActiveHealthCheckOptions { Enabled = true, Interval = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(6), Path = "/api/health", Policy = "MyActiveHealthPolicy" }, Passive = new PassiveHealthCheckOptions { Enabled = true, Policy = "MyPassiveHealthPolicy", ReactivationPeriod = TimeSpan.FromSeconds(7) } }, Metadata = new Dictionary <string, string> { { "Foo", "Bar" }, }, HttpClient = new ProxyHttpClientOptions { ActivityContextHeaders = ActivityContextHeaders.BaggageAndCorrelationContext, DangerousAcceptAnyServerCertificate = true, #if NET EnableMultipleHttp2Connections = false, RequestHeaderEncoding = Encoding.GetEncoding("utf-8"), #endif MaxConnectionsPerServer = 1000, SslProtocols = SslProtocols.Tls12 } }; cluster.Should().BeEquivalentTo(expectedCluster); }
/// <inheritdoc/> public async Task<(IReadOnlyList<RouteConfig> Routes, IReadOnlyList<ClusterConfig> Clusters)> DiscoverAsync(CancellationToken cancellation) { // Take a snapshot of current options and use that consistently for this execution. var options = _optionsMonitor.CurrentValue; _serviceFabricCaller.CleanUpExpired(); var discoveredBackends = new Dictionary<string, ClusterConfig>(StringComparer.Ordinal); var discoveredRoutes = new List<RouteConfig>(); IEnumerable<ApplicationWrapper> applications; try { applications = await _serviceFabricCaller.GetApplicationListAsync(cancellation); } catch (OperationCanceledException) when (cancellation.IsCancellationRequested) { throw; } catch (Exception ex) // TODO: davidni: not fatal? { // The serviceFabricCaller does their best effort to use LKG information, nothing we can do at this point Log.GettingApplicationFailed(_logger, ex); applications = Enumerable.Empty<ApplicationWrapper>(); } foreach (var application in applications) { IEnumerable<ServiceWrapper> services; try { services = await _serviceFabricCaller.GetServiceListAsync(application.ApplicationName, cancellation); } catch (OperationCanceledException) when (cancellation.IsCancellationRequested) { throw; } catch (Exception ex) // TODO: davidni: not fatal? { Log.GettingServiceFailed(_logger, application.ApplicationName, ex); continue; } foreach (var service in services) { try { var serviceExtensionLabels = await _serviceFabricExtensionConfigProvider.GetExtensionLabelsAsync(application, service, cancellation); // If this service wants to use us as the proxy if (serviceExtensionLabels.GetValueOrDefault("YARP.Enable", null) != "true") { // Skip this service continue; } var destinations = await DiscoverDestinationsAsync(options, service, serviceExtensionLabels, cancellation); var cluster = LabelsParser.BuildCluster(service.ServiceName, serviceExtensionLabels, destinations); var clusterValidationErrors = await _configValidator.ValidateClusterAsync(cluster); if (clusterValidationErrors.Count > 0) { throw new ConfigException($"Skipping cluster id '{cluster.ClusterId} due to validation errors.", new AggregateException(clusterValidationErrors)); } if (!discoveredBackends.TryAdd(cluster.ClusterId, cluster)) { throw new ConfigException($"Duplicated cluster id '{cluster.ClusterId}'. Skipping repeated definition, service '{service.ServiceName}'"); } var routes = LabelsParser.BuildRoutes(service.ServiceName, serviceExtensionLabels); var routeValidationErrors = new List<Exception>(); foreach (var route in routes) { routeValidationErrors.AddRange(await _configValidator.ValidateRouteAsync(route)); } if (routeValidationErrors.Count > 0) { // Don't add ANY routes if even a single one is bad. Trying to add partial routes // could lead to unexpected results (e.g. a typo in the configuration of higher-priority route // could lead to a lower-priority route being selected for requests it should not be handling). throw new ConfigException($"Skipping ALL routes for cluster id '{cluster.ClusterId} due to validation errors.", new AggregateException(routeValidationErrors)); } discoveredRoutes.AddRange(routes); ReportServiceHealth(options, service.ServiceName, HealthState.Ok, $"Successfully built cluster '{cluster.ClusterId}' with {routes.Count} routes."); } catch (ConfigException ex) { // User error Log.InvalidServiceConfig(_logger, service.ServiceName, ex); // TODO: emit Error health report once we are able to detect config issues *during* (as opposed to *after*) a target service upgrade. // Proactive Error health report would trigger a rollback of the target service as desired. However, an Error report after rhe fact // will NOT cause a rollback and will prevent the target service from performing subsequent monitored upgrades to mitigate, making things worse. ReportServiceHealth(options, service.ServiceName, HealthState.Warning, $"Could not load service configuration: {ex.Message}."); } catch (Exception ex) // TODO: davidni: not fatal? { // Not user's problem Log.ErrorLoadingServiceConfig(_logger, service.ServiceName, ex); } } } Log.ServiceDiscovered(_logger, discoveredBackends.Count, discoveredRoutes.Count); return (discoveredRoutes, discoveredBackends.Values.ToList()); }