Exemple #1
0
        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);
        }
Exemple #4
0
        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);
        }
Exemple #13
0
    /// <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());
    }