protected virtual string GetSettingValue(string key, SessionAffinityOptions options)
        {
            if (options.Settings == null || !options.Settings.TryGetValue(key, out var value))
            {
                throw new ArgumentException($"{nameof(CookieSessionAffinityProvider)} couldn't find the required parameter {key} in session affinity settings.", nameof(options));
            }

            return(value);
        }
        public Task <bool> Handle(HttpContext context, SessionAffinityOptions options, AffinityStatus affinityStatus)
        {
            if (affinityStatus == AffinityStatus.OK ||
                affinityStatus == AffinityStatus.AffinityKeyNotSet)
            {
                throw new InvalidOperationException($"{nameof(Return503ErrorAffinityFailurePolicy)} is called to handle a successful request's affinity status {affinityStatus}.");
            }

            context.Response.StatusCode = 503;
            return(TaskUtilities.FalseTask);
        }
        public Task <bool> Handle(HttpContext context, SessionAffinityOptions options, AffinityStatus affinityStatus)
        {
            if (affinityStatus == AffinityStatus.OK ||
                affinityStatus == AffinityStatus.AffinityKeyNotSet)
            {
                throw new InvalidOperationException($"{nameof(RedistributeAffinityFailurePolicy)} is called to handle a successful request's affinity status {affinityStatus}.");
            }

            // Available destinations list have not been changed in the context,
            // so simply allow processing to proceed to load balancing.
            return(TaskUtilities.TrueTask);
        }
Exemple #4
0
        public void Equals_Second_Null_Returns_False()
        {
            var options1 = new SessionAffinityOptions
            {
                Enabled       = true,
                FailurePolicy = "policy1",
                Mode          = "mode1"
            };

            var equals = options1.Equals(null);

            Assert.False(equals);
        }
Exemple #5
0
        public void FindAffinitizedDestination_AffinityDisabledOnCluster_ReturnsAffinityDisabled()
        {
            var provider = new ProviderStub(GetDataProtector().Object, AffinityTestHelper.GetLogger <BaseSessionAffinityProvider <string> >().Object);
            var options  = new SessionAffinityOptions
            {
                Enabled       = false,
                Mode          = _defaultOptions.Mode,
                FailurePolicy = _defaultOptions.FailurePolicy,
                Settings      = _defaultOptions.Settings,
            };

            Assert.Throws <InvalidOperationException>(() => provider.FindAffinitizedDestinations(new DefaultHttpContext(), new[] { new DestinationInfo("1") }, "cluster-1", options));
        }
        public virtual void AffinitizeRequest(HttpContext context, SessionAffinityOptions options, DestinationInfo destination)
        {
            if (!options.Enabled.GetValueOrDefault())
            {
                throw new InvalidOperationException($"Session affinity is disabled for cluster.");
            }

            // Affinity key is set on the response only if it's a new affinity.
            if (!context.Items.ContainsKey(AffinityKeyId))
            {
                var affinityKey = GetDestinationAffinityKey(destination);
                SetAffinityKey(context, options, affinityKey);
            }
        }
Exemple #7
0
        public void Equals_Different_Value_Returns_False()
        {
            var options1 = new SessionAffinityOptions
            {
                Enabled       = true,
                FailurePolicy = "policy1",
                Mode          = "mode1"
            };

            var options2 = new SessionAffinityOptions
            {
                Enabled       = false,
                FailurePolicy = "policy2",
                Mode          = "mode2"
            };

            var equals = options1.Equals(options2);

            Assert.False(equals);
        }
Exemple #8
0
        public void Equals_Same_Value_Returns_True()
        {
            var options1 = new SessionAffinityOptions
            {
                Enabled       = true,
                FailurePolicy = "policy1",
                Mode          = "mode1"
            };

            var options2 = new SessionAffinityOptions
            {
                Enabled       = true,
                FailurePolicy = "policy1",
                Mode          = "mode1"
            };

            var equals = options1.Equals(options2);

            Assert.True(equals);
        }
Exemple #9
0
        public void FindAffinitizedDestination_CustomHeaderNameIsNotSpecified_UseDefaultName(Dictionary <string, string> settings)
        {
            var options = new SessionAffinityOptions
            {
                Enabled       = true,
                Mode          = "CustomHeader",
                FailurePolicy = "Return503",
                Settings      = settings,
            };
            var provider = new CustomHeaderSessionAffinityProvider(AffinityTestHelper.GetDataProtector().Object, AffinityTestHelper.GetLogger <CustomHeaderSessionAffinityProvider>().Object);
            var context  = new DefaultHttpContext();
            var affinitizedDestination = _destinations[1];

            context.Request.Headers[CustomHeaderSessionAffinityProvider.DefaultCustomHeaderName] = new[] { affinitizedDestination.DestinationId.ToUTF8BytesInBase64() };

            var affinityResult = provider.FindAffinitizedDestinations(context, _destinations, "cluster-1", options);

            Assert.Equal(AffinityStatus.OK, affinityResult.Status);
            Assert.Equal(1, affinityResult.Destinations.Count);
            Assert.Same(affinitizedDestination, affinityResult.Destinations[0]);
        }
Exemple #10
0
        protected override void SetAffinityKey(HttpContext context, SessionAffinityOptions options, string unencryptedKey)
        {
            var customHeaderName = GetSettingValue(CustomHeaderNameKey, options);

            context.Response.Headers.Append(customHeaderName, Protect(unencryptedKey));
        }
        public virtual AffinityResult FindAffinitizedDestinations(HttpContext context, IReadOnlyList <DestinationInfo> destinations, string clusterId, SessionAffinityOptions options)
        {
            if (!options.Enabled.GetValueOrDefault())
            {
                throw new InvalidOperationException($"Session affinity is disabled for cluster {clusterId}.");
            }

            var requestAffinityKey = GetRequestAffinityKey(context, options);

            if (requestAffinityKey.Key == null)
            {
                return(new AffinityResult(null, requestAffinityKey.ExtractedSuccessfully ? AffinityStatus.AffinityKeyNotSet : AffinityStatus.AffinityKeyExtractionFailed));
            }

            IReadOnlyList <DestinationInfo> matchingDestinations = null;

            if (destinations.Count > 0)
            {
                for (var i = 0; i < destinations.Count; i++)
                {
                    // TODO: Add fast destination lookup by ID
                    if (requestAffinityKey.Key.Equals(GetDestinationAffinityKey(destinations[i])))
                    {
                        // It's allowed to affinitize a request to a pool of destinations so as to enable load-balancing among them.
                        // However, we currently stop after the first match found to avoid performance degradation.
                        matchingDestinations = destinations[i];
                        break;
                    }
                }

                if (matchingDestinations == null)
                {
                    Log.DestinationMatchingToAffinityKeyNotFound(Logger, clusterId);
                }
            }
            else
            {
                Log.AffinityCannotBeEstablishedBecauseNoDestinationsFound(Logger, clusterId);
            }

            // Empty destination list passed to this method is handled the same way as if no matching destinations are found.
            if (matchingDestinations == null)
            {
                return(new AffinityResult(null, AffinityStatus.DestinationNotFound));
            }

            context.Items[AffinityKeyId] = requestAffinityKey;
            return(new AffinityResult(matchingDestinations, AffinityStatus.OK));
        }
 protected abstract void SetAffinityKey(HttpContext context, SessionAffinityOptions options, T unencryptedKey);
 protected abstract (T Key, bool ExtractedSuccessfully) GetRequestAffinityKey(HttpContext context, SessionAffinityOptions options);
Exemple #14
0
        protected override void SetAffinityKey(HttpContext context, SessionAffinityOptions options, string unencryptedKey)
        {
            var affinityCookieOptions = _providerOptions.Cookie.Build(context);

            context.Response.Cookies.Append(_providerOptions.Cookie.Name, Protect(unencryptedKey), affinityCookieOptions);
        }
Exemple #15
0
        protected override (string Key, bool ExtractedSuccessfully) GetRequestAffinityKey(HttpContext context, SessionAffinityOptions options)
        {
            var encryptedRequestKey = context.Request.Cookies.TryGetValue(_providerOptions.Cookie.Name, out var keyInCookie) ? keyInCookie : null;

            return(Unprotect(encryptedRequestKey));
        }
Exemple #16
0
        protected override (string Key, bool ExtractedSuccessfully) GetRequestAffinityKey(HttpContext context, SessionAffinityOptions options)
        {
            var customHeaderName = options.Settings != null && options.Settings.TryGetValue(CustomHeaderNameKey, out var nameInSettings) ? nameInSettings : DefaultCustomHeaderName;
            var keyHeaderValues  = context.Request.Headers[customHeaderName];

            if (StringValues.IsNullOrEmpty(keyHeaderValues))
            {
                // It means affinity key is not defined that is a successful case
                return(Key : null, ExtractedSuccessfully : true);
            }

            if (keyHeaderValues.Count > 1)
            {
                // Multiple values is an ambiguous case which is considered a key extraction failure
                Log.RequestAffinityHeaderHasMultipleValues(Logger, customHeaderName, keyHeaderValues.Count);
                return(Key : null, ExtractedSuccessfully : false);
            }

            return(Unprotect(keyHeaderValues[0]));
        }
        private async Task InvokeInternal(HttpContext context, IReverseProxyFeature proxyFeature, SessionAffinityOptions options, string clusterId)
        {
            var destinations = proxyFeature.AvailableDestinations;

            var currentProvider = _sessionAffinityProviders.GetRequiredServiceById(options.Mode, SessionAffinityConstants.Modes.Cookie);
            var affinityResult  = currentProvider.FindAffinitizedDestinations(context, destinations, clusterId, options);

            switch (affinityResult.Status)
            {
            case AffinityStatus.OK:
                proxyFeature.AvailableDestinations = affinityResult.Destinations;
                break;

            case AffinityStatus.AffinityKeyNotSet:
                // Nothing to do so just continue processing
                break;

            case AffinityStatus.AffinityKeyExtractionFailed:
            case AffinityStatus.DestinationNotFound:

                var failurePolicy  = _affinityFailurePolicies.GetRequiredServiceById(options.FailurePolicy, SessionAffinityConstants.AffinityFailurePolicies.Redistribute);
                var keepProcessing = await failurePolicy.Handle(context, options, affinityResult.Status);

                if (!keepProcessing)
                {
                    // Policy reported the failure is unrecoverable and took the full responsibility for its handling,
                    // so we simply stop processing.
                    Log.AffinityResolutionFailedForCluster(_logger, clusterId);
                    return;
                }

                Log.AffinityResolutionFailureWasHandledProcessingWillBeContinued(_logger, clusterId, options.FailurePolicy);

                break;

            default:
                throw new NotSupportedException($"Affinity status '{affinityResult.Status}' is not supported.");
            }

            await _next(context);
        }
Exemple #18
0
        public void Equals_Different_Value_Returns_False()
        {
            var options1 = new Cluster
            {
                Id           = "cluster1",
                Destinations = new Dictionary <string, Destination>(StringComparer.OrdinalIgnoreCase)
                {
                    {
                        "destinationA",
                        new Destination
                        {
                            Address  = "https://localhost:10000/destA",
                            Health   = "https://localhost:20000/destA",
                            Metadata = new Dictionary <string, string> {
                                { "destA-K1", "destA-V1" }, { "destA-K2", "destA-V2" }
                            }
                        }
                    },
                    {
                        "destinationB",
                        new Destination
                        {
                            Address  = "https://localhost:10000/destB",
                            Health   = "https://localhost:20000/destB",
                            Metadata = new Dictionary <string, string> {
                                { "destB-K1", "destB-V1" }, { "destB-K2", "destB-V2" }
                            }
                        }
                    }
                },
                HealthCheck = new HealthCheckOptions
                {
                    Passive = new PassiveHealthCheckOptions
                    {
                        Enabled            = true,
                        Policy             = "FailureRate",
                        ReactivationPeriod = TimeSpan.FromMinutes(5)
                    },
                    Active = new ActiveHealthCheckOptions
                    {
                        Enabled  = true,
                        Interval = TimeSpan.FromSeconds(4),
                        Timeout  = TimeSpan.FromSeconds(6),
                        Policy   = "Any5xxResponse",
                        Path     = "healthCheckPath"
                    }
                },
                LoadBalancingPolicy = LoadBalancingPolicies.Random,
                SessionAffinity     = new SessionAffinityOptions
                {
                    Enabled       = true,
                    FailurePolicy = "Return503Error",
                    Mode          = "Cookie",
                    Settings      = new Dictionary <string, string> {
                        { "affinity1-K1", "affinity1-V1" }, { "affinity1-K2", "affinity1-V2" }
                    }
                },
                HttpClient = new ProxyHttpClientOptions
                {
                    SslProtocols                        = SslProtocols.Tls11 | SslProtocols.Tls12,
                    MaxConnectionsPerServer             = 10,
                    DangerousAcceptAnyServerCertificate = true,
                    PropagateActivityContext            = true,
                },
                HttpRequest = new RequestProxyOptions
                {
                    Timeout = TimeSpan.FromSeconds(60),
                    Version = Version.Parse("1.0"),
#if NET
                    VersionPolicy = HttpVersionPolicy.RequestVersionExact,
#endif
                },
                Metadata = new Dictionary <string, string> {
                    { "cluster1-K1", "cluster1-V1" }, {
                        "cluster1-K2", "cluster1-V2"
                    }
                }
            };

            Assert.False(options1.Equals(options1 with {
                Id = "different"
            }));
            Assert.False(options1.Equals(options1 with {
                Destinations = new Dictionary <string, Destination>()
            }));
            Assert.False(options1.Equals(options1 with {
                HealthCheck = new HealthCheckOptions()
            }));
            Assert.False(options1.Equals(options1 with {
                LoadBalancingPolicy = "different"
            }));
            Assert.False(options1.Equals(options1 with
            {
                SessionAffinity = new SessionAffinityOptions
                {
                    Enabled       = true,
                    FailurePolicy = "Return503Error",
                    Mode          = "Cookie",
                    Settings      = new Dictionary <string, string> {
                        { "affinity1-K1", "affinity1-V1" }
                    }
                }
            }));
            Assert.False(options1.Equals(options1 with
            {
                HttpClient = new ProxyHttpClientOptions
                {
                    SslProtocols                        = SslProtocols.Tls12,
                    MaxConnectionsPerServer             = 10,
                    DangerousAcceptAnyServerCertificate = true,
                    PropagateActivityContext            = true,
                }
            }));
            Assert.False(options1.Equals(options1 with {
                HttpRequest = new RequestProxyOptions()
                {
                }
            }));
            Assert.False(options1.Equals(options1 with {
                Metadata = null
            }));
        }
        private void AffinitizeRequest(HttpContext context, SessionAffinityOptions options, DestinationInfo destination)
        {
            var currentProvider = _sessionAffinityProviders.GetRequiredServiceById(options.Mode, SessionAffinityConstants.Modes.Cookie);

            currentProvider.AffinitizeRequest(context, options, destination);
        }