Feature/move polly (#561)

* added delegate to select last handler

* #529 implemented a way we can inject the last delegating handler

* wip - moving code

* #529 removed loads of qos code and moved it into Ocelot.Provider.Polly
This commit is contained in:
Tom Pallister
2018-08-19 10:14:15 +01:00
committed by GitHub
parent e909cf9ce7
commit 98ba0271be
27 changed files with 213 additions and 669 deletions

View File

@ -1,32 +1,33 @@
using Polly.Timeout;
namespace Ocelot.Configuration
{
public class QoSOptions
{
public QoSOptions(
int exceptionsAllowedBeforeBreaking,
int durationofBreak,
int timeoutValue,
string key,
TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic)
{
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
DurationOfBreak = durationofBreak;
TimeoutValue = timeoutValue;
TimeoutStrategy = timeoutStrategy;
Key = key;
}
public int ExceptionsAllowedBeforeBreaking { get; }
public int DurationOfBreak { get; }
public int TimeoutValue { get; }
public TimeoutStrategy TimeoutStrategy { get; }
public bool UseQos => ExceptionsAllowedBeforeBreaking > 0 && TimeoutValue > 0;
public string Key { get; }
}
}
namespace Ocelot.Configuration
{
public class QoSOptions
{
public QoSOptions(
int exceptionsAllowedBeforeBreaking,
int durationofBreak,
int timeoutValue,
string key,
//todo - this is never set in Ocelot so always Pessimistic...I guess it doesn't
//matter to much.
string timeoutStrategy = "Pessimistic")
{
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
DurationOfBreak = durationofBreak;
TimeoutValue = timeoutValue;
TimeoutStrategy = timeoutStrategy;
Key = key;
}
public int ExceptionsAllowedBeforeBreaking { get; }
public int DurationOfBreak { get; }
public int TimeoutValue { get; }
public string TimeoutStrategy { get; }
public bool UseQos => ExceptionsAllowedBeforeBreaking > 0 && TimeoutValue > 0;
public string Key { get; }
}
}

View File

@ -28,17 +28,12 @@ namespace Ocelot.DependencyInjection
using Ocelot.Requester.QoS;
using Ocelot.Responder;
using Ocelot.ServiceDiscovery;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Builder;
using Ocelot.Configuration;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Net.Http;
using Ocelot.Infrastructure;
using Ocelot.Middleware.Multiplexer;
using ServiceDiscovery.Providers;
using Ocelot.Request.Creator;
public class OcelotBuilder : IOcelotBuilder
@ -75,8 +70,6 @@ namespace Ocelot.DependencyInjection
Services.TryAddSingleton<IRegionCreator, RegionCreator>();
Services.TryAddSingleton<IFileConfigurationRepository, DiskFileConfigurationRepository>();
Services.TryAddSingleton<IFileConfigurationSetter, FileAndInternalConfigurationSetter>();
Services.TryAddSingleton<IQosProviderHouse, QosProviderHouse>();
Services.TryAddSingleton<IQoSProviderFactory, QoSProviderFactory>();
Services.TryAddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
Services.TryAddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
Services.TryAddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
@ -136,6 +129,7 @@ namespace Ocelot.DependencyInjection
Services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();
Services.TryAddSingleton<IDownstreamRequestCreator, DownstreamRequestCreator>();
Services.TryAddSingleton<IFrameworkDescription, FrameworkDescription>();
Services.TryAddSingleton<IQoSFactory, QoSFactory>();
}
public IOcelotBuilder AddSingletonDefinedAggregator<T>()

View File

@ -45,6 +45,5 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.0" />
<PackageReference Include="Polly" Version="6.0.1" />
</ItemGroup>
</Project>

View File

@ -1,31 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration;
using Ocelot.Logging;
using Ocelot.Requester.QoS;
using Ocelot.Responses;
namespace Ocelot.Requester
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration;
using Ocelot.Responses;
using QoS;
public class DelegatingHandlerHandlerFactory : IDelegatingHandlerHandlerFactory
{
private readonly ITracingHandlerFactory _factory;
private readonly IOcelotLoggerFactory _loggerFactory;
private readonly IQosProviderHouse _qosProviderHouse;
private readonly ITracingHandlerFactory _tracingFactory;
private readonly IQoSFactory _qoSFactory;
private readonly IServiceProvider _serviceProvider;
public DelegatingHandlerHandlerFactory(IOcelotLoggerFactory loggerFactory,
ITracingHandlerFactory factory,
IQosProviderHouse qosProviderHouse,
public DelegatingHandlerHandlerFactory(
ITracingHandlerFactory tracingFactory,
IQoSFactory qoSFactory,
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_factory = factory;
_loggerFactory = loggerFactory;
_qosProviderHouse = qosProviderHouse;
_tracingFactory = tracingFactory;
_qoSFactory = qoSFactory;
}
public Response<List<Func<DelegatingHandler>>> Get(DownstreamReRoute request)
@ -64,19 +61,21 @@ namespace Ocelot.Requester
if (request.HttpHandlerOptions.UseTracing)
{
handlers.Add(() => (DelegatingHandler)_factory.Get());
handlers.Add(() => (DelegatingHandler)_tracingFactory.Get());
}
if (request.QosOptions.UseQos)
{
var qosProvider = _qosProviderHouse.Get(request);
var handler = _qoSFactory.Get(request);
if (qosProvider.IsError)
if (handler != null && !handler.IsError)
{
return new ErrorResponse<List<Func<DelegatingHandler>>>(qosProvider.Errors);
handlers.Add(() => handler.Data);
}
else
{
return new ErrorResponse<List<Func<DelegatingHandler>>>(handler?.Errors);
}
handlers.Add(() => new PollyCircuitBreakingDelegatingHandler(qosProvider.Data, _loggerFactory));
}
return new OkResponse<List<Func<DelegatingHandler>>>(handlers);

View File

@ -132,7 +132,7 @@ namespace Ocelot.Requester
{
var cacheKey = $"{request.DownstreamRequest.Method}:{request.DownstreamRequest.OriginalString}";
this._logger.LogDebug($"Cache key for request is {cacheKey}");
_logger.LogDebug($"Cache key for request is {cacheKey}");
return cacheKey;
}

View File

@ -4,8 +4,6 @@ using System.Threading.Tasks;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Responses;
using Polly.CircuitBreaker;
using Polly.Timeout;
namespace Ocelot.Requester
{
@ -35,18 +33,6 @@ namespace Ocelot.Requester
var response = await httpClient.SendAsync(context.DownstreamRequest.ToHttpRequestMessage());
return new OkResponse<HttpResponseMessage>(response);
}
catch (TimeoutRejectedException exception)
{
return new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
}
catch (TaskCanceledException exception)
{
return new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
}
catch (BrokenCircuitException exception)
{
return new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
}
catch (Exception exception)
{
return new ErrorResponse<HttpResponseMessage>(new UnableToCompleteRequestError(exception));

View File

@ -1,45 +0,0 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Ocelot.Logging;
using Ocelot.Requester.QoS;
using Polly;
using Polly.CircuitBreaker;
using Polly.Timeout;
namespace Ocelot.Requester
{
public class PollyCircuitBreakingDelegatingHandler : DelegatingHandler
{
private readonly IQoSProvider _qoSProvider;
private readonly IOcelotLogger _logger;
public PollyCircuitBreakingDelegatingHandler(
IQoSProvider qoSProvider,
IOcelotLoggerFactory loggerFactory)
{
_qoSProvider = qoSProvider;
_logger = loggerFactory.CreateLogger<PollyCircuitBreakingDelegatingHandler>();
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
return await Policy
.WrapAsync(_qoSProvider.CircuitBreaker.CircuitBreakerPolicy, _qoSProvider.CircuitBreaker.TimeoutPolicy)
.ExecuteAsync(() => base.SendAsync(request,cancellationToken));
}
catch (BrokenCircuitException ex)
{
_logger.LogError($"Reached to allowed number of exceptions. Circuit is open",ex);
throw;
}
catch (HttpRequestException ex)
{
_logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAync", ex);
throw;
}
}
}
}

View File

@ -1,17 +0,0 @@
using Polly.CircuitBreaker;
using Polly.Timeout;
namespace Ocelot.Requester.QoS
{
public class CircuitBreaker
{
public CircuitBreaker(CircuitBreakerPolicy circuitBreakerPolicy, TimeoutPolicy timeoutPolicy)
{
CircuitBreakerPolicy = circuitBreakerPolicy;
TimeoutPolicy = timeoutPolicy;
}
public CircuitBreakerPolicy CircuitBreakerPolicy { get; private set; }
public TimeoutPolicy TimeoutPolicy { get; private set; }
}
}

View File

@ -1,7 +0,0 @@
namespace Ocelot.Requester.QoS
{
public interface IQoSProvider
{
CircuitBreaker CircuitBreaker { get; }
}
}

View File

@ -1,10 +0,0 @@
using Ocelot.Configuration;
using Ocelot.LoadBalancer.LoadBalancers;
namespace Ocelot.Requester.QoS
{
public interface IQoSProviderFactory
{
IQoSProvider Get(DownstreamReRoute reRoute);
}
}

View File

@ -0,0 +1,11 @@
namespace Ocelot.Requester.QoS
{
using System.Net.Http;
using Configuration;
using Responses;
public interface IQoSFactory
{
Response<DelegatingHandler> Get(DownstreamReRoute request);
}
}

View File

@ -1,10 +0,0 @@
using Ocelot.Configuration;
using Ocelot.Responses;
namespace Ocelot.Requester.QoS
{
public interface IQosProviderHouse
{
Response<IQoSProvider> Get(DownstreamReRoute reRoute);
}
}

View File

@ -1,7 +0,0 @@
namespace Ocelot.Requester.QoS
{
public class NoQoSProvider : IQoSProvider
{
public CircuitBreaker CircuitBreaker { get; }
}
}

View File

@ -1,51 +0,0 @@
using System;
using System.Net.Http;
using Ocelot.Configuration;
using Ocelot.Logging;
using Polly;
using Polly.CircuitBreaker;
using Polly.Timeout;
namespace Ocelot.Requester.QoS
{
public class PollyQoSProvider : IQoSProvider
{
private readonly CircuitBreakerPolicy _circuitBreakerPolicy;
private readonly TimeoutPolicy _timeoutPolicy;
private readonly IOcelotLogger _logger;
private readonly CircuitBreaker _circuitBreaker;
public PollyQoSProvider(DownstreamReRoute reRoute, IOcelotLoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<PollyQoSProvider>();
_timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(reRoute.QosOptions.TimeoutValue), reRoute.QosOptions.TimeoutStrategy);
_circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutRejectedException>()
.Or<TimeoutException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: reRoute.QosOptions.ExceptionsAllowedBeforeBreaking,
durationOfBreak: TimeSpan.FromMilliseconds(reRoute.QosOptions.DurationOfBreak),
onBreak: (ex, breakDelay) =>
{
_logger.LogError(
".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex);
},
onReset: () =>
{
_logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again.");
},
onHalfOpen: () =>
{
_logger.LogDebug(".Breaker logging: Half-open; next call is a trial.");
}
);
_circuitBreaker = new CircuitBreaker(_circuitBreakerPolicy, _timeoutPolicy);
}
public CircuitBreaker CircuitBreaker => _circuitBreaker;
}
}

View File

@ -1,25 +0,0 @@
using Ocelot.Configuration;
using Ocelot.Logging;
namespace Ocelot.Requester.QoS
{
public class QoSProviderFactory : IQoSProviderFactory
{
private readonly IOcelotLoggerFactory _loggerFactory;
public QoSProviderFactory(IOcelotLoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
public IQoSProvider Get(DownstreamReRoute reRoute)
{
if (reRoute.QosOptions.UseQos)
{
return new PollyQoSProvider(reRoute, _loggerFactory);
}
return new NoQoSProvider();
}
}
}

View File

@ -0,0 +1,33 @@
namespace Ocelot.Requester.QoS
{
using System;
using System.Net.Http;
using Configuration;
using Logging;
using Microsoft.Extensions.DependencyInjection;
using Responses;
public class QoSFactory : IQoSFactory
{
private readonly IServiceProvider _serviceProvider;
private readonly IOcelotLoggerFactory _ocelotLoggerFactory;
public QoSFactory(IServiceProvider serviceProvider, IOcelotLoggerFactory ocelotLoggerFactory)
{
_serviceProvider = serviceProvider;
_ocelotLoggerFactory = ocelotLoggerFactory;
}
public Response<DelegatingHandler> Get(DownstreamReRoute request)
{
var handler = _serviceProvider.GetService<QosDelegatingHandlerDelegate>();
if (handler != null)
{
return new OkResponse<DelegatingHandler>(handler(request, _ocelotLoggerFactory));
}
return new ErrorResponse<DelegatingHandler>(new UnableToFindQoSProviderError($"could not find qosProvider for {request.DownstreamScheme}{request.DownstreamAddresses}{request.DownstreamPathTemplate}"));
}
}
}

View File

@ -1,53 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Ocelot.Configuration;
using Ocelot.Responses;
namespace Ocelot.Requester.QoS
{
public class QosProviderHouse : IQosProviderHouse
{
private readonly ConcurrentDictionary<string, IQoSProvider> _qoSProviders;
private readonly IQoSProviderFactory _qoSProviderFactory;
public QosProviderHouse(IQoSProviderFactory qoSProviderFactory)
{
_qoSProviderFactory = qoSProviderFactory;
_qoSProviders = new ConcurrentDictionary<string, IQoSProvider>();
}
public Response<IQoSProvider> Get(DownstreamReRoute reRoute)
{
try
{
if (_qoSProviders.TryGetValue(reRoute.QosOptions.Key, out var qosProvider))
{
if (reRoute.QosOptions.UseQos && qosProvider.CircuitBreaker == null)
{
qosProvider = _qoSProviderFactory.Get(reRoute);
Add(reRoute.QosOptions.Key, qosProvider);
}
return new OkResponse<IQoSProvider>(_qoSProviders[reRoute.QosOptions.Key]);
}
qosProvider = _qoSProviderFactory.Get(reRoute);
Add(reRoute.QosOptions.Key, qosProvider);
return new OkResponse<IQoSProvider>(qosProvider);
}
catch (Exception ex)
{
return new ErrorResponse<IQoSProvider>(new List<Ocelot.Errors.Error>()
{
new UnableToFindQoSProviderError($"unabe to find qos provider for {reRoute.QosOptions.Key}, exception was {ex}")
});
}
}
private void Add(string key, IQoSProvider qosProvider)
{
_qoSProviders.AddOrUpdate(key, qosProvider, (x, y) => qosProvider);
}
}
}

View File

@ -1,11 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ocelot.Errors;
namespace Ocelot.Requester.QoS
namespace Ocelot.Requester.QoS
{
using Ocelot.Errors;
public class UnableToFindQoSProviderError : Error
{
public UnableToFindQoSProviderError(string message)

View File

@ -0,0 +1,8 @@
namespace Ocelot.Requester
{
using System.Net.Http;
using Configuration;
using Logging;
public delegate DelegatingHandler QosDelegatingHandlerDelegate(DownstreamReRoute reRoute, IOcelotLoggerFactory logger);
}

View File

@ -1,13 +0,0 @@
using System;
using Ocelot.Errors;
namespace Ocelot.Requester
{
public class RequestTimedOutError : Error
{
public RequestTimedOutError(Exception exception)
: base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError)
{
}
}
}