Fixed issue where qos was being created for each request so circuit breaker was never stopping traffic going to downstream service.

This commit is contained in:
TomPallister 2017-02-11 18:56:36 +00:00
parent 820673dda8
commit 286c7f8488
30 changed files with 819 additions and 184 deletions

View File

@ -0,0 +1,34 @@
namespace Ocelot.Configuration.Builder
{
public class QoSOptionsBuilder
{
private int _exceptionsAllowedBeforeBreaking;
private int _durationOfBreak;
private int _timeoutValue;
public QoSOptionsBuilder WithExceptionsAllowedBeforeBreaking(int exceptionsAllowedBeforeBreaking)
{
_exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
return this;
}
public QoSOptionsBuilder WithDurationOfBreak(int durationOfBreak)
{
_durationOfBreak = durationOfBreak;
return this;
}
public QoSOptionsBuilder WithTimeoutValue(int timeoutValue)
{
_timeoutValue = timeoutValue;
return this;
}
public QoSOptions Build()
{
return new QoSOptions(_exceptionsAllowedBeforeBreaking, _durationOfBreak, _timeoutValue);
}
}
}

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -10,9 +9,9 @@ using Ocelot.Configuration.File;
using Ocelot.Configuration.Parser; using Ocelot.Configuration.Parser;
using Ocelot.Configuration.Validator; using Ocelot.Configuration.Validator;
using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Requester.QoS;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Utilities; using Ocelot.Utilities;
using Ocelot.Values;
namespace Ocelot.Configuration.Creator namespace Ocelot.Configuration.Creator
{ {
@ -26,11 +25,14 @@ namespace Ocelot.Configuration.Creator
private const string RegExMatchEverything = ".*"; private const string RegExMatchEverything = ".*";
private const string RegExMatchEndString = "$"; private const string RegExMatchEndString = "$";
private const string RegExIgnoreCase = "(?i)"; private const string RegExIgnoreCase = "(?i)";
private const string RegExForwardSlashOnly = "^/$";
private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser; private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser;
private readonly ILogger<FileOcelotConfigurationCreator> _logger; private readonly ILogger<FileOcelotConfigurationCreator> _logger;
private readonly ILoadBalancerFactory _loadBalanceFactory; private readonly ILoadBalancerFactory _loadBalanceFactory;
private readonly ILoadBalancerHouse _loadBalancerHouse; private readonly ILoadBalancerHouse _loadBalancerHouse;
private readonly IQoSProviderFactory _qoSProviderFactory;
private readonly IQosProviderHouse _qosProviderHouse;
public FileOcelotConfigurationCreator( public FileOcelotConfigurationCreator(
IOptions<FileConfiguration> options, IOptions<FileConfiguration> options,
@ -38,10 +40,14 @@ namespace Ocelot.Configuration.Creator
IClaimToThingConfigurationParser claimToThingConfigurationParser, IClaimToThingConfigurationParser claimToThingConfigurationParser,
ILogger<FileOcelotConfigurationCreator> logger, ILogger<FileOcelotConfigurationCreator> logger,
ILoadBalancerFactory loadBalancerFactory, ILoadBalancerFactory loadBalancerFactory,
ILoadBalancerHouse loadBalancerHouse) ILoadBalancerHouse loadBalancerHouse,
IQoSProviderFactory qoSProviderFactory,
IQosProviderHouse qosProviderHouse)
{ {
_loadBalanceFactory = loadBalancerFactory; _loadBalanceFactory = loadBalancerFactory;
_loadBalancerHouse = loadBalancerHouse; _loadBalancerHouse = loadBalancerHouse;
_qoSProviderFactory = qoSProviderFactory;
_qosProviderHouse = qosProviderHouse;
_options = options; _options = options;
_configurationValidator = configurationValidator; _configurationValidator = configurationValidator;
_claimToThingConfigurationParser = claimToThingConfigurationParser; _claimToThingConfigurationParser = claimToThingConfigurationParser;
@ -86,17 +92,17 @@ namespace Ocelot.Configuration.Creator
{ {
var isAuthenticated = IsAuthenticated(fileReRoute); var isAuthenticated = IsAuthenticated(fileReRoute);
var isAuthorised = IsAuthenticated(fileReRoute); var isAuthorised = IsAuthorised(fileReRoute);
var isCached = IsCached(fileReRoute); var isCached = IsCached(fileReRoute);
var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration); var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration);
var loadBalancerKey = BuildLoadBalancerKey(fileReRoute); var reRouteKey = BuildReRouteKey(fileReRoute);
var upstreamTemplatePattern = BuildUpstreamTemplate(fileReRoute); var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute);
var isQos = fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions.TimeoutValue >0; var isQos = IsQoS(fileReRoute);
var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration);
@ -108,6 +114,8 @@ namespace Ocelot.Configuration.Creator
var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest); var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest);
var qosOptions = BuildQoSOptions(fileReRoute);
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
@ -127,16 +135,31 @@ namespace Ocelot.Configuration.Creator
.WithLoadBalancer(fileReRoute.LoadBalancer) .WithLoadBalancer(fileReRoute.LoadBalancer)
.WithDownstreamHost(fileReRoute.DownstreamHost) .WithDownstreamHost(fileReRoute.DownstreamHost)
.WithDownstreamPort(fileReRoute.DownstreamPort) .WithDownstreamPort(fileReRoute.DownstreamPort)
.WithLoadBalancerKey(loadBalancerKey) .WithLoadBalancerKey(reRouteKey)
.WithServiceProviderConfiguraion(serviceProviderConfiguration) .WithServiceProviderConfiguraion(serviceProviderConfiguration)
.WithIsQos(isQos) .WithIsQos(isQos)
.WithQosOptions(new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue)) .WithQosOptions(qosOptions)
.Build(); .Build();
await SetupLoadBalancer(reRoute); await SetupLoadBalancer(reRoute);
SetupQosProvider(reRoute);
return reRoute; return reRoute;
} }
private QoSOptions BuildQoSOptions(FileReRoute fileReRoute)
{
return new QoSOptionsBuilder()
.WithExceptionsAllowedBeforeBreaking(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking)
.WithDurationOfBreak(fileReRoute.QoSOptions.DurationOfBreak)
.WithTimeoutValue(fileReRoute.QoSOptions.TimeoutValue)
.Build();
}
private bool IsQoS(FileReRoute fileReRoute)
{
return fileReRoute.QoSOptions?.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions?.TimeoutValue > 0;
}
private bool IsAuthenticated(FileReRoute fileReRoute) private bool IsAuthenticated(FileReRoute fileReRoute)
{ {
return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider);
@ -163,7 +186,7 @@ namespace Ocelot.Configuration.Creator
return requestIdKey; return requestIdKey;
} }
private string BuildLoadBalancerKey(FileReRoute fileReRoute) private string BuildReRouteKey(FileReRoute fileReRoute)
{ {
//note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain //note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain
var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}"; var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}";
@ -185,7 +208,13 @@ namespace Ocelot.Configuration.Creator
private async Task SetupLoadBalancer(ReRoute reRoute) private async Task SetupLoadBalancer(ReRoute reRoute)
{ {
var loadBalancer = await _loadBalanceFactory.Get(reRoute); var loadBalancer = await _loadBalanceFactory.Get(reRoute);
_loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); _loadBalancerHouse.Add(reRoute.ReRouteKey, loadBalancer);
}
private void SetupQosProvider(ReRoute reRoute)
{
var loadBalancer = _qoSProviderFactory.Get(reRoute);
_qosProviderHouse.Add(reRoute.ReRouteKey, loadBalancer);
} }
private ServiceProviderConfiguraion BuildServiceProviderConfiguration(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) private ServiceProviderConfiguraion BuildServiceProviderConfiguration(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
@ -206,7 +235,7 @@ namespace Ocelot.Configuration.Creator
.Build(); .Build();
} }
private string BuildUpstreamTemplate(FileReRoute reRoute) private string BuildUpstreamTemplatePattern(FileReRoute reRoute)
{ {
var upstreamTemplate = reRoute.UpstreamPathTemplate; var upstreamTemplate = reRoute.UpstreamPathTemplate;
@ -230,6 +259,11 @@ namespace Ocelot.Configuration.Creator
upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything); upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything);
} }
if (upstreamTemplate == "/")
{
return RegExForwardSlashOnly;
}
var route = reRoute.ReRouteIsCaseSensitive var route = reRoute.ReRouteIsCaseSensitive
? $"{upstreamTemplate}{RegExMatchEndString}" ? $"{upstreamTemplate}{RegExMatchEndString}"
: $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; : $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";

View File

@ -1,8 +1,5 @@
using Polly.Timeout; using System;
using System; using Polly.Timeout;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ocelot.Configuration namespace Ocelot.Configuration
{ {

View File

@ -25,12 +25,12 @@ namespace Ocelot.Configuration
string loadBalancer, string loadBalancer,
string downstreamHost, string downstreamHost,
int downstreamPort, int downstreamPort,
string loadBalancerKey, string reRouteKey,
ServiceProviderConfiguraion serviceProviderConfiguraion, ServiceProviderConfiguraion serviceProviderConfiguraion,
bool isQos, bool isQos,
QoSOptions qos) QoSOptions qos)
{ {
LoadBalancerKey = loadBalancerKey; ReRouteKey = reRouteKey;
ServiceProviderConfiguraion = serviceProviderConfiguraion; ServiceProviderConfiguraion = serviceProviderConfiguraion;
LoadBalancer = loadBalancer; LoadBalancer = loadBalancer;
DownstreamHost = downstreamHost; DownstreamHost = downstreamHost;
@ -57,7 +57,7 @@ namespace Ocelot.Configuration
QosOptions = qos; QosOptions = qos;
} }
public string LoadBalancerKey {get;private set;} public string ReRouteKey {get;private set;}
public PathTemplate DownstreamPathTemplate { get; private set; } public PathTemplate DownstreamPathTemplate { get; private set; }
public PathTemplate UpstreamPathTemplate { get; private set; } public PathTemplate UpstreamPathTemplate { get; private set; }
public string UpstreamTemplatePattern { get; private set; } public string UpstreamTemplatePattern { get; private set; }

View File

@ -28,6 +28,7 @@ using Ocelot.Logging;
using Ocelot.QueryStrings; using Ocelot.QueryStrings;
using Ocelot.Request.Builder; using Ocelot.Request.Builder;
using Ocelot.Requester; using Ocelot.Requester;
using Ocelot.Requester.QoS;
using Ocelot.Responder; using Ocelot.Responder;
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
@ -61,6 +62,8 @@ namespace Ocelot.DependencyInjection
{ {
services.AddMvcCore().AddJsonFormatters(); services.AddMvcCore().AddJsonFormatters();
services.AddLogging(); services.AddLogging();
services.AddSingleton<IQosProviderHouse, QosProviderHouse>();
services.AddSingleton<IQoSProviderFactory, QoSProviderFactory>();
services.AddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>(); services.AddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
services.AddSingleton<ILoadBalancerFactory, LoadBalancerFactory>(); services.AddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
services.AddSingleton<ILoadBalancerHouse, LoadBalancerHouse>(); services.AddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();

View File

@ -30,6 +30,13 @@ namespace Ocelot.DownstreamRouteFinder.Finder
foreach (var reRoute in applicableReRoutes) foreach (var reRoute in applicableReRoutes)
{ {
if (upstreamUrlPath == reRoute.UpstreamTemplatePattern)
{
var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value);
return new OkResponse<DownstreamRoute>(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute));
}
var urlMatch = _urlMatcher.Match(upstreamUrlPath, reRoute.UpstreamTemplatePattern); var urlMatch = _urlMatcher.Match(upstreamUrlPath, reRoute.UpstreamTemplatePattern);
if (urlMatch.Data.Match) if (urlMatch.Data.Match)

View File

@ -26,6 +26,7 @@
ServicesAreEmptyError, ServicesAreEmptyError,
UnableToFindServiceDiscoveryProviderError, UnableToFindServiceDiscoveryProviderError,
UnableToFindLoadBalancerError, UnableToFindLoadBalancerError,
RequestTimedOutError RequestTimedOutError,
UnableToFindQoSProviderError
} }
} }

View File

@ -6,7 +6,6 @@ using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.QueryStrings.Middleware; using Ocelot.QueryStrings.Middleware;
using Ocelot.ServiceDiscovery;
namespace Ocelot.LoadBalancer.Middleware namespace Ocelot.LoadBalancer.Middleware
{ {
@ -31,7 +30,7 @@ namespace Ocelot.LoadBalancer.Middleware
{ {
_logger.LogDebug("started calling load balancing middleware"); _logger.LogDebug("started calling load balancing middleware");
var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey); var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.ReRouteKey);
if(loadBalancer.IsError) if(loadBalancer.IsError)
{ {
SetPipelineError(loadBalancer.Errors); SetPipelineError(loadBalancer.Errors);

View File

@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Requester.QoS;
namespace Ocelot.Request.Builder namespace Ocelot.Request.Builder
{ {
@ -18,7 +19,7 @@ namespace Ocelot.Request.Builder
string contentType, string contentType,
RequestId.RequestId requestId, RequestId.RequestId requestId,
bool isQos, bool isQos,
QoSOptions qos) IQoSProvider qosProvider)
{ {
var request = await new RequestBuilder() var request = await new RequestBuilder()
.WithHttpMethod(httpMethod) .WithHttpMethod(httpMethod)
@ -30,7 +31,7 @@ namespace Ocelot.Request.Builder
.WithRequestId(requestId) .WithRequestId(requestId)
.WithCookies(cookies) .WithCookies(cookies)
.WithIsQos(isQos) .WithIsQos(isQos)
.WithQos(qos) .WithQos(qosProvider)
.Build(); .Build();
return new OkResponse<Request>(request); return new OkResponse<Request>(request);

View File

@ -1,8 +1,8 @@
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Requester.QoS;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Configuration;
namespace Ocelot.Request.Builder namespace Ocelot.Request.Builder
{ {
@ -17,6 +17,6 @@ namespace Ocelot.Request.Builder
string contentType, string contentType,
RequestId.RequestId requestId, RequestId.RequestId requestId,
bool isQos, bool isQos,
QoSOptions qos); IQoSProvider qosProvider);
} }
} }

View File

@ -8,7 +8,7 @@ using System.Net.Http.Headers;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Ocelot.Configuration; using Ocelot.Requester.QoS;
namespace Ocelot.Request.Builder namespace Ocelot.Request.Builder
{ {
@ -24,7 +24,7 @@ namespace Ocelot.Request.Builder
private IRequestCookieCollection _cookies; private IRequestCookieCollection _cookies;
private readonly string[] _unsupportedHeaders = {"host"}; private readonly string[] _unsupportedHeaders = {"host"};
private bool _isQos; private bool _isQos;
private QoSOptions _qos; private IQoSProvider _qoSProvider;
public RequestBuilder WithHttpMethod(string httpMethod) public RequestBuilder WithHttpMethod(string httpMethod)
{ {
@ -80,9 +80,9 @@ namespace Ocelot.Request.Builder
return this; return this;
} }
public RequestBuilder WithQos(QoSOptions qos) public RequestBuilder WithQos(IQoSProvider qoSProvider)
{ {
_qos = qos; _qoSProvider = qoSProvider;
return this; return this;
} }
@ -105,7 +105,7 @@ namespace Ocelot.Request.Builder
var cookieContainer = CreateCookieContainer(uri); var cookieContainer = CreateCookieContainer(uri);
return new Request(httpRequestMessage, cookieContainer,_isQos,_qos); return new Request(httpRequestMessage, cookieContainer,_isQos, _qoSProvider);
} }
private Uri CreateUri() private Uri CreateUri()

View File

@ -1,10 +1,10 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Request.Builder; using Ocelot.Request.Builder;
using Ocelot.Requester.QoS;
namespace Ocelot.Request.Middleware namespace Ocelot.Request.Middleware
{ {
@ -13,15 +13,18 @@ namespace Ocelot.Request.Middleware
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly IRequestCreator _requestCreator; private readonly IRequestCreator _requestCreator;
private readonly IOcelotLogger _logger; private readonly IOcelotLogger _logger;
private readonly IQosProviderHouse _qosProviderHouse;
public HttpRequestBuilderMiddleware(RequestDelegate next, public HttpRequestBuilderMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IRequestScopedDataRepository requestScopedDataRepository, IRequestScopedDataRepository requestScopedDataRepository,
IRequestCreator requestCreator) IRequestCreator requestCreator,
IQosProviderHouse qosProviderHouse)
:base(requestScopedDataRepository) :base(requestScopedDataRepository)
{ {
_next = next; _next = next;
_requestCreator = requestCreator; _requestCreator = requestCreator;
_qosProviderHouse = qosProviderHouse;
_logger = loggerFactory.CreateLogger<HttpRequestBuilderMiddleware>(); _logger = loggerFactory.CreateLogger<HttpRequestBuilderMiddleware>();
} }
@ -29,17 +32,35 @@ namespace Ocelot.Request.Middleware
{ {
_logger.LogDebug("started calling request builder middleware"); _logger.LogDebug("started calling request builder middleware");
var qosProvider = _qosProviderHouse.Get(DownstreamRoute.ReRoute.ReRouteKey);
if (qosProvider.IsError)
{
_logger.LogDebug("IQosProviderHouse returned an error, setting pipeline error");
SetPipelineError(qosProvider.Errors);
return;
}
var buildResult = await _requestCreator var buildResult = await _requestCreator
.Build(context.Request.Method, DownstreamUrl, context.Request.Body, .Build(context.Request.Method,
context.Request.Headers, context.Request.Cookies, context.Request.QueryString, DownstreamUrl,
context.Request.ContentType, new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier), context.Request.Body,
DownstreamRoute.ReRoute.IsQos,DownstreamRoute.ReRoute.QosOptions); context.Request.Headers,
context.Request.Cookies,
context.Request.QueryString,
context.Request.ContentType,
new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier),
DownstreamRoute.ReRoute.IsQos,
qosProvider.Data);
if (buildResult.IsError) if (buildResult.IsError)
{ {
_logger.LogDebug("IRequestCreator returned an error, setting pipeline error"); _logger.LogDebug("IRequestCreator returned an error, setting pipeline error");
SetPipelineError(buildResult.Errors); SetPipelineError(buildResult.Errors);
return; return;
} }
_logger.LogDebug("setting upstream request"); _logger.LogDebug("setting upstream request");

View File

@ -2,22 +2,27 @@
using Ocelot.Values; using Ocelot.Values;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using Ocelot.Requester.QoS;
namespace Ocelot.Request namespace Ocelot.Request
{ {
public class Request public class Request
{ {
public Request(HttpRequestMessage httpRequestMessage, CookieContainer cookieContainer,bool isQos, QoSOptions qos) public Request(
HttpRequestMessage httpRequestMessage,
CookieContainer cookieContainer,
bool isQos,
IQoSProvider qosProvider)
{ {
HttpRequestMessage = httpRequestMessage; HttpRequestMessage = httpRequestMessage;
CookieContainer = cookieContainer; CookieContainer = cookieContainer;
IsQos = isQos; IsQos = isQos;
Qos = qos; QosProvider = qosProvider;
} }
public HttpRequestMessage HttpRequestMessage { get; private set; } public HttpRequestMessage HttpRequestMessage { get; private set; }
public CookieContainer CookieContainer { get; private set; } public CookieContainer CookieContainer { get; private set; }
public bool IsQos { get; private set; } public bool IsQos { get; private set; }
public QoSOptions Qos { get; private set; } public IQoSProvider QosProvider { get; private set; }
} }
} }

View File

@ -1,77 +0,0 @@
using Ocelot.Logging;
using Polly;
using Polly.CircuitBreaker;
using Polly.Timeout;
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Ocelot.Requester
{
public class CircuitBreakingDelegatingHandler : DelegatingHandler
{
private readonly IOcelotLogger _logger;
private readonly int _exceptionsAllowedBeforeBreaking;
private readonly TimeSpan _durationOfBreak;
private readonly Policy _circuitBreakerPolicy;
private readonly TimeoutPolicy _timeoutPolicy;
public CircuitBreakingDelegatingHandler(
int exceptionsAllowedBeforeBreaking,
TimeSpan durationOfBreak,
TimeSpan timeoutValue,
TimeoutStrategy timeoutStrategy,
IOcelotLogger logger,
HttpMessageHandler innerHandler)
: base(innerHandler)
{
this._exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
this._durationOfBreak = durationOfBreak;
_circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutRejectedException>()
.Or<TimeoutException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: exceptionsAllowedBeforeBreaking,
durationOfBreak: 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.")
);
_timeoutPolicy = Policy.TimeoutAsync(timeoutValue, timeoutStrategy);
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
return await Policy.WrapAsync(_circuitBreakerPolicy, _timeoutPolicy).ExecuteAsync(() => base.SendAsync(request,cancellationToken));
}
catch (BrokenCircuitException ex)
{
_logger.LogError($"Reached to allowed number of exceptions. Circuit is open. AllowedExceptionCount: {_exceptionsAllowedBeforeBreaking}, DurationOfBreak: {_durationOfBreak}",ex);
throw;
}
catch (HttpRequestException ex)
{
_logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAync", ex);
throw;
}
}
private static bool IsTransientFailure(HttpResponseMessage result)
{
return result.StatusCode >= HttpStatusCode.InternalServerError;
}
}
}

View File

@ -1,35 +1,39 @@
using Ocelot.Configuration; using System;
using Ocelot.Logging;
using Ocelot.Values;
using Polly.Timeout;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using Ocelot.Logging;
using Ocelot.Requester.QoS;
namespace Ocelot.Requester namespace Ocelot.Requester
{ {
internal class HttpClientBuilder internal class HttpClientBuilder
{ {
private readonly Dictionary<int, Func<DelegatingHandler>> handlers = new Dictionary<int, Func<DelegatingHandler>>(); private readonly Dictionary<int, Func<DelegatingHandler>> _handlers = new Dictionary<int, Func<DelegatingHandler>>();
public HttpClientBuilder WithCircuitBreaker(QoSOptions qos, IOcelotLogger logger, HttpMessageHandler innerHandler) public HttpClientBuilder WithQoS(IQoSProvider qoSProvider, IOcelotLogger logger, HttpMessageHandler innerHandler)
{ {
handlers.Add(5000, () => new CircuitBreakingDelegatingHandler(qos.ExceptionsAllowedBeforeBreaking, qos.DurationOfBreak, qos.TimeoutValue, qos.TimeoutStrategy, logger, innerHandler)); _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qoSProvider, logger, innerHandler));
return this; return this;
} }
internal HttpClient Build(HttpMessageHandler innerHandler) internal HttpClient Build(HttpMessageHandler innerHandler)
{ {
return handlers.Any() ? new HttpClient(CreateHttpMessageHandler()) : new HttpClient(innerHandler); return _handlers.Any() ?
new HttpClient(CreateHttpMessageHandler()) :
new HttpClient(innerHandler);
} }
private HttpMessageHandler CreateHttpMessageHandler() private HttpMessageHandler CreateHttpMessageHandler()
{ {
HttpMessageHandler httpMessageHandler = new HttpClientHandler(); HttpMessageHandler httpMessageHandler = new HttpClientHandler();
handlers.OrderByDescending(handler => handler.Key).Select(handler => handler.Value).Reverse().ToList().ForEach(handler => _handlers
.OrderByDescending(handler => handler.Key)
.Select(handler => handler.Value)
.Reverse()
.ToList()
.ForEach(handler =>
{ {
var delegatingHandler = handler(); var delegatingHandler = handler();
delegatingHandler.InnerHandler = httpMessageHandler; delegatingHandler.InnerHandler = httpMessageHandler;

View File

@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Errors;
using Ocelot.Responses;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Responses;
using Polly.CircuitBreaker;
using Polly.Timeout;
namespace Ocelot.Requester namespace Ocelot.Requester
{ {
@ -19,13 +19,13 @@ namespace Ocelot.Requester
public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request) public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request)
{ {
HttpClientBuilder builder = new HttpClientBuilder(); var builder = new HttpClientBuilder();
using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer }) using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer })
{ {
if (request.IsQos) if (request.IsQos)
{ {
builder.WithCircuitBreaker(request.Qos, _logger, handler); builder.WithQoS(request.QosProvider, _logger, handler);
} }
using (var httpClient = builder.Build(handler)) using (var httpClient = builder.Build(handler))
@ -35,7 +35,12 @@ namespace Ocelot.Requester
var response = await httpClient.SendAsync(request.HttpRequestMessage); var response = await httpClient.SendAsync(request.HttpRequestMessage);
return new OkResponse<HttpResponseMessage>(response); return new OkResponse<HttpResponseMessage>(response);
} }
catch (Polly.Timeout.TimeoutRejectedException exception) catch (TimeoutRejectedException exception)
{
return
new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
}
catch (BrokenCircuitException exception)
{ {
return return
new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception)); new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));

View File

@ -0,0 +1,47 @@
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,
IOcelotLogger logger,
HttpMessageHandler innerHandler)
: base(innerHandler)
{
_qoSProvider = qoSProvider;
_logger = logger;
}
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

@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Configuration;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging;
using Ocelot.Responses;
using Polly;
using Polly.CircuitBreaker;
using Polly.Timeout;
namespace Ocelot.Requester.QoS
{
public interface IQoSProviderFactory
{
IQoSProvider Get(ReRoute reRoute);
}
public class QoSProviderFactory : IQoSProviderFactory
{
private readonly IOcelotLoggerFactory _loggerFactory;
public QoSProviderFactory(IOcelotLoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
public IQoSProvider Get(ReRoute reRoute)
{
if (reRoute.IsQos)
{
return new PollyQoSProvider(reRoute, _loggerFactory);
}
return new NoQoSProvider();
}
}
public interface IQoSProvider
{
CircuitBreaker CircuitBreaker { get; }
}
public class NoQoSProvider : IQoSProvider
{
public CircuitBreaker CircuitBreaker { get; }
}
public class PollyQoSProvider : IQoSProvider
{
private readonly CircuitBreakerPolicy _circuitBreakerPolicy;
private readonly TimeoutPolicy _timeoutPolicy;
private readonly IOcelotLogger _logger;
private readonly CircuitBreaker _circuitBreaker;
public PollyQoSProvider(ReRoute reRoute, IOcelotLoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<PollyQoSProvider>();
_timeoutPolicy = Policy.TimeoutAsync(reRoute.QosOptions.TimeoutValue, reRoute.QosOptions.TimeoutStrategy);
_circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutRejectedException>()
.Or<TimeoutException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: reRoute.QosOptions.ExceptionsAllowedBeforeBreaking,
durationOfBreak: 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;
}
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; }
}
public interface IQosProviderHouse
{
Response<IQoSProvider> Get(string key);
Response Add(string key, IQoSProvider loadBalancer);
}
public class QosProviderHouse : IQosProviderHouse
{
private readonly Dictionary<string, IQoSProvider> _qoSProviders;
public QosProviderHouse()
{
_qoSProviders = new Dictionary<string, IQoSProvider>();
}
public Response<IQoSProvider> Get(string key)
{
IQoSProvider qoSProvider;
if (_qoSProviders.TryGetValue(key, out qoSProvider))
{
return new OkResponse<IQoSProvider>(_qoSProviders[key]);
}
return new ErrorResponse<IQoSProvider>(new List<Ocelot.Errors.Error>()
{
new UnableToFindQoSProviderError($"unabe to find qos provider for {key}")
});
}
public Response Add(string key, IQoSProvider loadBalancer)
{
if (!_qoSProviders.ContainsKey(key))
{
_qoSProviders.Add(key, loadBalancer);
}
_qoSProviders.Remove(key);
_qoSProviders.Add(key, loadBalancer);
return new OkResponse();
}
}
}

View File

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

View File

@ -24,7 +24,7 @@ namespace Ocelot.Responder
if (errors.Any(e => e.Code == OcelotErrorCode.RequestTimedOutError)) if (errors.Any(e => e.Code == OcelotErrorCode.RequestTimedOutError))
{ {
return new OkResponse<int>(408); return new OkResponse<int>(503);
} }
return new OkResponse<int>(404); return new OkResponse<int>(404);

View File

@ -15,16 +15,16 @@ namespace Ocelot.AcceptanceTests
{ {
public class QoSTests : IDisposable public class QoSTests : IDisposable
{ {
private IWebHost _builder; private IWebHost _brokenService;
private readonly Steps _steps; private readonly Steps _steps;
private int _requestCount; private int _requestCount;
private IWebHost _workingService;
public QoSTests() public QoSTests()
{ {
_steps = new Steps(); _steps = new Steps();
} }
[Fact] [Fact]
public void should_open_circuit_breaker_then_close() public void should_open_circuit_breaker_then_close()
{ {
@ -50,33 +50,90 @@ namespace Ocelot.AcceptanceTests
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "Hello from Laura")) this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura"))
.Given(x => _steps.GivenThereIsAConfiguration(configuration)) .Given(x => _steps.GivenThereIsAConfiguration(configuration))
.Given(x => _steps.GivenOcelotIsRunning()) .Given(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.RequestTimeout)) .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.RequestTimeout)) .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.RequestTimeout)) .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.Given(x => x.GivenIWaitMilliSeconds(2000)) .Given(x => x.GivenIWaitMilliseconds(3000))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy(); .BDDfy();
} }
private void GivenIWaitMilliSeconds(int ms) [Fact]
public void open_circuit_should_not_effect_different_reRoute()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHost = "localhost",
DownstreamPort = 51879,
UpstreamPathTemplate = "/",
UpstreamHttpMethod = "Get",
QoSOptions = new FileQoSOptions
{
ExceptionsAllowedBeforeBreaking = 1,
TimeoutValue = 500,
DurationOfBreak = 1000
}
},
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHost = "localhost",
DownstreamPort = 51880,
UpstreamPathTemplate = "working",
UpstreamHttpMethod = "Get",
}
}
};
this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura"))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", 200, "Hello from Tom"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/working"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom"))
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.And(x => x.GivenIWaitMilliseconds(3000))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
private void GivenIWaitMilliseconds(int ms)
{ {
Thread.Sleep(ms); Thread.Sleep(ms);
} }
private void GivenThereIsAServiceRunningOn(string url, string responseBody) private void GivenThereIsAPossiblyBrokenServiceRunningOn(string url, string responseBody)
{ {
_builder = new WebHostBuilder() _brokenService = new WebHostBuilder()
.UseUrls(url) .UseUrls(url)
.UseKestrel() .UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
@ -95,7 +152,7 @@ namespace Ocelot.AcceptanceTests
return; return;
} }
//request one times out and polly throws exception //request one times out and polly throws exception, circuit opens
if (_requestCount == 1) if (_requestCount == 1)
{ {
_requestCount++; _requestCount++;
@ -104,17 +161,8 @@ namespace Ocelot.AcceptanceTests
return; return;
} }
//request two times out and polly throws exception circuit opens
if (_requestCount == 2)
{
_requestCount++;
await Task.Delay(1000);
context.Response.StatusCode = 200;
return;
}
//after break closes we return 200 OK //after break closes we return 200 OK
if (_requestCount == 3) if (_requestCount == 2)
{ {
context.Response.StatusCode = 200; context.Response.StatusCode = 200;
await context.Response.WriteAsync(responseBody); await context.Response.WriteAsync(responseBody);
@ -124,12 +172,34 @@ namespace Ocelot.AcceptanceTests
}) })
.Build(); .Build();
_builder.Start(); _brokenService.Start();
}
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody)
{
_workingService = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody);
});
})
.Build();
_workingService.Start();
} }
public void Dispose() public void Dispose()
{ {
_builder?.Dispose(); _workingService?.Dispose();
_brokenService?.Dispose();
_steps.Dispose(); _steps.Dispose();
} }
} }

View File

@ -9,6 +9,7 @@ using Ocelot.Configuration.File;
using Ocelot.Configuration.Parser; using Ocelot.Configuration.Parser;
using Ocelot.Configuration.Validator; using Ocelot.Configuration.Validator;
using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Requester.QoS;
using Ocelot.Responses; using Ocelot.Responses;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
@ -28,9 +29,15 @@ namespace Ocelot.UnitTests.Configuration
private readonly Mock<ILoadBalancerFactory> _loadBalancerFactory; private readonly Mock<ILoadBalancerFactory> _loadBalancerFactory;
private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse; private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse;
private readonly Mock<ILoadBalancer> _loadBalancer; private readonly Mock<ILoadBalancer> _loadBalancer;
private readonly Mock<IQoSProviderFactory> _qosProviderFactory;
private readonly Mock<IQosProviderHouse> _qosProviderHouse;
private readonly Mock<IQoSProvider> _qosProvider;
public FileConfigurationCreatorTests() public FileConfigurationCreatorTests()
{ {
_qosProviderFactory = new Mock<IQoSProviderFactory>();
_qosProviderHouse = new Mock<IQosProviderHouse>();
_qosProvider = new Mock<IQoSProvider>();
_logger = new Mock<ILogger<FileOcelotConfigurationCreator>>(); _logger = new Mock<ILogger<FileOcelotConfigurationCreator>>();
_configParser = new Mock<IClaimToThingConfigurationParser>(); _configParser = new Mock<IClaimToThingConfigurationParser>();
_validator = new Mock<IConfigurationValidator>(); _validator = new Mock<IConfigurationValidator>();
@ -40,7 +47,8 @@ namespace Ocelot.UnitTests.Configuration
_loadBalancer = new Mock<ILoadBalancer>(); _loadBalancer = new Mock<ILoadBalancer>();
_ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _ocelotConfigurationCreator = new FileOcelotConfigurationCreator(
_fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object, _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object,
_loadBalancerFactory.Object, _loadBalancerHouse.Object); _loadBalancerFactory.Object, _loadBalancerHouse.Object,
_qosProviderFactory.Object, _qosProviderHouse.Object);
} }
[Fact] [Fact]
@ -64,7 +72,36 @@ namespace Ocelot.UnitTests.Configuration
.When(x => x.WhenICreateTheConfig()) .When(x => x.WhenICreateTheConfig())
.Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly()) .Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly())
.And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly()) .And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly())
.BDDfy();
}
[Fact]
public void should_create_qos_provider()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamHost = "127.0.0.1",
UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
QoSOptions = new FileQoSOptions
{
TimeoutValue = 1,
DurationOfBreak = 1,
ExceptionsAllowedBeforeBreaking = 1
}
}
},
}))
.And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheQosProviderFactoryReturns())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.TheQosProviderFactoryIsCalledCorrectly())
.And(x => x.ThenTheQosProviderHouseIsCalledCorrectly())
.BDDfy(); .BDDfy();
} }
@ -568,7 +605,7 @@ namespace Ocelot.UnitTests.Configuration
.WithDownstreamPathTemplate("/api/products/") .WithDownstreamPathTemplate("/api/products/")
.WithUpstreamPathTemplate("/") .WithUpstreamPathTemplate("/")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/$") .WithUpstreamTemplatePattern("^/$")
.Build() .Build()
})) }))
.BDDfy(); .BDDfy();
@ -643,5 +680,24 @@ namespace Ocelot.UnitTests.Configuration
_loadBalancerHouse _loadBalancerHouse
.Verify(x => x.Add(It.IsAny<string>(), _loadBalancer.Object), Times.Once); .Verify(x => x.Add(It.IsAny<string>(), _loadBalancer.Object), Times.Once);
} }
private void GivenTheQosProviderFactoryReturns()
{
_qosProviderFactory
.Setup(x => x.Get(It.IsAny<ReRoute>()))
.Returns(_qosProvider.Object);
}
private void TheQosProviderFactoryIsCalledCorrectly()
{
_qosProviderFactory
.Verify(x => x.Get(It.IsAny<ReRoute>()), Times.Once);
}
private void ThenTheQosProviderHouseIsCalledCorrectly()
{
_qosProviderHouse
.Verify(x => x.Add(It.IsAny<string>(), _qosProvider.Object), Times.Once);
}
} }
} }

View File

@ -35,6 +35,37 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
[Fact] [Fact]
public void should_return_route() public void should_return_route()
{
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher"))
.And(x =>x.GivenTheTemplateVariableAndNameFinderReturns(
new OkResponse<List<UrlPathPlaceholderNameAndValue>>(
new List<UrlPathPlaceholderNameAndValue>())))
.And(x => x.GivenTheConfigurationIs(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamPathTemplate("someUpstreamPath")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("someUpstreamPath")
.Build()
}))
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
.And(x => x.GivenTheUpstreamHttpMethodIs("Get"))
.When(x => x.WhenICallTheFinder())
.Then(
x => x.ThenTheFollowingIsReturned(new DownstreamRoute(
new List<UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithDownstreamPathTemplate("someDownstreamPath")
.WithUpstreamHttpMethod("Get")
.Build()
)))
.And(x => x.ThenTheUrlMatcherIsCalledCorrectly())
.BDDfy();
}
[Fact]
public void should_return_route_if_upstream_path_and_upstream_template_are_the_same()
{ {
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath"))
.And( .And(
@ -61,7 +92,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.Build() .Build()
))) )))
.And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) .And(x => x.ThenTheUrlMatcherIsNotCalled())
.BDDfy(); .BDDfy();
} }
@ -105,7 +136,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
[Fact] [Fact]
public void should_not_return_route() public void should_not_return_route()
{ {
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("somePath")) this.Given(x => x.GivenThereIsAnUpstreamUrlPath("dontMatchPath"))
.And(x => x.GivenTheConfigurationIs(new List<ReRoute> .And(x => x.GivenTheConfigurationIs(new List<ReRoute>
{ {
new ReRouteBuilder() new ReRouteBuilder()
@ -148,6 +179,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once);
} }
private void ThenTheUrlMatcherIsNotCalled()
{
_mockMatcher
.Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Never);
}
private void GivenTheUrlMatcherReturns(Response<UrlMatch> match) private void GivenTheUrlMatcherReturns(Response<UrlMatch> match)
{ {
_match = match; _match = match;

View File

@ -18,6 +18,26 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher
_urlMatcher = new RegExUrlMatcher(); _urlMatcher = new RegExUrlMatcher();
} }
[Fact]
public void should_not_match_forward_slash_only_regex()
{
this.Given(x => x.GivenIHaveAUpstreamPath("/working/"))
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$"))
.When(x => x.WhenIMatchThePaths())
.And(x => x.ThenTheResultIsFalse())
.BDDfy();
}
[Fact]
public void should_match_forward_slash_only_regex()
{
this.Given(x => x.GivenIHaveAUpstreamPath("/"))
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$"))
.When(x => x.WhenIMatchThePaths())
.And(x => x.ThenTheResultIsTrue())
.BDDfy();
}
[Fact] [Fact]
public void should_find_match_when_template_smaller_than_valid_path() public void should_find_match_when_template_smaller_than_valid_path()
{ {

View File

@ -20,6 +20,7 @@ using Ocelot.Responses;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Requester.QoS;
namespace Ocelot.UnitTests.Request namespace Ocelot.UnitTests.Request
{ {
@ -27,6 +28,7 @@ namespace Ocelot.UnitTests.Request
{ {
private readonly Mock<IRequestCreator> _requestBuilder; private readonly Mock<IRequestCreator> _requestBuilder;
private readonly Mock<IRequestScopedDataRepository> _scopedRepository; private readonly Mock<IRequestScopedDataRepository> _scopedRepository;
private readonly Mock<IQosProviderHouse> _qosProviderHouse;
private readonly string _url; private readonly string _url;
private readonly TestServer _server; private readonly TestServer _server;
private readonly HttpClient _client; private readonly HttpClient _client;
@ -38,6 +40,7 @@ namespace Ocelot.UnitTests.Request
public HttpRequestBuilderMiddlewareTests() public HttpRequestBuilderMiddlewareTests()
{ {
_url = "http://localhost:51879"; _url = "http://localhost:51879";
_qosProviderHouse = new Mock<IQosProviderHouse>();
_requestBuilder = new Mock<IRequestCreator>(); _requestBuilder = new Mock<IRequestCreator>();
_scopedRepository = new Mock<IRequestScopedDataRepository>(); _scopedRepository = new Mock<IRequestScopedDataRepository>();
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
@ -45,6 +48,7 @@ namespace Ocelot.UnitTests.Request
{ {
x.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>(); x.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
x.AddLogging(); x.AddLogging();
x.AddSingleton(_qosProviderHouse.Object);
x.AddSingleton(_requestBuilder.Object); x.AddSingleton(_requestBuilder.Object);
x.AddSingleton(_scopedRepository.Object); x.AddSingleton(_scopedRepository.Object);
}) })
@ -72,15 +76,22 @@ namespace Ocelot.UnitTests.Request
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.Build()); .Build());
this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
.And(x => x.GivenTheQosProviderHouseReturns(new OkResponse<IQoSProvider>(new NoQoSProvider())))
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
.And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer(), true, new QoSOptions(3, 8 ,5000, Polly.Timeout.TimeoutStrategy.Pessimistic)))) .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer(), true, new NoQoSProvider())))
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
.BDDfy(); .BDDfy();
} }
private void GivenTheQosProviderHouseReturns(Response<IQoSProvider> qosProvider)
{
_qosProviderHouse
.Setup(x => x.Get(It.IsAny<string>()))
.Returns(qosProvider);
}
private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute)
{ {
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute); _downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
@ -94,7 +105,7 @@ namespace Ocelot.UnitTests.Request
_request = new OkResponse<Ocelot.Request.Request>(request); _request = new OkResponse<Ocelot.Request.Request>(request);
_requestBuilder _requestBuilder
.Setup(x => x.Build(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<IHeaderDictionary>(), .Setup(x => x.Build(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<IHeaderDictionary>(),
It.IsAny<IRequestCookieCollection>(), It.IsAny<QueryString>(), It.IsAny<string>(), It.IsAny<Ocelot.RequestId.RequestId>(),It.IsAny<bool>(), It.IsAny<QoSOptions>())) It.IsAny<IRequestCookieCollection>(), It.IsAny<QueryString>(), It.IsAny<string>(), It.IsAny<Ocelot.RequestId.RequestId>(),It.IsAny<bool>(), It.IsAny<IQoSProvider>()))
.ReturnsAsync(_request); .ReturnsAsync(_request);
} }

View File

@ -11,6 +11,7 @@ using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Requester.QoS;
namespace Ocelot.UnitTests.Request namespace Ocelot.UnitTests.Request
{ {
@ -27,7 +28,7 @@ namespace Ocelot.UnitTests.Request
private Response<Ocelot.Request.Request> _result; private Response<Ocelot.Request.Request> _result;
private Ocelot.RequestId.RequestId _requestId; private Ocelot.RequestId.RequestId _requestId;
private bool _isQos; private bool _isQos;
private QoSOptions _qos; private IQoSProvider _qoSProvider;
public RequestBuilderTests() public RequestBuilderTests()
{ {
@ -40,7 +41,7 @@ namespace Ocelot.UnitTests.Request
{ {
this.Given(x => x.GivenIHaveHttpMethod("GET")) this.Given(x => x.GivenIHaveHttpMethod("GET"))
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x=> x.GivenTheQos(true,new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x=> x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectDownstreamUrlIsUsed("http://www.bbc.co.uk/")) .And(x => x.ThenTheCorrectDownstreamUrlIsUsed("http://www.bbc.co.uk/"))
.BDDfy(); .BDDfy();
@ -51,7 +52,7 @@ namespace Ocelot.UnitTests.Request
{ {
this.Given(x => x.GivenIHaveHttpMethod("POST")) this.Given(x => x.GivenIHaveHttpMethod("POST"))
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x => x.GivenTheQos(true,new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectHttpMethodIsUsed(HttpMethod.Post)) .And(x => x.ThenTheCorrectHttpMethodIsUsed(HttpMethod.Post))
@ -65,7 +66,7 @@ namespace Ocelot.UnitTests.Request
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom")))
.And(x => x.GivenTheContentTypeIs("application/json")) .And(x => x.GivenTheContentTypeIs("application/json"))
.And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectContentIsUsed(new StringContent("Hi from Tom"))) .And(x => x.ThenTheCorrectContentIsUsed(new StringContent("Hi from Tom")))
@ -79,7 +80,7 @@ namespace Ocelot.UnitTests.Request
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom")))
.And(x => x.GivenTheContentTypeIs("application/json")) .And(x => x.GivenTheContentTypeIs("application/json"))
.And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary
@ -98,7 +99,7 @@ namespace Ocelot.UnitTests.Request
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom")))
.And(x => x.GivenTheContentTypeIs("application/json; charset=utf-8")) .And(x => x.GivenTheContentTypeIs("application/json; charset=utf-8"))
.And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary
@ -119,7 +120,7 @@ namespace Ocelot.UnitTests.Request
{ {
{"ChopSticks", "Bubbles" } {"ChopSticks", "Bubbles" }
})) }))
.And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary
@ -138,7 +139,7 @@ namespace Ocelot.UnitTests.Request
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary())) .And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary()))
.And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", requestId))) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", requestId)))
.And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary
{ {
@ -157,7 +158,7 @@ namespace Ocelot.UnitTests.Request
{"RequestId", "534534gv54gv45g" } {"RequestId", "534534gv54gv45g" }
})) }))
.And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", Guid.NewGuid().ToString()))) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", Guid.NewGuid().ToString())))
.And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary
{ {
@ -177,7 +178,7 @@ namespace Ocelot.UnitTests.Request
.And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
.And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary())) .And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary()))
.And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId(requestIdKey, requestIdValue))) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId(requestIdKey, requestIdValue)))
.And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .And(x => x.GivenTheQos(true, new NoQoSProvider()))
.When(x => x.WhenICreateARequest()) .When(x => x.WhenICreateARequest())
.And(x => x.ThenTheRequestIdIsNotInTheHeaders()) .And(x => x.ThenTheRequestIdIsNotInTheHeaders())
.BDDfy(); .BDDfy();
@ -188,10 +189,10 @@ namespace Ocelot.UnitTests.Request
_requestId = requestId; _requestId = requestId;
} }
private void GivenTheQos(bool isQos, QoSOptions qos) private void GivenTheQos(bool isQos, IQoSProvider qoSProvider)
{ {
_isQos = isQos; _isQos = isQos;
_qos = qos; _qoSProvider = qoSProvider;
} }
[Fact] [Fact]
@ -304,7 +305,7 @@ namespace Ocelot.UnitTests.Request
private void WhenICreateARequest() private void WhenICreateARequest()
{ {
_result = _requestCreator.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers, _result = _requestCreator.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers,
_cookies, _query, _contentType, _requestId,_isQos,_qos).Result; _cookies, _query, _contentType, _requestId,_isQos,_qoSProvider).Result;
} }

View File

@ -13,6 +13,7 @@ using Ocelot.Logging;
using Ocelot.QueryStrings.Middleware; using Ocelot.QueryStrings.Middleware;
using Ocelot.Requester; using Ocelot.Requester;
using Ocelot.Requester.Middleware; using Ocelot.Requester.Middleware;
using Ocelot.Requester.QoS;
using Ocelot.Responder; using Ocelot.Responder;
using Ocelot.Responses; using Ocelot.Responses;
using TestStack.BDDfy; using TestStack.BDDfy;
@ -61,7 +62,7 @@ namespace Ocelot.UnitTests.Requester
[Fact] [Fact]
public void should_call_scoped_data_repository_correctly() public void should_call_scoped_data_repository_correctly()
{ {
this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),new CookieContainer(),true, new Ocelot.Configuration.QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic)))) this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),new CookieContainer(),true, new NoQoSProvider())))
.And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage()))
.And(x => x.GivenTheScopedRepoReturns()) .And(x => x.GivenTheScopedRepoReturns())
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())

View File

@ -0,0 +1,80 @@
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Logging;
using Ocelot.Requester.QoS;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Requester
{
public class QoSProviderFactoryTests
{
private readonly IQoSProviderFactory _factory;
private ReRoute _reRoute;
private IQoSProvider _result;
private Mock<IOcelotLoggerFactory> _loggerFactory;
private Mock<IOcelotLogger> _logger;
public QoSProviderFactoryTests()
{
_logger = new Mock<IOcelotLogger>();
_loggerFactory = new Mock<IOcelotLoggerFactory>();
_loggerFactory
.Setup(x => x.CreateLogger<PollyQoSProvider>())
.Returns(_logger.Object);
_factory = new QoSProviderFactory(_loggerFactory.Object);
}
[Fact]
public void should_return_no_qos_provider()
{
var reRoute = new ReRouteBuilder()
.WithUpstreamHttpMethod("get")
.WithIsQos(false)
.Build();
this.Given(x => x.GivenAReRoute(reRoute))
.When(x => x.WhenIGetTheQoSProvider())
.Then(x => x.ThenTheQoSProviderIsReturned<NoQoSProvider>())
.BDDfy();
}
[Fact]
public void should_return_polly_qos_provider()
{
var qosOptions = new QoSOptionsBuilder()
.WithTimeoutValue(100)
.WithDurationOfBreak(100)
.WithExceptionsAllowedBeforeBreaking(100)
.Build();
var reRoute = new ReRouteBuilder()
.WithUpstreamHttpMethod("get")
.WithIsQos(true)
.WithQosOptions(qosOptions)
.Build();
this.Given(x => x.GivenAReRoute(reRoute))
.When(x => x.WhenIGetTheQoSProvider())
.Then(x => x.ThenTheQoSProviderIsReturned<PollyQoSProvider>())
.BDDfy();
}
private void GivenAReRoute(ReRoute reRoute)
{
_reRoute = reRoute;
}
private void WhenIGetTheQoSProvider()
{
_result = _factory.Get(_reRoute);
}
private void ThenTheQoSProviderIsReturned<T>()
{
_result.ShouldBeOfType<T>();
}
}
}

View File

@ -0,0 +1,117 @@
using Ocelot.Requester.QoS;
using Ocelot.Responses;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Requester
{
public class QosProviderHouseTests
{
private IQoSProvider _qoSProvider;
private readonly QosProviderHouse _qosProviderHouse;
private Response _addResult;
private Response<IQoSProvider> _getResult;
private string _key;
public QosProviderHouseTests()
{
_qosProviderHouse = new QosProviderHouse();
}
[Fact]
public void should_store_qos_provider()
{
var key = "test";
this.Given(x => x.GivenThereIsAQoSProvider(key, new FakeQoSProvider()))
.When(x => x.WhenIAddTheQoSProvider())
.Then(x => x.ThenItIsAdded())
.BDDfy();
}
[Fact]
public void should_get_qos_provider()
{
var key = "test";
this.Given(x => x.GivenThereIsAQoSProvider(key, new FakeQoSProvider()))
.When(x => x.WhenWeGetTheQoSProvider(key))
.Then(x => x.ThenItIsReturned())
.BDDfy();
}
[Fact]
public void should_store_qos_providers_by_key()
{
var key = "test";
var keyTwo = "testTwo";
this.Given(x => x.GivenThereIsAQoSProvider(key, new FakeQoSProvider()))
.And(x => x.GivenThereIsAQoSProvider(keyTwo, new FakePollyQoSProvider()))
.When(x => x.WhenWeGetTheQoSProvider(key))
.Then(x => x.ThenTheQoSProviderIs<FakeQoSProvider>())
.When(x => x.WhenWeGetTheQoSProvider(keyTwo))
.Then(x => x.ThenTheQoSProviderIs<FakePollyQoSProvider>())
.BDDfy();
}
[Fact]
public void should_return_error_if_no_qos_provider_with_key()
{
this.When(x => x.WhenWeGetTheQoSProvider("test"))
.Then(x => x.ThenAnErrorIsReturned())
.BDDfy();
}
private void ThenAnErrorIsReturned()
{
_getResult.IsError.ShouldBeTrue();
_getResult.Errors[0].ShouldBeOfType<UnableToFindQoSProviderError>();
}
private void ThenTheQoSProviderIs<T>()
{
_getResult.Data.ShouldBeOfType<T>();
}
private void ThenItIsAdded()
{
_addResult.IsError.ShouldBe(false);
_addResult.ShouldBeOfType<OkResponse>();
}
private void WhenIAddTheQoSProvider()
{
_addResult = _qosProviderHouse.Add(_key, _qoSProvider);
}
private void GivenThereIsAQoSProvider(string key, IQoSProvider qoSProvider)
{
_key = key;
_qoSProvider = qoSProvider;
WhenIAddTheQoSProvider();
}
private void WhenWeGetTheQoSProvider(string key)
{
_getResult = _qosProviderHouse.Get(key);
}
private void ThenItIsReturned()
{
_getResult.Data.ShouldBe(_qoSProvider);
}
class FakeQoSProvider : IQoSProvider
{
public CircuitBreaker CircuitBreaker { get; }
}
class FakePollyQoSProvider : IQoSProvider
{
public CircuitBreaker CircuitBreaker { get; }
}
}
}

View File

@ -30,7 +30,7 @@ namespace Ocelot.UnitTests.Responder
new RequestTimedOutError(new Exception()) new RequestTimedOutError(new Exception())
})) }))
.When(x => x.WhenIGetErrorStatusCode()) .When(x => x.WhenIGetErrorStatusCode())
.Then(x => x.ThenTheResponseIsStatusCodeIs(408)) .Then(x => x.ThenTheResponseIsStatusCodeIs(503))
.BDDfy(); .BDDfy();
} }