Merge pull request #39 from TomPallister/feature/qos-documentation

Feature/qos documentation
This commit is contained in:
Tom Pallister 2017-02-11 19:01:52 +00:00 committed by GitHub
commit dc8106c904
34 changed files with 1014 additions and 180 deletions

View File

@ -305,19 +305,25 @@ Below is an example configuration that will transforms claims to query string pa
This shows a transform where Ocelot looks at the users LocationId claim and add its as This shows a transform where Ocelot looks at the users LocationId claim and add its as
a query string parameter to be forwarded onto the downstream service. a query string parameter to be forwarded onto the downstream service.
## Logging ## Quality of Service
Ocelot uses the standard logging interfaces ILoggerFactory / ILogger<T> at the moment. Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you
This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation want to use a circuit breaker when making requests to a downstream service. This uses the an awesome
for the standard asp.net core logging stuff at the moment. .NET library called Polly check them out [here](https://github.com/App-vNext/Polly).
There are a bunch of debugging logs in the ocelot middlewares however I think the Add the following section to a ReRoute configuration.
system probably needs more logging in the code it calls into. Other than the debugging
there is a global error handler that should catch any errors thrown and log them as errors.
The reason for not just using bog standard framework logging is that I could not "QoSOptions": {
work out how to override the request id that get's logged when setting IncludeScopes "ExceptionsAllowedBeforeBreaking":3,
to true for logging settings. Nicely onto the next feature. "DurationOfBreak":5,
"TimeoutValue":5000
}
You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be
implemented. Duration of break is how long the circuit breaker will stay open for after it is tripped.
TimeoutValue means ff a request takes more than 5 seconds it will automatically be timed out.
If you do not add a QoS section QoS will not be used.
## RequestId / CorrelationId ## RequestId / CorrelationId
@ -404,6 +410,20 @@ http request before it is passed to Ocelots request creator.
Obviously you can just add middleware as normal before the call to app.UseOcelot() It cannot be added Obviously you can just add middleware as normal before the call to app.UseOcelot() It cannot be added
after as Ocelot does not call the next middleware. after as Ocelot does not call the next middleware.
## Logging
Ocelot uses the standard logging interfaces ILoggerFactory / ILogger<T> at the moment.
This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation
for the standard asp.net core logging stuff at the moment.
There are a bunch of debugging logs in the ocelot middlewares however I think the
system probably needs more logging in the code it calls into. Other than the debugging
there is a global error handler that should catch any errors thrown and log them as errors.
The reason for not just using bog standard framework logging is that I could not
work out how to override the request id that get's logged when setting IncludeScopes
to true for logging settings. Nicely onto the next feature.
## Not supported ## Not supported
Ocelot does not support... Ocelot does not support...

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

@ -22,7 +22,6 @@ namespace Ocelot.Configuration.Builder
private string _requestIdHeaderKey; private string _requestIdHeaderKey;
private bool _isCached; private bool _isCached;
private CacheOptions _fileCacheOptions; private CacheOptions _fileCacheOptions;
private string _serviceName;
private string _downstreamScheme; private string _downstreamScheme;
private string _downstreamHost; private string _downstreamHost;
private int _downstreamPort; private int _downstreamPort;
@ -49,12 +48,6 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public ReRouteBuilder WithServiceName(string serviceName)
{
_serviceName = serviceName;
return this;
}
public ReRouteBuilder WithDownstreamPathTemplate(string input) public ReRouteBuilder WithDownstreamPathTemplate(string input)
{ {
_downstreamPathTemplate = input; _downstreamPathTemplate = input;

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,16 +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);
@ -103,11 +110,12 @@ namespace Ocelot.Configuration.Creator
var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest); var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest);
var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest); var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest);
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,14 +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)
.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);
@ -161,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}";
@ -183,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)
@ -204,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;
@ -228,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,14 +1,15 @@
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
{ {
public class QoSOptions public class QoSOptions
{ {
public QoSOptions(int exceptionsAllowedBeforeBreaking, int durationofBreak, int timeoutValue, TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) public QoSOptions(
int exceptionsAllowedBeforeBreaking,
int durationofBreak,
int timeoutValue,
TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic)
{ {
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak); DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak);

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

@ -25,6 +25,8 @@
ServicesAreNullError, ServicesAreNullError,
ServicesAreEmptyError, ServicesAreEmptyError,
UnableToFindServiceDiscoveryProviderError, UnableToFindServiceDiscoveryProviderError,
UnableToFindLoadBalancerError UnableToFindLoadBalancerError,
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,74 +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 Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Task<HttpResponseMessage> responseTask = null;
try
{
responseTask = Policy.WrapAsync(_circuitBreakerPolicy, _timeoutPolicy).ExecuteAsync<HttpResponseMessage>(() =>
{
return base.SendAsync(request,cancellationToken);
});
return responseTask;
}
catch (BrokenCircuitException ex)
{
_logger.LogError($"Reached to allowed number of exceptions. Circuit is open. AllowedExceptionCount: {_exceptionsAllowedBeforeBreaking}, DurationOfBreak: {_durationOfBreak}",ex);
throw;
}
catch (HttpRequestException)
{
return responseTask;
}
}
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,14 +19,15 @@ 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))
{ {
try try
@ -34,13 +35,19 @@ 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 (Exception exception) catch (TimeoutRejectedException exception)
{ {
return return
new ErrorResponse<HttpResponseMessage>(new List<Error> new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
}
catch (BrokenCircuitException exception)
{ {
new UnableToCompleteRequestError(exception) return
}); new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
}
catch (Exception exception)
{
return new ErrorResponse<HttpResponseMessage>(new UnableToCompleteRequestError(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

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

View File

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

View File

@ -5,7 +5,13 @@ namespace Ocelot.Responses
{ {
public class ErrorResponse<T> : Response<T> public class ErrorResponse<T> : Response<T>
{ {
public ErrorResponse(List<Error> errors) : base(errors) public ErrorResponse(Error error)
: base(new List<Error> {error})
{
}
public ErrorResponse(List<Error> errors)
: base(errors)
{ {
} }
} }

View File

@ -0,0 +1,206 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.AcceptanceTests
{
public class QoSTests : IDisposable
{
private IWebHost _brokenService;
private readonly Steps _steps;
private int _requestCount;
private IWebHost _workingService;
public QoSTests()
{
_steps = new Steps();
}
[Fact]
public void should_open_circuit_breaker_then_close()
{
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
}
}
}
};
this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura"))
.Given(x => _steps.GivenThereIsAConfiguration(configuration))
.Given(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.Given(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.Given(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.Given(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.Given(x => x.GivenIWaitMilliseconds(3000))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
[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);
}
private void GivenThereIsAPossiblyBrokenServiceRunningOn(string url, string responseBody)
{
_brokenService = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
//circuit starts closed
if (_requestCount == 0)
{
_requestCount++;
context.Response.StatusCode = 200;
await context.Response.WriteAsync(responseBody);
return;
}
//request one times out and polly throws exception, circuit opens
if (_requestCount == 1)
{
_requestCount++;
await Task.Delay(1000);
context.Response.StatusCode = 200;
return;
}
//after break closes we return 200 OK
if (_requestCount == 2)
{
context.Response.StatusCode = 200;
await context.Response.WriteAsync(responseBody);
return;
}
});
})
.Build();
_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()
{
_workingService?.Dispose();
_brokenService?.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();
} }
@ -164,11 +201,11 @@ namespace Ocelot.UnitTests.Configuration
.WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.WithServiceName("ProductService")
.WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder() .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder()
.WithUseServiceDiscovery(true) .WithUseServiceDiscovery(true)
.WithServiceDiscoveryProvider("consul") .WithServiceDiscoveryProvider("consul")
.WithServiceDiscoveryProviderHost("127.0.0.1") .WithServiceDiscoveryProviderHost("127.0.0.1")
.WithServiceName("ProductService")
.Build()) .Build())
.Build() .Build()
})) }))
@ -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

@ -1,6 +1,8 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using Ocelot.Errors; using Ocelot.Errors;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Requester;
using Ocelot.Responder; using Ocelot.Responder;
using Ocelot.Responses; using Ocelot.Responses;
using Shouldly; using Shouldly;
@ -20,6 +22,18 @@ namespace Ocelot.UnitTests.Responder
_codeMapper = new ErrorsToHttpStatusCodeMapper(); _codeMapper = new ErrorsToHttpStatusCodeMapper();
} }
[Fact]
public void should_return_timeout()
{
this.Given(x => x.GivenThereAreErrors(new List<Error>
{
new RequestTimedOutError(new Exception())
}))
.When(x => x.WhenIGetErrorStatusCode())
.Then(x => x.ThenTheResponseIsStatusCodeIs(503))
.BDDfy();
}
[Fact] [Fact]
public void should_create_unauthenticated_response_code() public void should_create_unauthenticated_response_code()
{ {