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); }
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); }
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); } }
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); }
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); }
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]); }
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);
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); }
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)); }
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); }
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); }