mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 02:42:52 +08:00
Feature/sticky sessions (#336)
* started messing around with sticky sessions idea * more tests for sticky session thing * more faffing cant make up my mind how to do this * +semver: breaking added sticky session load balancer and changed way load balancer configuration is set by user * #336 made tests BDDFy
This commit is contained in:
parent
97e7d32d14
commit
6793278597
@ -1,7 +1,7 @@
|
|||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
end_of_line = crlf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[*.cs]
|
[*.cs]
|
||||||
|
@ -11,6 +11,8 @@ The type of load balancer available are:
|
|||||||
|
|
||||||
NoLoadBalancer - takes the first available service from config or service discovery.
|
NoLoadBalancer - takes the first available service from config or service discovery.
|
||||||
|
|
||||||
|
CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below.
|
||||||
|
|
||||||
You must choose in your configuration which load balancer to use.
|
You must choose in your configuration which load balancer to use.
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
@ -34,7 +36,9 @@ The following shows how to set up multiple downstream services for a ReRoute usi
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
"LoadBalancer": "LeastConnection",
|
"LoadBalancerOptions": {
|
||||||
|
"Type": "LeastConnection"
|
||||||
|
},
|
||||||
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,9 +56,56 @@ The following shows how to set up a ReRoute using service discovery then select
|
|||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
"UpstreamHttpMethod": [ "Put" ],
|
"UpstreamHttpMethod": [ "Put" ],
|
||||||
"ServiceName": "product",
|
"ServiceName": "product",
|
||||||
"LoadBalancer": "LeastConnection",
|
"LoadBalancerOptions": {
|
||||||
|
"Type": "LeastConnection"
|
||||||
|
},
|
||||||
"UseServiceDiscovery": true
|
"UseServiceDiscovery": true
|
||||||
}
|
}
|
||||||
|
|
||||||
When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the
|
When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the
|
||||||
service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added.
|
service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added.
|
||||||
|
|
||||||
|
CookieStickySessions
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream
|
||||||
|
servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each
|
||||||
|
time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 <https://github.com/ThreeMammals/Ocelot/issues/322>`_
|
||||||
|
though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have!
|
||||||
|
|
||||||
|
In order to set up CookieStickySessions load balancer you need to do something like the following.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "10.0.1.10",
|
||||||
|
"Port": 5000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Host": "10.0.1.11",
|
||||||
|
"Port": 5000,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"LoadBalancerOptions": {
|
||||||
|
"Type": "CookieStickySessions",
|
||||||
|
"Key": "ASP.NET_SessionId",
|
||||||
|
"Expiry": 1800000
|
||||||
|
},
|
||||||
|
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you
|
||||||
|
wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this
|
||||||
|
refreshes on every request which is meant to mimick how sessions work usually.
|
||||||
|
|
||||||
|
If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there
|
||||||
|
subsequent requests. This means the sessions will be stuck across ReRoutes.
|
||||||
|
|
||||||
|
Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul
|
||||||
|
and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the
|
||||||
|
moment but could be changed.
|
@ -24,7 +24,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
private bool _isCached;
|
private bool _isCached;
|
||||||
private CacheOptions _fileCacheOptions;
|
private CacheOptions _fileCacheOptions;
|
||||||
private string _downstreamScheme;
|
private string _downstreamScheme;
|
||||||
private string _loadBalancer;
|
private LoadBalancerOptions _loadBalancerOptions;
|
||||||
private bool _useQos;
|
private bool _useQos;
|
||||||
private QoSOptions _qosOptions;
|
private QoSOptions _qosOptions;
|
||||||
private HttpHandlerOptions _httpHandlerOptions;
|
private HttpHandlerOptions _httpHandlerOptions;
|
||||||
@ -41,6 +41,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
private List<AddHeader> _addHeadersToDownstream;
|
private List<AddHeader> _addHeadersToDownstream;
|
||||||
private List<AddHeader> _addHeadersToUpstream;
|
private List<AddHeader> _addHeadersToUpstream;
|
||||||
private bool _dangerousAcceptAnyServerCertificateValidator;
|
private bool _dangerousAcceptAnyServerCertificateValidator;
|
||||||
|
private string _qosKey;
|
||||||
|
|
||||||
public DownstreamReRouteBuilder()
|
public DownstreamReRouteBuilder()
|
||||||
{
|
{
|
||||||
@ -62,9 +63,9 @@ namespace Ocelot.Configuration.Builder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownstreamReRouteBuilder WithLoadBalancer(string loadBalancer)
|
public DownstreamReRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions)
|
||||||
{
|
{
|
||||||
_loadBalancer = loadBalancer;
|
_loadBalancerOptions = loadBalancerOptions;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +171,12 @@ namespace Ocelot.Configuration.Builder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DownstreamReRouteBuilder WithQosKey(string qosKey)
|
||||||
|
{
|
||||||
|
_qosKey = qosKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public DownstreamReRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions)
|
public DownstreamReRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions)
|
||||||
{
|
{
|
||||||
_authenticationOptions = authenticationOptions;
|
_authenticationOptions = authenticationOptions;
|
||||||
@ -266,7 +273,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
_requestIdHeaderKey,
|
_requestIdHeaderKey,
|
||||||
_isCached,
|
_isCached,
|
||||||
_fileCacheOptions,
|
_fileCacheOptions,
|
||||||
_loadBalancer,
|
_loadBalancerOptions,
|
||||||
_rateLimitOptions,
|
_rateLimitOptions,
|
||||||
_routeClaimRequirement,
|
_routeClaimRequirement,
|
||||||
_claimToQueries,
|
_claimToQueries,
|
||||||
@ -280,7 +287,8 @@ namespace Ocelot.Configuration.Builder
|
|||||||
_delegatingHandlers,
|
_delegatingHandlers,
|
||||||
_addHeadersToDownstream,
|
_addHeadersToDownstream,
|
||||||
_addHeadersToUpstream,
|
_addHeadersToUpstream,
|
||||||
_dangerousAcceptAnyServerCertificateValidator);
|
_dangerousAcceptAnyServerCertificateValidator,
|
||||||
|
_qosKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,229 +1,251 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Ocelot.Cache;
|
using Ocelot.Cache;
|
||||||
using Ocelot.Configuration.Builder;
|
using Ocelot.Configuration.Builder;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Configuration.Validator;
|
using Ocelot.Configuration.Validator;
|
||||||
using Ocelot.DependencyInjection;
|
using Ocelot.DependencyInjection;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Creator
|
namespace Ocelot.Configuration.Creator
|
||||||
{
|
{
|
||||||
/// <summary>
|
using LoadBalancer.LoadBalancers;
|
||||||
/// Register as singleton
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
public class FileInternalConfigurationCreator : IInternalConfigurationCreator
|
/// Register as singleton
|
||||||
{
|
/// </summary>
|
||||||
private readonly IConfigurationValidator _configurationValidator;
|
public class FileInternalConfigurationCreator : IInternalConfigurationCreator
|
||||||
private readonly IOcelotLogger _logger;
|
{
|
||||||
private readonly IClaimsToThingCreator _claimsToThingCreator;
|
private readonly IConfigurationValidator _configurationValidator;
|
||||||
private readonly IAuthenticationOptionsCreator _authOptionsCreator;
|
private readonly IOcelotLogger _logger;
|
||||||
private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator;
|
private readonly IClaimsToThingCreator _claimsToThingCreator;
|
||||||
private readonly IRequestIdKeyCreator _requestIdKeyCreator;
|
private readonly IAuthenticationOptionsCreator _authOptionsCreator;
|
||||||
private readonly IServiceProviderConfigurationCreator _serviceProviderConfigCreator;
|
private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator;
|
||||||
private readonly IQoSOptionsCreator _qosOptionsCreator;
|
private readonly IRequestIdKeyCreator _requestIdKeyCreator;
|
||||||
private readonly IReRouteOptionsCreator _fileReRouteOptionsCreator;
|
private readonly IServiceProviderConfigurationCreator _serviceProviderConfigCreator;
|
||||||
private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator;
|
private readonly IQoSOptionsCreator _qosOptionsCreator;
|
||||||
private readonly IRegionCreator _regionCreator;
|
private readonly IReRouteOptionsCreator _fileReRouteOptionsCreator;
|
||||||
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
|
private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator;
|
||||||
private readonly IAdministrationPath _adminPath;
|
private readonly IRegionCreator _regionCreator;
|
||||||
private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator;
|
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
|
||||||
private readonly IDownstreamAddressesCreator _downstreamAddressesCreator;
|
private readonly IAdministrationPath _adminPath;
|
||||||
|
private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator;
|
||||||
public FileInternalConfigurationCreator(
|
private readonly IDownstreamAddressesCreator _downstreamAddressesCreator;
|
||||||
IConfigurationValidator configurationValidator,
|
|
||||||
IOcelotLoggerFactory loggerFactory,
|
public FileInternalConfigurationCreator(
|
||||||
IClaimsToThingCreator claimsToThingCreator,
|
IConfigurationValidator configurationValidator,
|
||||||
IAuthenticationOptionsCreator authOptionsCreator,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator,
|
IClaimsToThingCreator claimsToThingCreator,
|
||||||
IRequestIdKeyCreator requestIdKeyCreator,
|
IAuthenticationOptionsCreator authOptionsCreator,
|
||||||
IServiceProviderConfigurationCreator serviceProviderConfigCreator,
|
IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator,
|
||||||
IQoSOptionsCreator qosOptionsCreator,
|
IRequestIdKeyCreator requestIdKeyCreator,
|
||||||
IReRouteOptionsCreator fileReRouteOptionsCreator,
|
IServiceProviderConfigurationCreator serviceProviderConfigCreator,
|
||||||
IRateLimitOptionsCreator rateLimitOptionsCreator,
|
IQoSOptionsCreator qosOptionsCreator,
|
||||||
IRegionCreator regionCreator,
|
IReRouteOptionsCreator fileReRouteOptionsCreator,
|
||||||
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
|
IRateLimitOptionsCreator rateLimitOptionsCreator,
|
||||||
IAdministrationPath adminPath,
|
IRegionCreator regionCreator,
|
||||||
IHeaderFindAndReplaceCreator headerFAndRCreator,
|
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
|
||||||
IDownstreamAddressesCreator downstreamAddressesCreator
|
IAdministrationPath adminPath,
|
||||||
)
|
IHeaderFindAndReplaceCreator headerFAndRCreator,
|
||||||
{
|
IDownstreamAddressesCreator downstreamAddressesCreator
|
||||||
_downstreamAddressesCreator = downstreamAddressesCreator;
|
)
|
||||||
_headerFAndRCreator = headerFAndRCreator;
|
{
|
||||||
_adminPath = adminPath;
|
_downstreamAddressesCreator = downstreamAddressesCreator;
|
||||||
_regionCreator = regionCreator;
|
_headerFAndRCreator = headerFAndRCreator;
|
||||||
_rateLimitOptionsCreator = rateLimitOptionsCreator;
|
_adminPath = adminPath;
|
||||||
_requestIdKeyCreator = requestIdKeyCreator;
|
_regionCreator = regionCreator;
|
||||||
_upstreamTemplatePatternCreator = upstreamTemplatePatternCreator;
|
_rateLimitOptionsCreator = rateLimitOptionsCreator;
|
||||||
_authOptionsCreator = authOptionsCreator;
|
_requestIdKeyCreator = requestIdKeyCreator;
|
||||||
_configurationValidator = configurationValidator;
|
_upstreamTemplatePatternCreator = upstreamTemplatePatternCreator;
|
||||||
_logger = loggerFactory.CreateLogger<FileInternalConfigurationCreator>();
|
_authOptionsCreator = authOptionsCreator;
|
||||||
_claimsToThingCreator = claimsToThingCreator;
|
_configurationValidator = configurationValidator;
|
||||||
_serviceProviderConfigCreator = serviceProviderConfigCreator;
|
_logger = loggerFactory.CreateLogger<FileInternalConfigurationCreator>();
|
||||||
_qosOptionsCreator = qosOptionsCreator;
|
_claimsToThingCreator = claimsToThingCreator;
|
||||||
_fileReRouteOptionsCreator = fileReRouteOptionsCreator;
|
_serviceProviderConfigCreator = serviceProviderConfigCreator;
|
||||||
_httpHandlerOptionsCreator = httpHandlerOptionsCreator;
|
_qosOptionsCreator = qosOptionsCreator;
|
||||||
}
|
_fileReRouteOptionsCreator = fileReRouteOptionsCreator;
|
||||||
|
_httpHandlerOptionsCreator = httpHandlerOptionsCreator;
|
||||||
public async Task<Response<IInternalConfiguration>> Create(FileConfiguration fileConfiguration)
|
}
|
||||||
{
|
|
||||||
var config = await SetUpConfiguration(fileConfiguration);
|
public async Task<Response<IInternalConfiguration>> Create(FileConfiguration fileConfiguration)
|
||||||
return config;
|
{
|
||||||
}
|
var config = await SetUpConfiguration(fileConfiguration);
|
||||||
|
return config;
|
||||||
private async Task<Response<IInternalConfiguration>> SetUpConfiguration(FileConfiguration fileConfiguration)
|
}
|
||||||
{
|
|
||||||
var response = await _configurationValidator.IsValid(fileConfiguration);
|
private async Task<Response<IInternalConfiguration>> SetUpConfiguration(FileConfiguration fileConfiguration)
|
||||||
|
{
|
||||||
if (response.Data.IsError)
|
var response = await _configurationValidator.IsValid(fileConfiguration);
|
||||||
{
|
|
||||||
return new ErrorResponse<IInternalConfiguration>(response.Data.Errors);
|
if (response.Data.IsError)
|
||||||
}
|
{
|
||||||
|
return new ErrorResponse<IInternalConfiguration>(response.Data.Errors);
|
||||||
var reRoutes = new List<ReRoute>();
|
}
|
||||||
|
|
||||||
foreach (var reRoute in fileConfiguration.ReRoutes)
|
var reRoutes = new List<ReRoute>();
|
||||||
{
|
|
||||||
var downstreamReRoute = SetUpDownstreamReRoute(reRoute, fileConfiguration.GlobalConfiguration);
|
foreach (var reRoute in fileConfiguration.ReRoutes)
|
||||||
|
{
|
||||||
var ocelotReRoute = SetUpReRoute(reRoute, downstreamReRoute);
|
var downstreamReRoute = SetUpDownstreamReRoute(reRoute, fileConfiguration.GlobalConfiguration);
|
||||||
|
|
||||||
reRoutes.Add(ocelotReRoute);
|
var ocelotReRoute = SetUpReRoute(reRoute, downstreamReRoute);
|
||||||
}
|
|
||||||
|
reRoutes.Add(ocelotReRoute);
|
||||||
foreach (var aggregate in fileConfiguration.Aggregates)
|
}
|
||||||
{
|
|
||||||
var ocelotReRoute = SetUpAggregateReRoute(reRoutes, aggregate, fileConfiguration.GlobalConfiguration);
|
foreach (var aggregate in fileConfiguration.Aggregates)
|
||||||
reRoutes.Add(ocelotReRoute);
|
{
|
||||||
}
|
var ocelotReRoute = SetUpAggregateReRoute(reRoutes, aggregate, fileConfiguration.GlobalConfiguration);
|
||||||
|
reRoutes.Add(ocelotReRoute);
|
||||||
var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
|
}
|
||||||
|
|
||||||
var config = new InternalConfiguration(reRoutes, _adminPath.Path, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey);
|
var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
|
||||||
|
|
||||||
return new OkResponse<IInternalConfiguration>(config);
|
var config = new InternalConfiguration(reRoutes, _adminPath.Path, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey);
|
||||||
}
|
|
||||||
|
return new OkResponse<IInternalConfiguration>(config);
|
||||||
public ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration)
|
}
|
||||||
{
|
|
||||||
var applicableReRoutes = reRoutes
|
public ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration)
|
||||||
.SelectMany(x => x.DownstreamReRoute)
|
{
|
||||||
.Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key))
|
var applicableReRoutes = reRoutes
|
||||||
.ToList();
|
.SelectMany(x => x.DownstreamReRoute)
|
||||||
|
.Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key))
|
||||||
if(applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count)
|
.ToList();
|
||||||
{
|
|
||||||
//todo - log or throw or return error whatever?
|
if(applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count)
|
||||||
}
|
{
|
||||||
|
//todo - log or throw or return error whatever?
|
||||||
//make another re route out of these
|
}
|
||||||
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateReRoute);
|
|
||||||
|
//make another re route out of these
|
||||||
var reRoute = new ReRouteBuilder()
|
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateReRoute);
|
||||||
.WithUpstreamPathTemplate(aggregateReRoute.UpstreamPathTemplate)
|
|
||||||
.WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod)
|
var reRoute = new ReRouteBuilder()
|
||||||
.WithUpstreamTemplatePattern(upstreamTemplatePattern)
|
.WithUpstreamPathTemplate(aggregateReRoute.UpstreamPathTemplate)
|
||||||
.WithDownstreamReRoutes(applicableReRoutes)
|
.WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod)
|
||||||
.WithUpstreamHost(aggregateReRoute.UpstreamHost)
|
.WithUpstreamTemplatePattern(upstreamTemplatePattern)
|
||||||
.WithAggregator(aggregateReRoute.Aggregator)
|
.WithDownstreamReRoutes(applicableReRoutes)
|
||||||
.Build();
|
.WithUpstreamHost(aggregateReRoute.UpstreamHost)
|
||||||
|
.WithAggregator(aggregateReRoute.Aggregator)
|
||||||
return reRoute;
|
.Build();
|
||||||
}
|
|
||||||
|
return reRoute;
|
||||||
private ReRoute SetUpReRoute(FileReRoute fileReRoute, DownstreamReRoute downstreamReRoutes)
|
}
|
||||||
{
|
|
||||||
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
|
private ReRoute SetUpReRoute(FileReRoute fileReRoute, DownstreamReRoute downstreamReRoutes)
|
||||||
|
{
|
||||||
var reRoute = new ReRouteBuilder()
|
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
|
||||||
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
|
|
||||||
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
|
var reRoute = new ReRouteBuilder()
|
||||||
.WithUpstreamTemplatePattern(upstreamTemplatePattern)
|
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
|
||||||
.WithDownstreamReRoute(downstreamReRoutes)
|
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
|
||||||
.WithUpstreamHost(fileReRoute.UpstreamHost)
|
.WithUpstreamTemplatePattern(upstreamTemplatePattern)
|
||||||
.Build();
|
.WithDownstreamReRoute(downstreamReRoutes)
|
||||||
|
.WithUpstreamHost(fileReRoute.UpstreamHost)
|
||||||
return reRoute;
|
.Build();
|
||||||
}
|
|
||||||
|
return reRoute;
|
||||||
private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
|
}
|
||||||
{
|
|
||||||
var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute);
|
private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
|
||||||
|
{
|
||||||
var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration);
|
var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute);
|
||||||
|
|
||||||
var reRouteKey = CreateReRouteKey(fileReRoute);
|
var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration);
|
||||||
|
|
||||||
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
|
var reRouteKey = CreateReRouteKey(fileReRoute);
|
||||||
|
|
||||||
var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute);
|
var qosKey = CreateQosKey(fileReRoute);
|
||||||
|
|
||||||
var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest);
|
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
|
||||||
|
|
||||||
var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest);
|
var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute);
|
||||||
|
|
||||||
var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest);
|
var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest);
|
||||||
|
|
||||||
var qosOptions = _qosOptionsCreator.Create(fileReRoute);
|
var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest);
|
||||||
|
|
||||||
var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting);
|
var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest);
|
||||||
|
|
||||||
var region = _regionCreator.Create(fileReRoute);
|
var qosOptions = _qosOptionsCreator.Create(fileReRoute);
|
||||||
|
|
||||||
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute);
|
var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting);
|
||||||
|
|
||||||
var hAndRs = _headerFAndRCreator.Create(fileReRoute);
|
var region = _regionCreator.Create(fileReRoute);
|
||||||
|
|
||||||
var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute);
|
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute);
|
||||||
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var hAndRs = _headerFAndRCreator.Create(fileReRoute);
|
||||||
.WithKey(fileReRoute.Key)
|
|
||||||
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
|
var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute);
|
||||||
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
|
|
||||||
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
|
var lbOptions = CreateLoadBalancerOptions(fileReRoute);
|
||||||
.WithUpstreamTemplatePattern(upstreamTemplatePattern)
|
|
||||||
.WithIsAuthenticated(fileReRouteOptions.IsAuthenticated)
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithAuthenticationOptions(authOptionsForRoute)
|
.WithKey(fileReRoute.Key)
|
||||||
.WithClaimsToHeaders(claimsToHeaders)
|
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
|
||||||
.WithClaimsToClaims(claimsToClaims)
|
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
|
||||||
.WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement)
|
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
|
||||||
.WithIsAuthorised(fileReRouteOptions.IsAuthorised)
|
.WithUpstreamTemplatePattern(upstreamTemplatePattern)
|
||||||
.WithClaimsToQueries(claimsToQueries)
|
.WithIsAuthenticated(fileReRouteOptions.IsAuthenticated)
|
||||||
.WithRequestIdKey(requestIdKey)
|
.WithAuthenticationOptions(authOptionsForRoute)
|
||||||
.WithIsCached(fileReRouteOptions.IsCached)
|
.WithClaimsToHeaders(claimsToHeaders)
|
||||||
.WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region))
|
.WithClaimsToClaims(claimsToClaims)
|
||||||
.WithDownstreamScheme(fileReRoute.DownstreamScheme)
|
.WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement)
|
||||||
.WithLoadBalancer(fileReRoute.LoadBalancer)
|
.WithIsAuthorised(fileReRouteOptions.IsAuthorised)
|
||||||
.WithDownstreamAddresses(downstreamAddresses)
|
.WithClaimsToQueries(claimsToQueries)
|
||||||
.WithReRouteKey(reRouteKey)
|
.WithRequestIdKey(requestIdKey)
|
||||||
.WithIsQos(fileReRouteOptions.IsQos)
|
.WithIsCached(fileReRouteOptions.IsCached)
|
||||||
.WithQosOptions(qosOptions)
|
.WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region))
|
||||||
.WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting)
|
.WithDownstreamScheme(fileReRoute.DownstreamScheme)
|
||||||
.WithRateLimitOptions(rateLimitOption)
|
.WithLoadBalancerOptions(lbOptions)
|
||||||
.WithHttpHandlerOptions(httpHandlerOptions)
|
.WithDownstreamAddresses(downstreamAddresses)
|
||||||
.WithServiceName(fileReRoute.ServiceName)
|
.WithReRouteKey(reRouteKey)
|
||||||
.WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery)
|
.WithQosKey(qosKey)
|
||||||
.WithUpstreamHeaderFindAndReplace(hAndRs.Upstream)
|
.WithIsQos(fileReRouteOptions.IsQos)
|
||||||
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
|
.WithQosOptions(qosOptions)
|
||||||
.WithUpstreamHost(fileReRoute.UpstreamHost)
|
.WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting)
|
||||||
.WithDelegatingHandlers(fileReRoute.DelegatingHandlers)
|
.WithRateLimitOptions(rateLimitOption)
|
||||||
.WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream)
|
.WithHttpHandlerOptions(httpHandlerOptions)
|
||||||
.WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream)
|
.WithServiceName(fileReRoute.ServiceName)
|
||||||
.WithDangerousAcceptAnyServerCertificateValidator(fileReRoute.DangerousAcceptAnyServerCertificateValidator)
|
.WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery)
|
||||||
.Build();
|
.WithUpstreamHeaderFindAndReplace(hAndRs.Upstream)
|
||||||
|
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
|
||||||
return reRoute;
|
.WithUpstreamHost(fileReRoute.UpstreamHost)
|
||||||
}
|
.WithDelegatingHandlers(fileReRoute.DelegatingHandlers)
|
||||||
|
.WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream)
|
||||||
private string CreateReRouteKey(FileReRoute fileReRoute)
|
.WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream)
|
||||||
{
|
.WithDangerousAcceptAnyServerCertificateValidator(fileReRoute.DangerousAcceptAnyServerCertificateValidator)
|
||||||
//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
|
.Build();
|
||||||
var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}";
|
|
||||||
return loadBalancerKey;
|
return reRoute;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
private LoadBalancerOptions CreateLoadBalancerOptions(FileReRoute fileReRoute)
|
||||||
|
{
|
||||||
|
return new LoadBalancerOptions(fileReRoute.LoadBalancerOptions.Type, fileReRoute.LoadBalancerOptions.Key, fileReRoute.LoadBalancerOptions.Expiry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CreateReRouteKey(FileReRoute fileReRoute)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(fileReRoute.LoadBalancerOptions.Type) && !string.IsNullOrEmpty(fileReRoute.LoadBalancerOptions.Key) && fileReRoute.LoadBalancerOptions.Type == nameof(CookieStickySessions))
|
||||||
|
{
|
||||||
|
return $"{nameof(CookieStickySessions)}:{fileReRoute.LoadBalancerOptions.Key}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateQosKey(fileReRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CreateQosKey(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
|
||||||
|
var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}";
|
||||||
|
return loadBalancerKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,42 +1,43 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Ocelot.Configuration.Creator;
|
|
||||||
using Ocelot.Values;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration
|
namespace Ocelot.Configuration
|
||||||
{
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Creator;
|
||||||
|
using Values;
|
||||||
|
|
||||||
public class DownstreamReRoute
|
public class DownstreamReRoute
|
||||||
{
|
{
|
||||||
public DownstreamReRoute(
|
public DownstreamReRoute(
|
||||||
string key,
|
string key,
|
||||||
PathTemplate upstreamPathTemplate,
|
PathTemplate upstreamPathTemplate,
|
||||||
List<HeaderFindAndReplace> upstreamHeadersFindAndReplace,
|
List<HeaderFindAndReplace> upstreamHeadersFindAndReplace,
|
||||||
List<HeaderFindAndReplace> downstreamHeadersFindAndReplace,
|
List<HeaderFindAndReplace> downstreamHeadersFindAndReplace,
|
||||||
List<DownstreamHostAndPort> downstreamAddresses,
|
List<DownstreamHostAndPort> downstreamAddresses,
|
||||||
string serviceName,
|
string serviceName,
|
||||||
HttpHandlerOptions httpHandlerOptions,
|
HttpHandlerOptions httpHandlerOptions,
|
||||||
bool useServiceDiscovery,
|
bool useServiceDiscovery,
|
||||||
bool enableEndpointEndpointRateLimiting,
|
bool enableEndpointEndpointRateLimiting,
|
||||||
bool isQos,
|
bool isQos,
|
||||||
QoSOptions qosOptionsOptions,
|
QoSOptions qosOptionsOptions,
|
||||||
string downstreamScheme,
|
string downstreamScheme,
|
||||||
string requestIdKey,
|
string requestIdKey,
|
||||||
bool isCached,
|
bool isCached,
|
||||||
CacheOptions cacheOptions,
|
CacheOptions cacheOptions,
|
||||||
string loadBalancer,
|
LoadBalancerOptions loadBalancerOptions,
|
||||||
RateLimitOptions rateLimitOptions,
|
RateLimitOptions rateLimitOptions,
|
||||||
Dictionary<string, string> routeClaimsRequirement,
|
Dictionary<string, string> routeClaimsRequirement,
|
||||||
List<ClaimToThing> claimsToQueries,
|
List<ClaimToThing> claimsToQueries,
|
||||||
List<ClaimToThing> claimsToHeaders,
|
List<ClaimToThing> claimsToHeaders,
|
||||||
List<ClaimToThing> claimsToClaims,
|
List<ClaimToThing> claimsToClaims,
|
||||||
bool isAuthenticated,
|
bool isAuthenticated,
|
||||||
bool isAuthorised,
|
bool isAuthorised,
|
||||||
AuthenticationOptions authenticationOptions,
|
AuthenticationOptions authenticationOptions,
|
||||||
PathTemplate downstreamPathTemplate,
|
PathTemplate downstreamPathTemplate,
|
||||||
string reRouteKey,
|
string loadBalancerKey,
|
||||||
List<string> delegatingHandlers,
|
List<string> delegatingHandlers,
|
||||||
List<AddHeader> addHeadersToDownstream,
|
List<AddHeader> addHeadersToDownstream,
|
||||||
List<AddHeader> addHeadersToUpstream,
|
List<AddHeader> addHeadersToUpstream,
|
||||||
bool dangerousAcceptAnyServerCertificateValidator)
|
bool dangerousAcceptAnyServerCertificateValidator,
|
||||||
|
string qosKey)
|
||||||
{
|
{
|
||||||
DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator;
|
DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator;
|
||||||
AddHeadersToDownstream = addHeadersToDownstream;
|
AddHeadersToDownstream = addHeadersToDownstream;
|
||||||
@ -56,7 +57,7 @@ namespace Ocelot.Configuration
|
|||||||
RequestIdKey = requestIdKey;
|
RequestIdKey = requestIdKey;
|
||||||
IsCached = isCached;
|
IsCached = isCached;
|
||||||
CacheOptions = cacheOptions;
|
CacheOptions = cacheOptions;
|
||||||
LoadBalancer = loadBalancer;
|
LoadBalancerOptions = loadBalancerOptions;
|
||||||
RateLimitOptions = rateLimitOptions;
|
RateLimitOptions = rateLimitOptions;
|
||||||
RouteClaimsRequirement = routeClaimsRequirement;
|
RouteClaimsRequirement = routeClaimsRequirement;
|
||||||
ClaimsToQueries = claimsToQueries ?? new List<ClaimToThing>();
|
ClaimsToQueries = claimsToQueries ?? new List<ClaimToThing>();
|
||||||
@ -66,39 +67,41 @@ namespace Ocelot.Configuration
|
|||||||
IsAuthorised = isAuthorised;
|
IsAuthorised = isAuthorised;
|
||||||
AuthenticationOptions = authenticationOptions;
|
AuthenticationOptions = authenticationOptions;
|
||||||
DownstreamPathTemplate = downstreamPathTemplate;
|
DownstreamPathTemplate = downstreamPathTemplate;
|
||||||
ReRouteKey = reRouteKey;
|
LoadBalancerKey = loadBalancerKey;
|
||||||
AddHeadersToUpstream = addHeadersToUpstream;
|
AddHeadersToUpstream = addHeadersToUpstream;
|
||||||
|
QosKey = qosKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Key { get; private set; }
|
public string QosKey { get; }
|
||||||
public PathTemplate UpstreamPathTemplate { get;private set; }
|
public string Key { get; }
|
||||||
public List<HeaderFindAndReplace> UpstreamHeadersFindAndReplace {get;private set;}
|
public PathTemplate UpstreamPathTemplate { get; }
|
||||||
public List<HeaderFindAndReplace> DownstreamHeadersFindAndReplace { get; private set; }
|
public List<HeaderFindAndReplace> UpstreamHeadersFindAndReplace { get; }
|
||||||
public List<DownstreamHostAndPort> DownstreamAddresses { get; private set; }
|
public List<HeaderFindAndReplace> DownstreamHeadersFindAndReplace { get; }
|
||||||
public string ServiceName { get; private set; }
|
public List<DownstreamHostAndPort> DownstreamAddresses { get; }
|
||||||
public HttpHandlerOptions HttpHandlerOptions { get; private set; }
|
public string ServiceName { get; }
|
||||||
public bool UseServiceDiscovery { get; private set; }
|
public HttpHandlerOptions HttpHandlerOptions { get; }
|
||||||
public bool EnableEndpointEndpointRateLimiting { get; private set; }
|
public bool UseServiceDiscovery { get; }
|
||||||
public bool IsQos { get; private set; }
|
public bool EnableEndpointEndpointRateLimiting { get; }
|
||||||
public QoSOptions QosOptionsOptions { get; private set; }
|
public bool IsQos { get; }
|
||||||
public string DownstreamScheme { get; private set; }
|
public QoSOptions QosOptionsOptions { get; }
|
||||||
public string RequestIdKey { get; private set; }
|
public string DownstreamScheme { get; }
|
||||||
public bool IsCached { get; private set; }
|
public string RequestIdKey { get; }
|
||||||
public CacheOptions CacheOptions { get; private set; }
|
public bool IsCached { get; }
|
||||||
public string LoadBalancer { get; private set; }
|
public CacheOptions CacheOptions { get; }
|
||||||
public RateLimitOptions RateLimitOptions { get; private set; }
|
public LoadBalancerOptions LoadBalancerOptions { get; }
|
||||||
public Dictionary<string, string> RouteClaimsRequirement { get; private set; }
|
public RateLimitOptions RateLimitOptions { get; }
|
||||||
public List<ClaimToThing> ClaimsToQueries { get; private set; }
|
public Dictionary<string, string> RouteClaimsRequirement { get; }
|
||||||
public List<ClaimToThing> ClaimsToHeaders { get; private set; }
|
public List<ClaimToThing> ClaimsToQueries { get; }
|
||||||
public List<ClaimToThing> ClaimsToClaims { get; private set; }
|
public List<ClaimToThing> ClaimsToHeaders { get; }
|
||||||
public bool IsAuthenticated { get; private set; }
|
public List<ClaimToThing> ClaimsToClaims { get; }
|
||||||
public bool IsAuthorised { get; private set; }
|
public bool IsAuthenticated { get; }
|
||||||
public AuthenticationOptions AuthenticationOptions { get; private set; }
|
public bool IsAuthorised { get; }
|
||||||
public PathTemplate DownstreamPathTemplate { get; private set; }
|
public AuthenticationOptions AuthenticationOptions { get; }
|
||||||
public string ReRouteKey { get; private set; }
|
public PathTemplate DownstreamPathTemplate { get; }
|
||||||
public List<string> DelegatingHandlers {get;private set;}
|
public string LoadBalancerKey { get; }
|
||||||
public List<AddHeader> AddHeadersToDownstream {get;private set;}
|
public List<string> DelegatingHandlers { get; }
|
||||||
public List<AddHeader> AddHeadersToUpstream { get; private set; }
|
public List<AddHeader> AddHeadersToDownstream { get; }
|
||||||
public bool DangerousAcceptAnyServerCertificateValidator { get; private set; }
|
public List<AddHeader> AddHeadersToUpstream { get; }
|
||||||
|
public bool DangerousAcceptAnyServerCertificateValidator { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
src/Ocelot/Configuration/File/FileLoadBalancerOptions.cs
Normal file
9
src/Ocelot/Configuration/File/FileLoadBalancerOptions.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Ocelot.Configuration.File
|
||||||
|
{
|
||||||
|
public class FileLoadBalancerOptions
|
||||||
|
{
|
||||||
|
public string Type { get; set; }
|
||||||
|
public string Key { get; set; }
|
||||||
|
public int Expiry { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,54 +1,55 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.File
|
namespace Ocelot.Configuration.File
|
||||||
{
|
{
|
||||||
public class FileReRoute : IReRoute
|
public class FileReRoute : IReRoute
|
||||||
{
|
{
|
||||||
public FileReRoute()
|
public FileReRoute()
|
||||||
{
|
{
|
||||||
UpstreamHttpMethod = new List<string>();
|
UpstreamHttpMethod = new List<string>();
|
||||||
AddHeadersToRequest = new Dictionary<string, string>();
|
AddHeadersToRequest = new Dictionary<string, string>();
|
||||||
AddClaimsToRequest = new Dictionary<string, string>();
|
AddClaimsToRequest = new Dictionary<string, string>();
|
||||||
RouteClaimsRequirement = new Dictionary<string, string>();
|
RouteClaimsRequirement = new Dictionary<string, string>();
|
||||||
AddQueriesToRequest = new Dictionary<string, string>();
|
AddQueriesToRequest = new Dictionary<string, string>();
|
||||||
DownstreamHeaderTransform = new Dictionary<string, string>();
|
DownstreamHeaderTransform = new Dictionary<string, string>();
|
||||||
FileCacheOptions = new FileCacheOptions();
|
FileCacheOptions = new FileCacheOptions();
|
||||||
QoSOptions = new FileQoSOptions();
|
QoSOptions = new FileQoSOptions();
|
||||||
RateLimitOptions = new FileRateLimitRule();
|
RateLimitOptions = new FileRateLimitRule();
|
||||||
AuthenticationOptions = new FileAuthenticationOptions();
|
AuthenticationOptions = new FileAuthenticationOptions();
|
||||||
HttpHandlerOptions = new FileHttpHandlerOptions();
|
HttpHandlerOptions = new FileHttpHandlerOptions();
|
||||||
UpstreamHeaderTransform = new Dictionary<string, string>();
|
UpstreamHeaderTransform = new Dictionary<string, string>();
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>();
|
DownstreamHostAndPorts = new List<FileHostAndPort>();
|
||||||
DelegatingHandlers = new List<string>();
|
DelegatingHandlers = new List<string>();
|
||||||
Priority = 1;
|
LoadBalancerOptions = new FileLoadBalancerOptions();
|
||||||
}
|
Priority = 1;
|
||||||
|
}
|
||||||
public string DownstreamPathTemplate { get; set; }
|
|
||||||
public string UpstreamPathTemplate { get; set; }
|
public string DownstreamPathTemplate { get; set; }
|
||||||
public List<string> UpstreamHttpMethod { get; set; }
|
public string UpstreamPathTemplate { get; set; }
|
||||||
public Dictionary<string, string> AddHeadersToRequest { get; set; }
|
public List<string> UpstreamHttpMethod { get; set; }
|
||||||
public Dictionary<string, string> UpstreamHeaderTransform { get; set; }
|
public Dictionary<string, string> AddHeadersToRequest { get; set; }
|
||||||
public Dictionary<string, string> DownstreamHeaderTransform { get; set; }
|
public Dictionary<string, string> UpstreamHeaderTransform { get; set; }
|
||||||
public Dictionary<string, string> AddClaimsToRequest { get; set; }
|
public Dictionary<string, string> DownstreamHeaderTransform { get; set; }
|
||||||
public Dictionary<string, string> RouteClaimsRequirement { get; set; }
|
public Dictionary<string, string> AddClaimsToRequest { get; set; }
|
||||||
public Dictionary<string, string> AddQueriesToRequest { get; set; }
|
public Dictionary<string, string> RouteClaimsRequirement { get; set; }
|
||||||
public string RequestIdKey { get; set; }
|
public Dictionary<string, string> AddQueriesToRequest { get; set; }
|
||||||
public FileCacheOptions FileCacheOptions { get; set; }
|
public string RequestIdKey { get; set; }
|
||||||
public bool ReRouteIsCaseSensitive { get; set; }
|
public FileCacheOptions FileCacheOptions { get; set; }
|
||||||
public string ServiceName { get; set; }
|
public bool ReRouteIsCaseSensitive { get; set; }
|
||||||
public string DownstreamScheme {get;set;}
|
public string ServiceName { get; set; }
|
||||||
public FileQoSOptions QoSOptions { get; set; }
|
public string DownstreamScheme {get;set;}
|
||||||
public string LoadBalancer { get;set; }
|
public FileQoSOptions QoSOptions { get; set; }
|
||||||
public FileRateLimitRule RateLimitOptions { get; set; }
|
public FileLoadBalancerOptions LoadBalancerOptions { get; set; }
|
||||||
public FileAuthenticationOptions AuthenticationOptions { get; set; }
|
public FileRateLimitRule RateLimitOptions { get; set; }
|
||||||
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
|
public FileAuthenticationOptions AuthenticationOptions { get; set; }
|
||||||
public bool UseServiceDiscovery { get;set; }
|
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
|
||||||
public List<FileHostAndPort> DownstreamHostAndPorts {get;set;}
|
public bool UseServiceDiscovery { get;set; }
|
||||||
public string UpstreamHost { get; set; }
|
public List<FileHostAndPort> DownstreamHostAndPorts {get;set;}
|
||||||
public string Key { get;set; }
|
public string UpstreamHost { get; set; }
|
||||||
public List<string> DelegatingHandlers {get;set;}
|
public string Key { get;set; }
|
||||||
public int Priority { get;set; }
|
public List<string> DelegatingHandlers {get;set;}
|
||||||
public int Timeout { get; set; }
|
public int Priority { get;set; }
|
||||||
public bool DangerousAcceptAnyServerCertificateValidator { get; set; }
|
public int Timeout { get; set; }
|
||||||
}
|
public bool DangerousAcceptAnyServerCertificateValidator { get; set; }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
18
src/Ocelot/Configuration/LoadBalancerOptions.cs
Normal file
18
src/Ocelot/Configuration/LoadBalancerOptions.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace Ocelot.Configuration
|
||||||
|
{
|
||||||
|
public class LoadBalancerOptions
|
||||||
|
{
|
||||||
|
public LoadBalancerOptions(string type, string key, int expiryInMs)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Key = key;
|
||||||
|
ExpiryInMs = expiryInMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Type { get; }
|
||||||
|
|
||||||
|
public string Key { get; }
|
||||||
|
|
||||||
|
public int ExpiryInMs { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Responses;
|
||||||
|
using Values;
|
||||||
|
|
||||||
|
public class CookieStickySessions : ILoadBalancer, IDisposable
|
||||||
|
{
|
||||||
|
private readonly int _expiryInMs;
|
||||||
|
private readonly string _key;
|
||||||
|
private readonly ILoadBalancer _loadBalancer;
|
||||||
|
private readonly ConcurrentDictionary<string, StickySession> _stored;
|
||||||
|
private readonly Timer _timer;
|
||||||
|
private bool _expiring;
|
||||||
|
|
||||||
|
public CookieStickySessions(ILoadBalancer loadBalancer, string key, int expiryInMs)
|
||||||
|
{
|
||||||
|
_key = key;
|
||||||
|
_expiryInMs = expiryInMs;
|
||||||
|
_loadBalancer = loadBalancer;
|
||||||
|
_stored = new ConcurrentDictionary<string, StickySession>();
|
||||||
|
_timer = new Timer(x =>
|
||||||
|
{
|
||||||
|
if (_expiring)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_expiring = true;
|
||||||
|
|
||||||
|
Expire();
|
||||||
|
|
||||||
|
_expiring = false;
|
||||||
|
}, null, 0, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_timer?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context)
|
||||||
|
{
|
||||||
|
var value = context.HttpContext.Request.Cookies[_key];
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(value) && _stored.ContainsKey(value))
|
||||||
|
{
|
||||||
|
var cached = _stored[value];
|
||||||
|
|
||||||
|
var updated = new StickySession(cached.HostAndPort, DateTime.UtcNow.AddMilliseconds(_expiryInMs));
|
||||||
|
|
||||||
|
_stored[value] = updated;
|
||||||
|
|
||||||
|
return new OkResponse<ServiceHostAndPort>(updated.HostAndPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
var next = await _loadBalancer.Lease(context);
|
||||||
|
|
||||||
|
if (next.IsError)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<ServiceHostAndPort>(next.Errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(value) && !_stored.ContainsKey(value))
|
||||||
|
{
|
||||||
|
_stored[value] = new StickySession(next.Data, DateTime.UtcNow.AddMilliseconds(_expiryInMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse<ServiceHostAndPort>(next.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Release(ServiceHostAndPort hostAndPort)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Expire()
|
||||||
|
{
|
||||||
|
var expired = _stored.Where(x => x.Value.Expiry < DateTime.UtcNow);
|
||||||
|
|
||||||
|
foreach (var expire in expired)
|
||||||
|
{
|
||||||
|
_stored.Remove(expire.Key, out _);
|
||||||
|
_loadBalancer.Release(expire.Value.HostAndPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,13 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Values;
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
|
||||||
{
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
public interface ILoadBalancer
|
{
|
||||||
{
|
public interface ILoadBalancer
|
||||||
Task<Response<ServiceHostAndPort>> Lease();
|
{
|
||||||
void Release(ServiceHostAndPort hostAndPort);
|
Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context);
|
||||||
}
|
void Release(ServiceHostAndPort hostAndPort);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
|
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
{
|
{
|
||||||
public interface ILoadBalancerFactory
|
public interface ILoadBalancerFactory
|
||||||
{
|
{
|
||||||
Task<ILoadBalancer> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config);
|
Task<ILoadBalancer> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
{
|
{
|
||||||
public interface ILoadBalancerHouse
|
public interface ILoadBalancerHouse
|
||||||
{
|
{
|
||||||
Task<Response<ILoadBalancer>> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config);
|
Task<Response<ILoadBalancer>> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,145 +1,146 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Values;
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
|
||||||
{
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
public class LeastConnection : ILoadBalancer
|
{
|
||||||
{
|
public class LeastConnection : ILoadBalancer
|
||||||
private readonly Func<Task<List<Service>>> _services;
|
{
|
||||||
private readonly List<Lease> _leases;
|
private readonly Func<Task<List<Service>>> _services;
|
||||||
private readonly string _serviceName;
|
private readonly List<Lease> _leases;
|
||||||
private static readonly object _syncLock = new object();
|
private readonly string _serviceName;
|
||||||
|
private static readonly object _syncLock = new object();
|
||||||
public LeastConnection(Func<Task<List<Service>>> services, string serviceName)
|
|
||||||
{
|
public LeastConnection(Func<Task<List<Service>>> services, string serviceName)
|
||||||
_services = services;
|
{
|
||||||
_serviceName = serviceName;
|
_services = services;
|
||||||
_leases = new List<Lease>();
|
_serviceName = serviceName;
|
||||||
}
|
_leases = new List<Lease>();
|
||||||
|
}
|
||||||
public async Task<Response<ServiceHostAndPort>> Lease()
|
|
||||||
{
|
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
|
||||||
var services = await _services.Invoke();
|
{
|
||||||
|
var services = await _services.Invoke();
|
||||||
if (services == null)
|
|
||||||
{
|
if (services == null)
|
||||||
return new ErrorResponse<ServiceHostAndPort>(new ServicesAreNullError($"services were null for {_serviceName}") );
|
{
|
||||||
}
|
return new ErrorResponse<ServiceHostAndPort>(new ServicesAreNullError($"services were null for {_serviceName}") );
|
||||||
|
}
|
||||||
if (!services.Any())
|
|
||||||
{
|
if (!services.Any())
|
||||||
return new ErrorResponse<ServiceHostAndPort>(new ServicesAreEmptyError($"services were empty for {_serviceName}"));
|
{
|
||||||
}
|
return new ErrorResponse<ServiceHostAndPort>(new ServicesAreEmptyError($"services were empty for {_serviceName}"));
|
||||||
|
}
|
||||||
lock(_syncLock)
|
|
||||||
{
|
lock(_syncLock)
|
||||||
//todo - maybe this should be moved somewhere else...? Maybe on a repeater on seperate thread? loop every second and update or something?
|
{
|
||||||
UpdateServices(services);
|
//todo - maybe this should be moved somewhere else...? Maybe on a repeater on seperate thread? loop every second and update or something?
|
||||||
|
UpdateServices(services);
|
||||||
var leaseWithLeastConnections = GetLeaseWithLeastConnections();
|
|
||||||
|
var leaseWithLeastConnections = GetLeaseWithLeastConnections();
|
||||||
_leases.Remove(leaseWithLeastConnections);
|
|
||||||
|
_leases.Remove(leaseWithLeastConnections);
|
||||||
leaseWithLeastConnections = AddConnection(leaseWithLeastConnections);
|
|
||||||
|
leaseWithLeastConnections = AddConnection(leaseWithLeastConnections);
|
||||||
_leases.Add(leaseWithLeastConnections);
|
|
||||||
|
_leases.Add(leaseWithLeastConnections);
|
||||||
return new OkResponse<ServiceHostAndPort>(new ServiceHostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort));
|
|
||||||
}
|
return new OkResponse<ServiceHostAndPort>(new ServiceHostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public void Release(ServiceHostAndPort hostAndPort)
|
|
||||||
{
|
public void Release(ServiceHostAndPort hostAndPort)
|
||||||
lock(_syncLock)
|
{
|
||||||
{
|
lock(_syncLock)
|
||||||
var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost
|
{
|
||||||
&& l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort);
|
var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost
|
||||||
|
&& l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort);
|
||||||
if (matchingLease != null)
|
|
||||||
{
|
if (matchingLease != null)
|
||||||
var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1);
|
{
|
||||||
|
var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1);
|
||||||
_leases.Remove(matchingLease);
|
|
||||||
|
_leases.Remove(matchingLease);
|
||||||
_leases.Add(replacementLease);
|
|
||||||
}
|
_leases.Add(replacementLease);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private Lease AddConnection(Lease lease)
|
|
||||||
{
|
private Lease AddConnection(Lease lease)
|
||||||
return new Lease(lease.HostAndPort, lease.Connections + 1);
|
{
|
||||||
}
|
return new Lease(lease.HostAndPort, lease.Connections + 1);
|
||||||
|
}
|
||||||
private Lease GetLeaseWithLeastConnections()
|
|
||||||
{
|
private Lease GetLeaseWithLeastConnections()
|
||||||
//now get the service with the least connections?
|
{
|
||||||
Lease leaseWithLeastConnections = null;
|
//now get the service with the least connections?
|
||||||
|
Lease leaseWithLeastConnections = null;
|
||||||
for (var i = 0; i < _leases.Count; i++)
|
|
||||||
{
|
for (var i = 0; i < _leases.Count; i++)
|
||||||
if (i == 0)
|
{
|
||||||
{
|
if (i == 0)
|
||||||
leaseWithLeastConnections = _leases[i];
|
{
|
||||||
}
|
leaseWithLeastConnections = _leases[i];
|
||||||
else
|
}
|
||||||
{
|
else
|
||||||
if (_leases[i].Connections < leaseWithLeastConnections.Connections)
|
{
|
||||||
{
|
if (_leases[i].Connections < leaseWithLeastConnections.Connections)
|
||||||
leaseWithLeastConnections = _leases[i];
|
{
|
||||||
}
|
leaseWithLeastConnections = _leases[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return leaseWithLeastConnections;
|
|
||||||
}
|
return leaseWithLeastConnections;
|
||||||
|
}
|
||||||
private Response UpdateServices(List<Service> services)
|
|
||||||
{
|
private Response UpdateServices(List<Service> services)
|
||||||
if (_leases.Count > 0)
|
{
|
||||||
{
|
if (_leases.Count > 0)
|
||||||
var leasesToRemove = new List<Lease>();
|
{
|
||||||
|
var leasesToRemove = new List<Lease>();
|
||||||
foreach (var lease in _leases)
|
|
||||||
{
|
foreach (var lease in _leases)
|
||||||
var match = services.FirstOrDefault(s => s.HostAndPort.DownstreamHost == lease.HostAndPort.DownstreamHost
|
{
|
||||||
&& s.HostAndPort.DownstreamPort == lease.HostAndPort.DownstreamPort);
|
var match = services.FirstOrDefault(s => s.HostAndPort.DownstreamHost == lease.HostAndPort.DownstreamHost
|
||||||
|
&& s.HostAndPort.DownstreamPort == lease.HostAndPort.DownstreamPort);
|
||||||
if (match == null)
|
|
||||||
{
|
if (match == null)
|
||||||
leasesToRemove.Add(lease);
|
{
|
||||||
}
|
leasesToRemove.Add(lease);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
foreach (var lease in leasesToRemove)
|
|
||||||
{
|
foreach (var lease in leasesToRemove)
|
||||||
_leases.Remove(lease);
|
{
|
||||||
}
|
_leases.Remove(lease);
|
||||||
|
}
|
||||||
foreach (var service in services)
|
|
||||||
{
|
foreach (var service in services)
|
||||||
var exists = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == service.HostAndPort.DownstreamHost && l.HostAndPort.DownstreamPort == service.HostAndPort.DownstreamPort);
|
{
|
||||||
|
var exists = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == service.HostAndPort.DownstreamHost && l.HostAndPort.DownstreamPort == service.HostAndPort.DownstreamPort);
|
||||||
if (exists == null)
|
|
||||||
{
|
if (exists == null)
|
||||||
_leases.Add(new Lease(service.HostAndPort, 0));
|
{
|
||||||
}
|
_leases.Add(new Lease(service.HostAndPort, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
{
|
else
|
||||||
foreach (var service in services)
|
{
|
||||||
{
|
foreach (var service in services)
|
||||||
_leases.Add(new Lease(service.HostAndPort, 0));
|
{
|
||||||
}
|
_leases.Add(new Lease(service.HostAndPort, 0));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return new OkResponse();
|
|
||||||
}
|
return new OkResponse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,30 +1,34 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.ServiceDiscovery;
|
using Ocelot.ServiceDiscovery;
|
||||||
|
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
{
|
{
|
||||||
public class LoadBalancerFactory : ILoadBalancerFactory
|
public class LoadBalancerFactory : ILoadBalancerFactory
|
||||||
{
|
{
|
||||||
private readonly IServiceDiscoveryProviderFactory _serviceProviderFactory;
|
private readonly IServiceDiscoveryProviderFactory _serviceProviderFactory;
|
||||||
public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFactory)
|
|
||||||
{
|
public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFactory)
|
||||||
_serviceProviderFactory = serviceProviderFactory;
|
{
|
||||||
}
|
_serviceProviderFactory = serviceProviderFactory;
|
||||||
|
}
|
||||||
public async Task<ILoadBalancer> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config)
|
|
||||||
{
|
public async Task<ILoadBalancer> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config)
|
||||||
var serviceProvider = _serviceProviderFactory.Get(config, reRoute);
|
{
|
||||||
|
var serviceProvider = _serviceProviderFactory.Get(config, reRoute);
|
||||||
switch (reRoute.LoadBalancer)
|
|
||||||
{
|
switch (reRoute.LoadBalancerOptions?.Type)
|
||||||
case "RoundRobin":
|
{
|
||||||
return new RoundRobin(async () => await serviceProvider.Get());
|
case nameof(RoundRobin):
|
||||||
case "LeastConnection":
|
return new RoundRobin(async () => await serviceProvider.Get());
|
||||||
return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName);
|
case nameof(LeastConnection):
|
||||||
default:
|
return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName);
|
||||||
return new NoLoadBalancer(await serviceProvider.Get());
|
case nameof(CookieStickySessions):
|
||||||
}
|
var loadBalancer = new RoundRobin(async () => await serviceProvider.Get());
|
||||||
}
|
return new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs);
|
||||||
}
|
default:
|
||||||
}
|
return new NoLoadBalancer(await serviceProvider.Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,56 +1,56 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
{
|
{
|
||||||
public class LoadBalancerHouse : ILoadBalancerHouse
|
public class LoadBalancerHouse : ILoadBalancerHouse
|
||||||
{
|
{
|
||||||
private readonly ILoadBalancerFactory _factory;
|
private readonly ILoadBalancerFactory _factory;
|
||||||
private readonly ConcurrentDictionary<string, ILoadBalancer> _loadBalancers;
|
private readonly ConcurrentDictionary<string, ILoadBalancer> _loadBalancers;
|
||||||
|
|
||||||
public LoadBalancerHouse(ILoadBalancerFactory factory)
|
public LoadBalancerHouse(ILoadBalancerFactory factory)
|
||||||
{
|
{
|
||||||
_factory = factory;
|
_factory = factory;
|
||||||
_loadBalancers = new ConcurrentDictionary<string, ILoadBalancer>();
|
_loadBalancers = new ConcurrentDictionary<string, ILoadBalancer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Response<ILoadBalancer>> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config)
|
public async Task<Response<ILoadBalancer>> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if(_loadBalancers.TryGetValue(reRoute.ReRouteKey, out var loadBalancer))
|
if(_loadBalancers.TryGetValue(reRoute.LoadBalancerKey, out var loadBalancer))
|
||||||
{
|
{
|
||||||
loadBalancer = _loadBalancers[reRoute.ReRouteKey];
|
loadBalancer = _loadBalancers[reRoute.LoadBalancerKey];
|
||||||
|
|
||||||
if(reRoute.LoadBalancer != loadBalancer.GetType().Name)
|
if(reRoute.LoadBalancerOptions.Type != loadBalancer.GetType().Name)
|
||||||
{
|
{
|
||||||
loadBalancer = await _factory.Get(reRoute, config);
|
loadBalancer = await _factory.Get(reRoute, config);
|
||||||
AddLoadBalancer(reRoute.ReRouteKey, loadBalancer);
|
AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OkResponse<ILoadBalancer>(loadBalancer);
|
return new OkResponse<ILoadBalancer>(loadBalancer);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadBalancer = await _factory.Get(reRoute, config);
|
loadBalancer = await _factory.Get(reRoute, config);
|
||||||
AddLoadBalancer(reRoute.ReRouteKey, loadBalancer);
|
AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer);
|
||||||
return new OkResponse<ILoadBalancer>(loadBalancer);
|
return new OkResponse<ILoadBalancer>(loadBalancer);
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch(Exception ex)
|
||||||
{
|
{
|
||||||
return new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
|
return new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
|
||||||
{
|
{
|
||||||
new UnableToFindLoadBalancerError($"unabe to find load balancer for {reRoute.ReRouteKey} exception is {ex}")
|
new UnableToFindLoadBalancerError($"unabe to find load balancer for {reRoute.LoadBalancerKey} exception is {ex}")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddLoadBalancer(string key, ILoadBalancer loadBalancer)
|
private void AddLoadBalancer(string key, ILoadBalancer loadBalancer)
|
||||||
{
|
{
|
||||||
_loadBalancers.AddOrUpdate(key, loadBalancer, (x, y) => loadBalancer);
|
_loadBalancers.AddOrUpdate(key, loadBalancer, (x, y) => loadBalancer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,35 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Values;
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
|
||||||
{
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
public class NoLoadBalancer : ILoadBalancer
|
{
|
||||||
{
|
public class NoLoadBalancer : ILoadBalancer
|
||||||
private readonly List<Service> _services;
|
{
|
||||||
|
private readonly List<Service> _services;
|
||||||
public NoLoadBalancer(List<Service> services)
|
|
||||||
{
|
public NoLoadBalancer(List<Service> services)
|
||||||
_services = services;
|
{
|
||||||
}
|
_services = services;
|
||||||
|
}
|
||||||
public async Task<Response<ServiceHostAndPort>> Lease()
|
|
||||||
{
|
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
|
||||||
//todo no point spinning a task up here, also first or default could be null..
|
{
|
||||||
if (_services == null || _services.Count == 0)
|
//todo no point spinning a task up here, also first or default could be null..
|
||||||
{
|
if (_services == null || _services.Count == 0)
|
||||||
return new ErrorResponse<ServiceHostAndPort>(new ServicesAreEmptyError("There were no services in NoLoadBalancer"));
|
{
|
||||||
}
|
return new ErrorResponse<ServiceHostAndPort>(new ServicesAreEmptyError("There were no services in NoLoadBalancer"));
|
||||||
|
}
|
||||||
var service = await Task.FromResult(_services.FirstOrDefault());
|
|
||||||
return new OkResponse<ServiceHostAndPort>(service.HostAndPort);
|
var service = await Task.FromResult(_services.FirstOrDefault());
|
||||||
}
|
return new OkResponse<ServiceHostAndPort>(service.HostAndPort);
|
||||||
|
}
|
||||||
public void Release(ServiceHostAndPort hostAndPort)
|
|
||||||
{
|
public void Release(ServiceHostAndPort hostAndPort)
|
||||||
}
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,37 +1,38 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
using System;
|
using System;
|
||||||
|
using Ocelot.Middleware;
|
||||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
|
||||||
{
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
public class RoundRobin : ILoadBalancer
|
{
|
||||||
{
|
public class RoundRobin : ILoadBalancer
|
||||||
private readonly Func<Task<List<Service>>> _services;
|
{
|
||||||
|
private readonly Func<Task<List<Service>>> _services;
|
||||||
private int _last;
|
|
||||||
|
private int _last;
|
||||||
public RoundRobin(Func<Task<List<Service>>> services)
|
|
||||||
{
|
public RoundRobin(Func<Task<List<Service>>> services)
|
||||||
_services = services;
|
{
|
||||||
}
|
_services = services;
|
||||||
|
}
|
||||||
public async Task<Response<ServiceHostAndPort>> Lease()
|
|
||||||
{
|
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
|
||||||
var services = await _services.Invoke();
|
{
|
||||||
if (_last >= services.Count)
|
var services = await _services.Invoke();
|
||||||
{
|
if (_last >= services.Count)
|
||||||
_last = 0;
|
{
|
||||||
}
|
_last = 0;
|
||||||
|
}
|
||||||
var next = await Task.FromResult(services[_last]);
|
|
||||||
_last++;
|
var next = await Task.FromResult(services[_last]);
|
||||||
return new OkResponse<ServiceHostAndPort>(next.HostAndPort);
|
_last++;
|
||||||
}
|
return new OkResponse<ServiceHostAndPort>(next.HostAndPort);
|
||||||
|
}
|
||||||
public void Release(ServiceHostAndPort hostAndPort)
|
|
||||||
{
|
public void Release(ServiceHostAndPort hostAndPort)
|
||||||
}
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
18
src/Ocelot/LoadBalancer/LoadBalancers/StickySession.cs
Normal file
18
src/Ocelot/LoadBalancer/LoadBalancers/StickySession.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
|
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||||
|
{
|
||||||
|
public class StickySession
|
||||||
|
{
|
||||||
|
public StickySession(ServiceHostAndPort hostAndPort, DateTime expiry)
|
||||||
|
{
|
||||||
|
HostAndPort = hostAndPort;
|
||||||
|
Expiry = expiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceHostAndPort HostAndPort { get; }
|
||||||
|
|
||||||
|
public DateTime Expiry { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,63 +1,63 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.LoadBalancer.LoadBalancers;
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
namespace Ocelot.LoadBalancer.Middleware
|
namespace Ocelot.LoadBalancer.Middleware
|
||||||
{
|
{
|
||||||
public class LoadBalancingMiddleware : OcelotMiddleware
|
public class LoadBalancingMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly OcelotRequestDelegate _next;
|
||||||
private readonly ILoadBalancerHouse _loadBalancerHouse;
|
private readonly ILoadBalancerHouse _loadBalancerHouse;
|
||||||
|
|
||||||
public LoadBalancingMiddleware(OcelotRequestDelegate next,
|
public LoadBalancingMiddleware(OcelotRequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
ILoadBalancerHouse loadBalancerHouse)
|
ILoadBalancerHouse loadBalancerHouse)
|
||||||
:base(loggerFactory.CreateLogger<LoadBalancingMiddleware>())
|
:base(loggerFactory.CreateLogger<LoadBalancingMiddleware>())
|
||||||
{
|
{
|
||||||
_next = next;
|
_next = next;
|
||||||
_loadBalancerHouse = loadBalancerHouse;
|
_loadBalancerHouse = loadBalancerHouse;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Invoke(DownstreamContext context)
|
public async Task Invoke(DownstreamContext context)
|
||||||
{
|
{
|
||||||
var loadBalancer = await _loadBalancerHouse.Get(context.DownstreamReRoute, context.ServiceProviderConfiguration);
|
var loadBalancer = await _loadBalancerHouse.Get(context.DownstreamReRoute, context.ServiceProviderConfiguration);
|
||||||
if(loadBalancer.IsError)
|
if(loadBalancer.IsError)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error");
|
Logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error");
|
||||||
SetPipelineError(context, loadBalancer.Errors);
|
SetPipelineError(context, loadBalancer.Errors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hostAndPort = await loadBalancer.Data.Lease();
|
var hostAndPort = await loadBalancer.Data.Lease(context);
|
||||||
if(hostAndPort.IsError)
|
if(hostAndPort.IsError)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error");
|
Logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error");
|
||||||
SetPipelineError(context, hostAndPort.Errors);
|
SetPipelineError(context, hostAndPort.Errors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.DownstreamRequest.Host = hostAndPort.Data.DownstreamHost;
|
context.DownstreamRequest.Host = hostAndPort.Data.DownstreamHost;
|
||||||
|
|
||||||
if (hostAndPort.Data.DownstreamPort > 0)
|
if (hostAndPort.Data.DownstreamPort > 0)
|
||||||
{
|
{
|
||||||
context.DownstreamRequest.Port = hostAndPort.Data.DownstreamPort;
|
context.DownstreamRequest.Port = hostAndPort.Data.DownstreamPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(context);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler");
|
Logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
loadBalancer.Data.Release(hostAndPort.Data);
|
loadBalancer.Data.Release(hostAndPort.Data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,26 +21,26 @@ namespace Ocelot.Requester.QoS
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_qoSProviders.TryGetValue(reRoute.ReRouteKey, out var qosProvider))
|
if (_qoSProviders.TryGetValue(reRoute.QosKey, out var qosProvider))
|
||||||
{
|
{
|
||||||
if (reRoute.IsQos && qosProvider.CircuitBreaker == null)
|
if (reRoute.IsQos && qosProvider.CircuitBreaker == null)
|
||||||
{
|
{
|
||||||
qosProvider = _qoSProviderFactory.Get(reRoute);
|
qosProvider = _qoSProviderFactory.Get(reRoute);
|
||||||
Add(reRoute.ReRouteKey, qosProvider);
|
Add(reRoute.QosKey, qosProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OkResponse<IQoSProvider>(_qoSProviders[reRoute.ReRouteKey]);
|
return new OkResponse<IQoSProvider>(_qoSProviders[reRoute.QosKey]);
|
||||||
}
|
}
|
||||||
|
|
||||||
qosProvider = _qoSProviderFactory.Get(reRoute);
|
qosProvider = _qoSProviderFactory.Get(reRoute);
|
||||||
Add(reRoute.ReRouteKey, qosProvider);
|
Add(reRoute.QosKey, qosProvider);
|
||||||
return new OkResponse<IQoSProvider>(qosProvider);
|
return new OkResponse<IQoSProvider>(qosProvider);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new ErrorResponse<IQoSProvider>(new List<Ocelot.Errors.Error>()
|
return new ErrorResponse<IQoSProvider>(new List<Ocelot.Errors.Error>()
|
||||||
{
|
{
|
||||||
new UnableToFindQoSProviderError($"unabe to find qos provider for {reRoute.ReRouteKey}, exception was {ex}")
|
new UnableToFindQoSProviderError($"unabe to find qos provider for {reRoute.QosKey}, exception was {ex}")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,163 +1,163 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
namespace Ocelot.AcceptanceTests
|
||||||
{
|
{
|
||||||
public class LoadBalancerTests : IDisposable
|
public class LoadBalancerTests : IDisposable
|
||||||
{
|
{
|
||||||
private IWebHost _builderOne;
|
private IWebHost _builderOne;
|
||||||
private IWebHost _builderTwo;
|
private IWebHost _builderTwo;
|
||||||
private readonly Steps _steps;
|
private readonly Steps _steps;
|
||||||
private int _counterOne;
|
private int _counterOne;
|
||||||
private int _counterTwo;
|
private int _counterTwo;
|
||||||
private static readonly object _syncLock = new object();
|
private static readonly object _syncLock = new object();
|
||||||
|
|
||||||
public LoadBalancerTests()
|
public LoadBalancerTests()
|
||||||
{
|
{
|
||||||
_steps = new Steps();
|
_steps = new Steps();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_load_balance_request()
|
public void should_load_balance_request()
|
||||||
{
|
{
|
||||||
var downstreamServiceOneUrl = "http://localhost:50881";
|
var downstreamServiceOneUrl = "http://localhost:50881";
|
||||||
var downstreamServiceTwoUrl = "http://localhost:50892";
|
var downstreamServiceTwoUrl = "http://localhost:50892";
|
||||||
|
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/",
|
DownstreamPathTemplate = "/",
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
LoadBalancer = "LeastConnection",
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 50881
|
Port = 50881
|
||||||
},
|
},
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 50892
|
Port = 50892
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
|
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
|
||||||
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
|
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50))
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50))
|
||||||
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50))
|
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50))
|
||||||
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26))
|
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top)
|
private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top)
|
||||||
{
|
{
|
||||||
_counterOne.ShouldBeInRange(bottom, top);
|
_counterOne.ShouldBeInRange(bottom, top);
|
||||||
_counterOne.ShouldBeInRange(bottom, top);
|
_counterOne.ShouldBeInRange(bottom, top);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected)
|
private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected)
|
||||||
{
|
{
|
||||||
var total = _counterOne + _counterTwo;
|
var total = _counterOne + _counterTwo;
|
||||||
total.ShouldBe(expected);
|
total.ShouldBe(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenProductServiceOneIsRunning(string url, int statusCode)
|
private void GivenProductServiceOneIsRunning(string url, int statusCode)
|
||||||
{
|
{
|
||||||
_builderOne = new WebHostBuilder()
|
_builderOne = new WebHostBuilder()
|
||||||
.UseUrls(url)
|
.UseUrls(url)
|
||||||
.UseKestrel()
|
.UseKestrel()
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
.UseIISIntegration()
|
.UseIISIntegration()
|
||||||
.UseUrls(url)
|
.UseUrls(url)
|
||||||
.Configure(app =>
|
.Configure(app =>
|
||||||
{
|
{
|
||||||
app.Run(async context =>
|
app.Run(async context =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = string.Empty;
|
var response = string.Empty;
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
_counterOne++;
|
_counterOne++;
|
||||||
response = _counterOne.ToString();
|
response = _counterOne.ToString();
|
||||||
}
|
}
|
||||||
context.Response.StatusCode = statusCode;
|
context.Response.StatusCode = statusCode;
|
||||||
await context.Response.WriteAsync(response);
|
await context.Response.WriteAsync(response);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
await context.Response.WriteAsync(exception.StackTrace);
|
await context.Response.WriteAsync(exception.StackTrace);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
_builderOne.Start();
|
_builderOne.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenProductServiceTwoIsRunning(string url, int statusCode)
|
private void GivenProductServiceTwoIsRunning(string url, int statusCode)
|
||||||
{
|
{
|
||||||
_builderTwo = new WebHostBuilder()
|
_builderTwo = new WebHostBuilder()
|
||||||
.UseUrls(url)
|
.UseUrls(url)
|
||||||
.UseKestrel()
|
.UseKestrel()
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
.UseIISIntegration()
|
.UseIISIntegration()
|
||||||
.UseUrls(url)
|
.UseUrls(url)
|
||||||
.Configure(app =>
|
.Configure(app =>
|
||||||
{
|
{
|
||||||
app.Run(async context =>
|
app.Run(async context =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = string.Empty;
|
var response = string.Empty;
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
_counterTwo++;
|
_counterTwo++;
|
||||||
response = _counterTwo.ToString();
|
response = _counterTwo.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Response.StatusCode = statusCode;
|
context.Response.StatusCode = statusCode;
|
||||||
await context.Response.WriteAsync(response);
|
await context.Response.WriteAsync(response);
|
||||||
}
|
}
|
||||||
catch (System.Exception exception)
|
catch (System.Exception exception)
|
||||||
{
|
{
|
||||||
await context.Response.WriteAsync(exception.StackTrace);
|
await context.Response.WriteAsync(exception.StackTrace);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
_builderTwo.Start();
|
_builderTwo.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_builderOne?.Dispose();
|
_builderOne?.Dispose();
|
||||||
_builderTwo?.Dispose();
|
_builderTwo?.Dispose();
|
||||||
_steps.Dispose();
|
_steps.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -61,7 +61,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
ServiceName = serviceName,
|
ServiceName = serviceName,
|
||||||
LoadBalancer = "LeastConnection",
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
||||||
UseServiceDiscovery = true,
|
UseServiceDiscovery = true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -127,7 +127,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
ServiceName = serviceName,
|
ServiceName = serviceName,
|
||||||
LoadBalancer = "LeastConnection",
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
||||||
UseServiceDiscovery = true,
|
UseServiceDiscovery = true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -183,7 +183,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/home",
|
UpstreamPathTemplate = "/home",
|
||||||
UpstreamHttpMethod = new List<string> { "Get", "Options" },
|
UpstreamHttpMethod = new List<string> { "Get", "Options" },
|
||||||
ServiceName = serviceName,
|
ServiceName = serviceName,
|
||||||
LoadBalancer = "LeastConnection",
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
||||||
UseServiceDiscovery = true,
|
UseServiceDiscovery = true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -239,7 +239,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/home",
|
UpstreamPathTemplate = "/home",
|
||||||
UpstreamHttpMethod = new List<string> { "Get", "Options" },
|
UpstreamHttpMethod = new List<string> { "Get", "Options" },
|
||||||
ServiceName = serviceName,
|
ServiceName = serviceName,
|
||||||
LoadBalancer = "LeastConnection",
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
||||||
UseServiceDiscovery = true,
|
UseServiceDiscovery = true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -308,7 +308,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
ServiceName = serviceName,
|
ServiceName = serviceName,
|
||||||
LoadBalancer = "LeastConnection",
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
||||||
UseServiceDiscovery = true,
|
UseServiceDiscovery = true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -30,6 +30,9 @@ using Ocelot.Middleware.Multiplexer;
|
|||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
namespace Ocelot.AcceptanceTests
|
||||||
{
|
{
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue;
|
||||||
|
|
||||||
public class Steps : IDisposable
|
public class Steps : IDisposable
|
||||||
{
|
{
|
||||||
private TestServer _ocelotServer;
|
private TestServer _ocelotServer;
|
||||||
@ -341,7 +344,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
|
|
||||||
internal void GivenIAddCookieToMyRequest(string cookie)
|
internal void GivenIAddCookieToMyRequest(string cookie)
|
||||||
{
|
{
|
||||||
_ocelotClient.DefaultRequestHeaders.Add("Set-Cookie", cookie);
|
_ocelotClient.DefaultRequestHeaders.Add("Set-Cookie", cookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -671,6 +674,14 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_response = _ocelotClient.GetAsync(url).Result;
|
_response = _ocelotClient.GetAsync(url).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WhenIGetUrlOnTheApiGateway(string url, string cookie, string value)
|
||||||
|
{
|
||||||
|
var request = _ocelotServer.CreateRequest(url);
|
||||||
|
request.And(x => { x.Headers.Add("Cookie", new CookieHeaderValue(cookie, value).ToString()); });
|
||||||
|
var response = request.GetAsync().Result;
|
||||||
|
_response = response;
|
||||||
|
}
|
||||||
|
|
||||||
public void GivenIAddAHeader(string key, string value)
|
public void GivenIAddAHeader(string key, string value)
|
||||||
{
|
{
|
||||||
_ocelotClient.DefaultRequestHeaders.Add(key, value);
|
_ocelotClient.DefaultRequestHeaders.Add(key, value);
|
||||||
@ -690,6 +701,30 @@ namespace Ocelot.AcceptanceTests
|
|||||||
Task.WaitAll(tasks);
|
Task.WaitAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times, string cookie, string value)
|
||||||
|
{
|
||||||
|
var tasks = new Task[times];
|
||||||
|
|
||||||
|
for (int i = 0; i < times; i++)
|
||||||
|
{
|
||||||
|
var urlCopy = url;
|
||||||
|
tasks[i] = GetForServiceDiscoveryTest(urlCopy, cookie, value);
|
||||||
|
Thread.Sleep(_random.Next(40, 60));
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.WaitAll(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetForServiceDiscoveryTest(string url, string cookie, string value)
|
||||||
|
{
|
||||||
|
var request = _ocelotServer.CreateRequest(url);
|
||||||
|
request.And(x => { x.Headers.Add("Cookie", new CookieHeaderValue(cookie, value).ToString()); });
|
||||||
|
var response = await request.GetAsync();
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
int count = int.Parse(content);
|
||||||
|
count.ShouldBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task GetForServiceDiscoveryTest(string url)
|
private async Task GetForServiceDiscoveryTest(string url)
|
||||||
{
|
{
|
||||||
var response = await _ocelotClient.GetAsync(url);
|
var response = await _ocelotClient.GetAsync(url);
|
||||||
|
321
test/Ocelot.AcceptanceTests/StickySessionsTests.cs
Normal file
321
test/Ocelot.AcceptanceTests/StickySessionsTests.cs
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.AcceptanceTests
|
||||||
|
{
|
||||||
|
public class StickySessionsTests : IDisposable
|
||||||
|
{
|
||||||
|
private IWebHost _builderOne;
|
||||||
|
private IWebHost _builderTwo;
|
||||||
|
private readonly Steps _steps;
|
||||||
|
private int _counterOne;
|
||||||
|
private int _counterTwo;
|
||||||
|
private static readonly object _syncLock = new object();
|
||||||
|
|
||||||
|
public StickySessionsTests()
|
||||||
|
{
|
||||||
|
_steps = new Steps();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_use_same_downstream_host()
|
||||||
|
{
|
||||||
|
var downstreamPortOne = 51881;
|
||||||
|
var downstreamPortTwo = 51892;
|
||||||
|
var downstreamServiceOneUrl = $"http://localhost:{downstreamPortOne}";
|
||||||
|
var downstreamServiceTwoUrl = $"http://localhost:{downstreamPortTwo}";
|
||||||
|
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
UpstreamPathTemplate = "/",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
LoadBalancerOptions = new FileLoadBalancerOptions
|
||||||
|
{
|
||||||
|
Type = "CookieStickySessions",
|
||||||
|
Key = "sessionid",
|
||||||
|
Expiry = 300000
|
||||||
|
},
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortOne
|
||||||
|
},
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortTwo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
|
||||||
|
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10, "sessionid", "123"))
|
||||||
|
.Then(x => x.ThenTheFirstServiceIsCalled(10))
|
||||||
|
.Then(x => x.ThenTheSecondServiceIsCalled(0))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_use_different_downstream_host_for_different_re_route()
|
||||||
|
{
|
||||||
|
var downstreamPortOne = 52881;
|
||||||
|
var downstreamPortTwo = 52892;
|
||||||
|
var downstreamServiceOneUrl = $"http://localhost:{downstreamPortOne}";
|
||||||
|
var downstreamServiceTwoUrl = $"http://localhost:{downstreamPortTwo}";
|
||||||
|
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
UpstreamPathTemplate = "/",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
LoadBalancerOptions = new FileLoadBalancerOptions
|
||||||
|
{
|
||||||
|
Type = "CookieStickySessions",
|
||||||
|
Key = "sessionid",
|
||||||
|
Expiry = 300000
|
||||||
|
},
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortOne
|
||||||
|
},
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortTwo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
UpstreamPathTemplate = "/test",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
LoadBalancerOptions = new FileLoadBalancerOptions
|
||||||
|
{
|
||||||
|
Type = "CookieStickySessions",
|
||||||
|
Key = "bestid",
|
||||||
|
Expiry = 300000
|
||||||
|
},
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortTwo
|
||||||
|
},
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortOne
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
|
||||||
|
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/", "sessionid", "123"))
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/test", "bestid", "123"))
|
||||||
|
.Then(x => x.ThenTheFirstServiceIsCalled(1))
|
||||||
|
.Then(x => x.ThenTheSecondServiceIsCalled(1))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_use_same_downstream_host_for_different_re_route()
|
||||||
|
{
|
||||||
|
var downstreamPortOne = 53881;
|
||||||
|
var downstreamPortTwo = 53892;
|
||||||
|
var downstreamServiceOneUrl = $"http://localhost:{downstreamPortOne}";
|
||||||
|
var downstreamServiceTwoUrl = $"http://localhost:{downstreamPortTwo}";
|
||||||
|
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
UpstreamPathTemplate = "/",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
LoadBalancerOptions = new FileLoadBalancerOptions
|
||||||
|
{
|
||||||
|
Type = "CookieStickySessions",
|
||||||
|
Key = "sessionid",
|
||||||
|
Expiry = 300000
|
||||||
|
},
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortOne
|
||||||
|
},
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortTwo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
UpstreamPathTemplate = "/test",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
LoadBalancerOptions = new FileLoadBalancerOptions
|
||||||
|
{
|
||||||
|
Type = "CookieStickySessions",
|
||||||
|
Key = "sessionid",
|
||||||
|
Expiry = 300000
|
||||||
|
},
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortTwo
|
||||||
|
},
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = downstreamPortOne
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
|
||||||
|
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/", "sessionid", "123"))
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/test", "sessionid", "123"))
|
||||||
|
.Then(x => x.ThenTheFirstServiceIsCalled(2))
|
||||||
|
.Then(x => x.ThenTheSecondServiceIsCalled(0))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheFirstServiceIsCalled(int expected)
|
||||||
|
{
|
||||||
|
_counterOne.ShouldBe(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheSecondServiceIsCalled(int expected)
|
||||||
|
{
|
||||||
|
_counterTwo.ShouldBe(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenProductServiceOneIsRunning(string url, int statusCode)
|
||||||
|
{
|
||||||
|
_builderOne = new WebHostBuilder()
|
||||||
|
.UseUrls(url)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.UseUrls(url)
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = string.Empty;
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
_counterOne++;
|
||||||
|
response = _counterOne.ToString();
|
||||||
|
}
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync(response);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync(exception.StackTrace);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builderOne.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenProductServiceTwoIsRunning(string url, int statusCode)
|
||||||
|
{
|
||||||
|
_builderTwo = new WebHostBuilder()
|
||||||
|
.UseUrls(url)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.UseUrls(url)
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = string.Empty;
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
_counterTwo++;
|
||||||
|
response = _counterTwo.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync(response);
|
||||||
|
}
|
||||||
|
catch (System.Exception exception)
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync(exception.StackTrace);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builderTwo.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_builderOne?.Dispose();
|
||||||
|
_builderTwo?.Dispose();
|
||||||
|
_steps.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -101,7 +101,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
Port = secondDownstreamPort
|
Port = secondDownstreamPort
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
LoadBalancer = "RoundRobin"
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "RoundRobin" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -159,7 +159,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
DownstreamPathTemplate = "/ws",
|
DownstreamPathTemplate = "/ws",
|
||||||
DownstreamScheme = "ws",
|
DownstreamScheme = "ws",
|
||||||
LoadBalancer = "RoundRobin",
|
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "RoundRobin" },
|
||||||
ServiceName = serviceName,
|
ServiceName = serviceName,
|
||||||
UseServiceDiscovery = true
|
UseServiceDiscovery = true
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
276
test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsTests.cs
Normal file
276
test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsTests.cs
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
namespace Ocelot.UnitTests.LoadBalancer
|
||||||
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using Moq;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Threading;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.UnitTests.Responder;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
|
||||||
|
public class CookieStickySessionsTests
|
||||||
|
{
|
||||||
|
private readonly CookieStickySessions _stickySessions;
|
||||||
|
private readonly Mock<ILoadBalancer> _loadBalancer;
|
||||||
|
private DownstreamContext _downstreamContext;
|
||||||
|
private Response<ServiceHostAndPort> _result;
|
||||||
|
private Response<ServiceHostAndPort> _firstHostAndPort;
|
||||||
|
private Response<ServiceHostAndPort> _secondHostAndPort;
|
||||||
|
|
||||||
|
public CookieStickySessionsTests()
|
||||||
|
{
|
||||||
|
_loadBalancer = new Mock<ILoadBalancer>();
|
||||||
|
const int defaultExpiryInMs = 100;
|
||||||
|
_stickySessions = new CookieStickySessions(_loadBalancer.Object, "sessionid", defaultExpiryInMs);
|
||||||
|
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_host_and_port()
|
||||||
|
{
|
||||||
|
this.Given(_ => GivenTheLoadBalancerReturns())
|
||||||
|
.When(_ => WhenILease())
|
||||||
|
.Then(_ => ThenTheHostAndPortIsNotNull())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_same_host_and_port()
|
||||||
|
{
|
||||||
|
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
|
||||||
|
.And(_ => GivenTheDownstreamRequestHasSessionId("321"))
|
||||||
|
.When(_ => WhenILeaseTwiceInARow())
|
||||||
|
.Then(_ => ThenTheFirstAndSecondResponseAreTheSame())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_different_host_and_port_if_load_balancer_does()
|
||||||
|
{
|
||||||
|
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
|
||||||
|
.When(_ => WhenIMakeTwoRequetsWithDifferentSessionValues())
|
||||||
|
.Then(_ => ThenADifferentHostAndPortIsReturned())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_error()
|
||||||
|
{
|
||||||
|
this.Given(_ => GivenTheLoadBalancerReturnsError())
|
||||||
|
.When(_ => WhenILease())
|
||||||
|
.Then(_ => ThenAnErrorIsReturned())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_expire_sticky_session()
|
||||||
|
{
|
||||||
|
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
|
||||||
|
.When(_ => WhenTheStickySessionExpires())
|
||||||
|
.Then(_ => ThenANewHostAndPortIsReturned())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_refresh_sticky_session()
|
||||||
|
{
|
||||||
|
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
|
||||||
|
.When(_ => WhenIMakeRequestsToKeepRefreshingTheSession())
|
||||||
|
.Then(_ => ThenTheSessionIsRefreshed())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_dispose()
|
||||||
|
{
|
||||||
|
_stickySessions.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_release()
|
||||||
|
{
|
||||||
|
_stickySessions.Release(new ServiceHostAndPort("", 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ThenTheSessionIsRefreshed()
|
||||||
|
{
|
||||||
|
var postExpireHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
postExpireHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||||
|
postExpireHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
|
||||||
|
_loadBalancer
|
||||||
|
.Verify(x => x.Lease(It.IsAny<DownstreamContext>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WhenIMakeRequestsToKeepRefreshingTheSession()
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext();
|
||||||
|
var cookies = new FakeCookies();
|
||||||
|
cookies.AddCookie("sessionid", "321");
|
||||||
|
context.Request.Cookies = cookies;
|
||||||
|
_downstreamContext = new DownstreamContext(context);
|
||||||
|
|
||||||
|
var firstHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
firstHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||||
|
firstHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
|
||||||
|
Thread.Sleep(80);
|
||||||
|
|
||||||
|
var secondHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
secondHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||||
|
secondHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
|
||||||
|
Thread.Sleep(80);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ThenANewHostAndPortIsReturned()
|
||||||
|
{
|
||||||
|
var postExpireHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
postExpireHostAndPort.Data.DownstreamHost.ShouldBe("two");
|
||||||
|
postExpireHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WhenTheStickySessionExpires()
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext();
|
||||||
|
var cookies = new FakeCookies();
|
||||||
|
cookies.AddCookie("sessionid", "321");
|
||||||
|
context.Request.Cookies = cookies;
|
||||||
|
_downstreamContext = new DownstreamContext(context);
|
||||||
|
|
||||||
|
var firstHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
var secondHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
|
||||||
|
firstHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||||
|
firstHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
|
||||||
|
secondHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||||
|
secondHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
|
||||||
|
Thread.Sleep(150);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenAnErrorIsReturned()
|
||||||
|
{
|
||||||
|
_result.IsError.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheLoadBalancerReturnsError()
|
||||||
|
{
|
||||||
|
_loadBalancer
|
||||||
|
.Setup(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||||
|
.ReturnsAsync(new ErrorResponse<ServiceHostAndPort>(new AnyError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenADifferentHostAndPortIsReturned()
|
||||||
|
{
|
||||||
|
_firstHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||||
|
_firstHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
_secondHostAndPort.Data.DownstreamHost.ShouldBe("two");
|
||||||
|
_secondHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WhenIMakeTwoRequetsWithDifferentSessionValues()
|
||||||
|
{
|
||||||
|
var contextOne = new DefaultHttpContext();
|
||||||
|
var cookiesOne = new FakeCookies();
|
||||||
|
cookiesOne.AddCookie("sessionid", "321");
|
||||||
|
contextOne.Request.Cookies = cookiesOne;
|
||||||
|
var contextTwo = new DefaultHttpContext();
|
||||||
|
var cookiesTwo = new FakeCookies();
|
||||||
|
cookiesTwo.AddCookie("sessionid", "123");
|
||||||
|
contextTwo.Request.Cookies = cookiesTwo;
|
||||||
|
_firstHostAndPort = await _stickySessions.Lease(new DownstreamContext(contextOne));
|
||||||
|
_secondHostAndPort = await _stickySessions.Lease(new DownstreamContext(contextTwo));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheLoadBalancerReturnsSequence()
|
||||||
|
{
|
||||||
|
_loadBalancer
|
||||||
|
.SetupSequence(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||||
|
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(new ServiceHostAndPort("one", 80)))
|
||||||
|
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(new ServiceHostAndPort("two", 80)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheFirstAndSecondResponseAreTheSame()
|
||||||
|
{
|
||||||
|
_firstHostAndPort.Data.DownstreamHost.ShouldBe(_secondHostAndPort.Data.DownstreamHost);
|
||||||
|
_firstHostAndPort.Data.DownstreamPort.ShouldBe(_secondHostAndPort.Data.DownstreamPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WhenILeaseTwiceInARow()
|
||||||
|
{
|
||||||
|
_firstHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
_secondHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheDownstreamRequestHasSessionId(string value)
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext();
|
||||||
|
var cookies = new FakeCookies();
|
||||||
|
cookies.AddCookie("sessionid", value);
|
||||||
|
context.Request.Cookies = cookies;
|
||||||
|
_downstreamContext = new DownstreamContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheLoadBalancerReturns()
|
||||||
|
{
|
||||||
|
_loadBalancer
|
||||||
|
.Setup(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||||
|
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(new ServiceHostAndPort("", 80)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WhenILease()
|
||||||
|
{
|
||||||
|
_result = await _stickySessions.Lease(_downstreamContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheHostAndPortIsNotNull()
|
||||||
|
{
|
||||||
|
_result.Data.ShouldNotBeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeCookies : IRequestCookieCollection
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, string> _cookies = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
public string this[string key] => _cookies[key];
|
||||||
|
|
||||||
|
public int Count => _cookies.Count;
|
||||||
|
|
||||||
|
public ICollection<string> Keys => _cookies.Keys;
|
||||||
|
|
||||||
|
public void AddCookie(string key, string value)
|
||||||
|
{
|
||||||
|
_cookies[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey(string key)
|
||||||
|
{
|
||||||
|
return _cookies.ContainsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
||||||
|
{
|
||||||
|
return _cookies.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(string key, out string value)
|
||||||
|
{
|
||||||
|
return _cookies.TryGetValue(key, out value);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return _cookies.GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,284 +1,288 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.LoadBalancer.LoadBalancers;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Responses;
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
using Ocelot.Values;
|
using Ocelot.Middleware;
|
||||||
using Shouldly;
|
using Ocelot.Responses;
|
||||||
using TestStack.BDDfy;
|
using Ocelot.Values;
|
||||||
using Xunit;
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
namespace Ocelot.UnitTests.LoadBalancer
|
using Xunit;
|
||||||
{
|
|
||||||
public class LeastConnectionTests
|
namespace Ocelot.UnitTests.LoadBalancer
|
||||||
{
|
{
|
||||||
private ServiceHostAndPort _hostAndPort;
|
public class LeastConnectionTests
|
||||||
private Response<ServiceHostAndPort> _result;
|
{
|
||||||
private LeastConnection _leastConnection;
|
private ServiceHostAndPort _hostAndPort;
|
||||||
private List<Service> _services;
|
private Response<ServiceHostAndPort> _result;
|
||||||
private Random _random;
|
private LeastConnection _leastConnection;
|
||||||
|
private List<Service> _services;
|
||||||
public LeastConnectionTests()
|
private Random _random;
|
||||||
{
|
private DownstreamContext _context;
|
||||||
_random = new Random();
|
|
||||||
}
|
public LeastConnectionTests()
|
||||||
|
{
|
||||||
[Fact]
|
_context = new DownstreamContext(new DefaultHttpContext());
|
||||||
public void should_be_able_to_lease_and_release_concurrently()
|
_random = new Random();
|
||||||
{
|
}
|
||||||
var serviceName = "products";
|
|
||||||
|
[Fact]
|
||||||
var availableServices = new List<Service>
|
public void should_be_able_to_lease_and_release_concurrently()
|
||||||
{
|
{
|
||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
var serviceName = "products";
|
||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
|
||||||
};
|
var availableServices = new List<Service>
|
||||||
|
{
|
||||||
_services = availableServices;
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||||
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||||
|
};
|
||||||
var tasks = new Task[100];
|
|
||||||
|
_services = availableServices;
|
||||||
for(var i = 0; i < tasks.Length; i++)
|
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||||
{
|
|
||||||
tasks[i] = LeaseDelayAndRelease();
|
var tasks = new Task[100];
|
||||||
}
|
|
||||||
|
for(var i = 0; i < tasks.Length; i++)
|
||||||
Task.WaitAll(tasks);
|
{
|
||||||
}
|
tasks[i] = LeaseDelayAndRelease();
|
||||||
|
}
|
||||||
[Fact]
|
|
||||||
public void should_handle_service_returning_to_available()
|
Task.WaitAll(tasks);
|
||||||
{
|
}
|
||||||
var serviceName = "products";
|
|
||||||
|
[Fact]
|
||||||
var availableServices = new List<Service>
|
public void should_handle_service_returning_to_available()
|
||||||
{
|
{
|
||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
var serviceName = "products";
|
||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
|
||||||
};
|
var availableServices = new List<Service>
|
||||||
|
{
|
||||||
_leastConnection = new LeastConnection(() => Task.FromResult(availableServices), serviceName);
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||||
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||||
var hostAndPortOne = _leastConnection.Lease().Result;
|
};
|
||||||
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
|
||||||
var hostAndPortTwo = _leastConnection.Lease().Result;
|
_leastConnection = new LeastConnection(() => Task.FromResult(availableServices), serviceName);
|
||||||
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2");
|
|
||||||
_leastConnection.Release(hostAndPortOne.Data);
|
var hostAndPortOne = _leastConnection.Lease(_context).Result;
|
||||||
_leastConnection.Release(hostAndPortTwo.Data);
|
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||||
|
var hostAndPortTwo = _leastConnection.Lease(_context).Result;
|
||||||
availableServices = new List<Service>
|
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2");
|
||||||
{
|
_leastConnection.Release(hostAndPortOne.Data);
|
||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
_leastConnection.Release(hostAndPortTwo.Data);
|
||||||
};
|
|
||||||
|
availableServices = new List<Service>
|
||||||
hostAndPortOne = _leastConnection.Lease().Result;
|
{
|
||||||
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||||
hostAndPortTwo = _leastConnection.Lease().Result;
|
};
|
||||||
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
|
||||||
_leastConnection.Release(hostAndPortOne.Data);
|
hostAndPortOne = _leastConnection.Lease(_context).Result;
|
||||||
_leastConnection.Release(hostAndPortTwo.Data);
|
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||||
|
hostAndPortTwo = _leastConnection.Lease(_context).Result;
|
||||||
availableServices = new List<Service>
|
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||||
{
|
_leastConnection.Release(hostAndPortOne.Data);
|
||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
_leastConnection.Release(hostAndPortTwo.Data);
|
||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
|
||||||
};
|
availableServices = new List<Service>
|
||||||
|
{
|
||||||
hostAndPortOne = _leastConnection.Lease().Result;
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||||
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||||
hostAndPortTwo = _leastConnection.Lease().Result;
|
};
|
||||||
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2");
|
|
||||||
_leastConnection.Release(hostAndPortOne.Data);
|
hostAndPortOne = _leastConnection.Lease(_context).Result;
|
||||||
_leastConnection.Release(hostAndPortTwo.Data);
|
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||||
}
|
hostAndPortTwo = _leastConnection.Lease(_context).Result;
|
||||||
|
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2");
|
||||||
private async Task LeaseDelayAndRelease()
|
_leastConnection.Release(hostAndPortOne.Data);
|
||||||
{
|
_leastConnection.Release(hostAndPortTwo.Data);
|
||||||
var hostAndPort = await _leastConnection.Lease();
|
}
|
||||||
await Task.Delay(_random.Next(1, 100));
|
|
||||||
_leastConnection.Release(hostAndPort.Data);
|
private async Task LeaseDelayAndRelease()
|
||||||
}
|
{
|
||||||
|
var hostAndPort = await _leastConnection.Lease(_context);
|
||||||
[Fact]
|
await Task.Delay(_random.Next(1, 100));
|
||||||
public void should_get_next_url()
|
_leastConnection.Release(hostAndPort.Data);
|
||||||
{
|
}
|
||||||
var serviceName = "products";
|
|
||||||
|
[Fact]
|
||||||
var hostAndPort = new ServiceHostAndPort("localhost", 80);
|
public void should_get_next_url()
|
||||||
|
{
|
||||||
var availableServices = new List<Service>
|
var serviceName = "products";
|
||||||
{
|
|
||||||
new Service(serviceName, hostAndPort, string.Empty, string.Empty, new string[0])
|
var hostAndPort = new ServiceHostAndPort("localhost", 80);
|
||||||
};
|
|
||||||
|
var availableServices = new List<Service>
|
||||||
this.Given(x => x.GivenAHostAndPort(hostAndPort))
|
{
|
||||||
.And(x => x.GivenTheLoadBalancerStarts(availableServices, serviceName))
|
new Service(serviceName, hostAndPort, string.Empty, string.Empty, new string[0])
|
||||||
.When(x => x.WhenIGetTheNextHostAndPort())
|
};
|
||||||
.Then(x => x.ThenTheNextHostAndPortIsReturned())
|
|
||||||
.BDDfy();
|
this.Given(x => x.GivenAHostAndPort(hostAndPort))
|
||||||
}
|
.And(x => x.GivenTheLoadBalancerStarts(availableServices, serviceName))
|
||||||
|
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||||
[Fact]
|
.Then(x => x.ThenTheNextHostAndPortIsReturned())
|
||||||
public void should_serve_from_service_with_least_connections()
|
.BDDfy();
|
||||||
{
|
}
|
||||||
var serviceName = "products";
|
|
||||||
|
[Fact]
|
||||||
var availableServices = new List<Service>
|
public void should_serve_from_service_with_least_connections()
|
||||||
{
|
{
|
||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
var serviceName = "products";
|
||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
|
||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.3", 80), string.Empty, string.Empty, new string[0])
|
var availableServices = new List<Service>
|
||||||
};
|
{
|
||||||
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||||
_services = availableServices;
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||||
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.3", 80), string.Empty, string.Empty, new string[0])
|
||||||
|
};
|
||||||
var response = _leastConnection.Lease().Result;
|
|
||||||
|
_services = availableServices;
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
var response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[2].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||||
}
|
|
||||||
|
response = _leastConnection.Lease(_context).Result;
|
||||||
[Fact]
|
|
||||||
public void should_build_connections_per_service()
|
response.Data.DownstreamHost.ShouldBe(availableServices[2].HostAndPort.DownstreamHost);
|
||||||
{
|
}
|
||||||
var serviceName = "products";
|
|
||||||
|
[Fact]
|
||||||
var availableServices = new List<Service>
|
public void should_build_connections_per_service()
|
||||||
{
|
{
|
||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
var serviceName = "products";
|
||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
|
||||||
};
|
var availableServices = new List<Service>
|
||||||
|
{
|
||||||
_services = availableServices;
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||||
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||||
|
};
|
||||||
var response = _leastConnection.Lease().Result;
|
|
||||||
|
_services = availableServices;
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
var response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||||
}
|
|
||||||
|
response = _leastConnection.Lease(_context).Result;
|
||||||
[Fact]
|
|
||||||
public void should_release_connection()
|
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||||
{
|
}
|
||||||
var serviceName = "products";
|
|
||||||
|
[Fact]
|
||||||
var availableServices = new List<Service>
|
public void should_release_connection()
|
||||||
{
|
{
|
||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
var serviceName = "products";
|
||||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
|
||||||
};
|
var availableServices = new List<Service>
|
||||||
|
{
|
||||||
_services = availableServices;
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||||
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||||
|
};
|
||||||
var response = _leastConnection.Lease().Result;
|
|
||||||
|
_services = availableServices;
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
var response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
response = _leastConnection.Lease().Result;
|
response = _leastConnection.Lease(_context).Result;
|
||||||
|
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||||
|
|
||||||
//release this so 2 should have 1 connection and we should get 2 back as our next host and port
|
response = _leastConnection.Lease(_context).Result;
|
||||||
_leastConnection.Release(availableServices[1].HostAndPort);
|
|
||||||
|
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||||
response = _leastConnection.Lease().Result;
|
|
||||||
|
//release this so 2 should have 1 connection and we should get 2 back as our next host and port
|
||||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
_leastConnection.Release(availableServices[1].HostAndPort);
|
||||||
}
|
|
||||||
|
response = _leastConnection.Lease(_context).Result;
|
||||||
[Fact]
|
|
||||||
public void should_return_error_if_services_are_null()
|
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||||
{
|
}
|
||||||
var serviceName = "products";
|
|
||||||
|
[Fact]
|
||||||
var hostAndPort = new ServiceHostAndPort("localhost", 80);
|
public void should_return_error_if_services_are_null()
|
||||||
this.Given(x => x.GivenAHostAndPort(hostAndPort))
|
{
|
||||||
.And(x => x.GivenTheLoadBalancerStarts(null, serviceName))
|
var serviceName = "products";
|
||||||
.When(x => x.WhenIGetTheNextHostAndPort())
|
|
||||||
.Then(x => x.ThenServiceAreNullErrorIsReturned())
|
var hostAndPort = new ServiceHostAndPort("localhost", 80);
|
||||||
.BDDfy();
|
this.Given(x => x.GivenAHostAndPort(hostAndPort))
|
||||||
}
|
.And(x => x.GivenTheLoadBalancerStarts(null, serviceName))
|
||||||
|
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||||
[Fact]
|
.Then(x => x.ThenServiceAreNullErrorIsReturned())
|
||||||
public void should_return_error_if_services_are_empty()
|
.BDDfy();
|
||||||
{
|
}
|
||||||
var serviceName = "products";
|
|
||||||
|
[Fact]
|
||||||
var hostAndPort = new ServiceHostAndPort("localhost", 80);
|
public void should_return_error_if_services_are_empty()
|
||||||
this.Given(x => x.GivenAHostAndPort(hostAndPort))
|
{
|
||||||
.And(x => x.GivenTheLoadBalancerStarts(new List<Service>(), serviceName))
|
var serviceName = "products";
|
||||||
.When(x => x.WhenIGetTheNextHostAndPort())
|
|
||||||
.Then(x => x.ThenServiceAreEmptyErrorIsReturned())
|
var hostAndPort = new ServiceHostAndPort("localhost", 80);
|
||||||
.BDDfy();
|
this.Given(x => x.GivenAHostAndPort(hostAndPort))
|
||||||
}
|
.And(x => x.GivenTheLoadBalancerStarts(new List<Service>(), serviceName))
|
||||||
|
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||||
private void ThenServiceAreNullErrorIsReturned()
|
.Then(x => x.ThenServiceAreEmptyErrorIsReturned())
|
||||||
{
|
.BDDfy();
|
||||||
_result.IsError.ShouldBeTrue();
|
}
|
||||||
_result.Errors[0].ShouldBeOfType<ServicesAreNullError>();
|
|
||||||
}
|
private void ThenServiceAreNullErrorIsReturned()
|
||||||
|
{
|
||||||
private void ThenServiceAreEmptyErrorIsReturned()
|
_result.IsError.ShouldBeTrue();
|
||||||
{
|
_result.Errors[0].ShouldBeOfType<ServicesAreNullError>();
|
||||||
_result.IsError.ShouldBeTrue();
|
}
|
||||||
_result.Errors[0].ShouldBeOfType<ServicesAreEmptyError>();
|
|
||||||
}
|
private void ThenServiceAreEmptyErrorIsReturned()
|
||||||
|
{
|
||||||
private void GivenTheLoadBalancerStarts(List<Service> services, string serviceName)
|
_result.IsError.ShouldBeTrue();
|
||||||
{
|
_result.Errors[0].ShouldBeOfType<ServicesAreEmptyError>();
|
||||||
_services = services;
|
}
|
||||||
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
|
||||||
}
|
private void GivenTheLoadBalancerStarts(List<Service> services, string serviceName)
|
||||||
|
{
|
||||||
private void WhenTheLoadBalancerStarts(List<Service> services, string serviceName)
|
_services = services;
|
||||||
{
|
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||||
GivenTheLoadBalancerStarts(services, serviceName);
|
}
|
||||||
}
|
|
||||||
|
private void WhenTheLoadBalancerStarts(List<Service> services, string serviceName)
|
||||||
private void GivenAHostAndPort(ServiceHostAndPort hostAndPort)
|
{
|
||||||
{
|
GivenTheLoadBalancerStarts(services, serviceName);
|
||||||
_hostAndPort = hostAndPort;
|
}
|
||||||
}
|
|
||||||
|
private void GivenAHostAndPort(ServiceHostAndPort hostAndPort)
|
||||||
private void WhenIGetTheNextHostAndPort()
|
{
|
||||||
{
|
_hostAndPort = hostAndPort;
|
||||||
_result = _leastConnection.Lease().Result;
|
}
|
||||||
}
|
|
||||||
|
private void WhenIGetTheNextHostAndPort()
|
||||||
private void ThenTheNextHostAndPortIsReturned()
|
{
|
||||||
{
|
_result = _leastConnection.Lease(_context).Result;
|
||||||
_result.Data.DownstreamHost.ShouldBe(_hostAndPort.DownstreamHost);
|
}
|
||||||
_result.Data.DownstreamPort.ShouldBe(_hostAndPort.DownstreamPort);
|
|
||||||
}
|
private void ThenTheNextHostAndPortIsReturned()
|
||||||
}
|
{
|
||||||
}
|
_result.Data.DownstreamHost.ShouldBe(_hostAndPort.DownstreamHost);
|
||||||
|
_result.Data.DownstreamPort.ShouldBe(_hostAndPort.DownstreamPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,126 +1,142 @@
|
|||||||
using Moq;
|
using Moq;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Configuration.Builder;
|
using Ocelot.Configuration.Builder;
|
||||||
using Ocelot.LoadBalancer.LoadBalancers;
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
using Ocelot.ServiceDiscovery;
|
using Ocelot.ServiceDiscovery;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Ocelot.ServiceDiscovery.Providers;
|
using Ocelot.ServiceDiscovery.Providers;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.LoadBalancer
|
namespace Ocelot.UnitTests.LoadBalancer
|
||||||
{
|
{
|
||||||
public class LoadBalancerFactoryTests
|
public class LoadBalancerFactoryTests
|
||||||
{
|
{
|
||||||
private DownstreamReRoute _reRoute;
|
private DownstreamReRoute _reRoute;
|
||||||
private LoadBalancerFactory _factory;
|
private readonly LoadBalancerFactory _factory;
|
||||||
private ILoadBalancer _result;
|
private ILoadBalancer _result;
|
||||||
private Mock<IServiceDiscoveryProviderFactory> _serviceProviderFactory;
|
private readonly Mock<IServiceDiscoveryProviderFactory> _serviceProviderFactory;
|
||||||
private Mock<IServiceDiscoveryProvider> _serviceProvider;
|
private readonly Mock<IServiceDiscoveryProvider> _serviceProvider;
|
||||||
private ServiceProviderConfiguration _serviceProviderConfig;
|
private ServiceProviderConfiguration _serviceProviderConfig;
|
||||||
|
|
||||||
public LoadBalancerFactoryTests()
|
public LoadBalancerFactoryTests()
|
||||||
{
|
{
|
||||||
_serviceProviderFactory = new Mock<IServiceDiscoveryProviderFactory>();
|
_serviceProviderFactory = new Mock<IServiceDiscoveryProviderFactory>();
|
||||||
_serviceProvider = new Mock<IServiceDiscoveryProvider>();
|
_serviceProvider = new Mock<IServiceDiscoveryProvider>();
|
||||||
_factory = new LoadBalancerFactory(_serviceProviderFactory.Object);
|
_factory = new LoadBalancerFactory(_serviceProviderFactory.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_no_load_balancer()
|
public void should_return_no_load_balancer()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenAReRoute(reRoute))
|
this.Given(x => x.GivenAReRoute(reRoute))
|
||||||
.And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build()))
|
.And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build()))
|
||||||
.And(x => x.GivenTheServiceProviderFactoryReturns())
|
.And(x => x.GivenTheServiceProviderFactoryReturns())
|
||||||
.When(x => x.WhenIGetTheLoadBalancer())
|
.When(x => x.WhenIGetTheLoadBalancer())
|
||||||
.Then(x => x.ThenTheLoadBalancerIsReturned<NoLoadBalancer>())
|
.Then(x => x.ThenTheLoadBalancerIsReturned<NoLoadBalancer>())
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_round_robin_load_balancer()
|
public void should_return_round_robin_load_balancer()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithLoadBalancer("RoundRobin")
|
.WithLoadBalancerOptions(new LoadBalancerOptions("RoundRobin", "", 0))
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenAReRoute(reRoute))
|
this.Given(x => x.GivenAReRoute(reRoute))
|
||||||
.And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build()))
|
.And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build()))
|
||||||
.And(x => x.GivenTheServiceProviderFactoryReturns())
|
.And(x => x.GivenTheServiceProviderFactoryReturns())
|
||||||
.When(x => x.WhenIGetTheLoadBalancer())
|
.When(x => x.WhenIGetTheLoadBalancer())
|
||||||
.Then(x => x.ThenTheLoadBalancerIsReturned<RoundRobin>())
|
.Then(x => x.ThenTheLoadBalancerIsReturned<RoundRobin>())
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_round_least_connection_balancer()
|
public void should_return_round_least_connection_balancer()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithLoadBalancer("LeastConnection")
|
.WithLoadBalancerOptions(new LoadBalancerOptions("LeastConnection", "", 0))
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenAReRoute(reRoute))
|
this.Given(x => x.GivenAReRoute(reRoute))
|
||||||
.And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build()))
|
.And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build()))
|
||||||
.And(x => x.GivenTheServiceProviderFactoryReturns())
|
.And(x => x.GivenTheServiceProviderFactoryReturns())
|
||||||
.When(x => x.WhenIGetTheLoadBalancer())
|
.When(x => x.WhenIGetTheLoadBalancer())
|
||||||
.Then(x => x.ThenTheLoadBalancerIsReturned<LeastConnection>())
|
.Then(x => x.ThenTheLoadBalancerIsReturned<LeastConnection>())
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_call_service_provider()
|
public void should_call_service_provider()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithLoadBalancer("RoundRobin")
|
.WithLoadBalancerOptions(new LoadBalancerOptions("RoundRobin", "", 0))
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenAReRoute(reRoute))
|
this.Given(x => x.GivenAReRoute(reRoute))
|
||||||
.And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build()))
|
.And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build()))
|
||||||
.And(x => x.GivenTheServiceProviderFactoryReturns())
|
.And(x => x.GivenTheServiceProviderFactoryReturns())
|
||||||
.When(x => x.WhenIGetTheLoadBalancer())
|
.When(x => x.WhenIGetTheLoadBalancer())
|
||||||
.Then(x => x.ThenTheServiceProviderIsCalledCorrectly())
|
.Then(x => x.ThenTheServiceProviderIsCalledCorrectly())
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenAServiceProviderConfig(ServiceProviderConfiguration serviceProviderConfig)
|
[Fact]
|
||||||
{
|
public void should_return_sticky_session()
|
||||||
_serviceProviderConfig = serviceProviderConfig;
|
{
|
||||||
}
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithLoadBalancerOptions(new LoadBalancerOptions("CookieStickySessions", "", 0))
|
||||||
private void GivenTheServiceProviderFactoryReturns()
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
{
|
.Build();
|
||||||
_serviceProviderFactory
|
|
||||||
.Setup(x => x.Get(It.IsAny<ServiceProviderConfiguration>(), It.IsAny<DownstreamReRoute>()))
|
this.Given(x => x.GivenAReRoute(reRoute))
|
||||||
.Returns(_serviceProvider.Object);
|
.And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build()))
|
||||||
}
|
.And(x => x.GivenTheServiceProviderFactoryReturns())
|
||||||
|
.When(x => x.WhenIGetTheLoadBalancer())
|
||||||
private void ThenTheServiceProviderIsCalledCorrectly()
|
.Then(x => x.ThenTheLoadBalancerIsReturned<CookieStickySessions>())
|
||||||
{
|
.BDDfy();
|
||||||
_serviceProviderFactory
|
}
|
||||||
.Verify(x => x.Get(It.IsAny<ServiceProviderConfiguration>(), It.IsAny<DownstreamReRoute>()), Times.Once);
|
|
||||||
}
|
private void GivenAServiceProviderConfig(ServiceProviderConfiguration serviceProviderConfig)
|
||||||
|
{
|
||||||
private void GivenAReRoute(DownstreamReRoute reRoute)
|
_serviceProviderConfig = serviceProviderConfig;
|
||||||
{
|
}
|
||||||
_reRoute = reRoute;
|
|
||||||
}
|
private void GivenTheServiceProviderFactoryReturns()
|
||||||
|
{
|
||||||
private void WhenIGetTheLoadBalancer()
|
_serviceProviderFactory
|
||||||
{
|
.Setup(x => x.Get(It.IsAny<ServiceProviderConfiguration>(), It.IsAny<DownstreamReRoute>()))
|
||||||
_result = _factory.Get(_reRoute, _serviceProviderConfig).Result;
|
.Returns(_serviceProvider.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheLoadBalancerIsReturned<T>()
|
private void ThenTheServiceProviderIsCalledCorrectly()
|
||||||
{
|
{
|
||||||
_result.ShouldBeOfType<T>();
|
_serviceProviderFactory
|
||||||
}
|
.Verify(x => x.Get(It.IsAny<ServiceProviderConfiguration>(), It.IsAny<DownstreamReRoute>()), Times.Once);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private void GivenAReRoute(DownstreamReRoute reRoute)
|
||||||
|
{
|
||||||
|
_reRoute = reRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenIGetTheLoadBalancer()
|
||||||
|
{
|
||||||
|
_result = _factory.Get(_reRoute, _serviceProviderConfig).Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheLoadBalancerIsReturned<T>()
|
||||||
|
{
|
||||||
|
_result.ShouldBeOfType<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,163 +1,182 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Configuration.Builder;
|
using Ocelot.Configuration.Builder;
|
||||||
using Ocelot.LoadBalancer.LoadBalancers;
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Values;
|
using Ocelot.Responses;
|
||||||
using Shouldly;
|
using Ocelot.Values;
|
||||||
using TestStack.BDDfy;
|
using Shouldly;
|
||||||
using Xunit;
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
namespace Ocelot.UnitTests.LoadBalancer
|
|
||||||
{
|
namespace Ocelot.UnitTests.LoadBalancer
|
||||||
public class LoadBalancerHouseTests
|
{
|
||||||
{
|
public class LoadBalancerHouseTests
|
||||||
private DownstreamReRoute _reRoute;
|
{
|
||||||
private ILoadBalancer _loadBalancer;
|
private DownstreamReRoute _reRoute;
|
||||||
private readonly LoadBalancerHouse _loadBalancerHouse;
|
private ILoadBalancer _loadBalancer;
|
||||||
private Response<ILoadBalancer> _getResult;
|
private readonly LoadBalancerHouse _loadBalancerHouse;
|
||||||
private readonly Mock<ILoadBalancerFactory> _factory;
|
private Response<ILoadBalancer> _getResult;
|
||||||
private readonly ServiceProviderConfiguration _serviceProviderConfig;
|
private readonly Mock<ILoadBalancerFactory> _factory;
|
||||||
|
private readonly ServiceProviderConfiguration _serviceProviderConfig;
|
||||||
public LoadBalancerHouseTests()
|
|
||||||
{
|
public LoadBalancerHouseTests()
|
||||||
_factory = new Mock<ILoadBalancerFactory>();
|
{
|
||||||
_loadBalancerHouse = new LoadBalancerHouse(_factory.Object);
|
_factory = new Mock<ILoadBalancerFactory>();
|
||||||
_serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123, string.Empty);
|
_loadBalancerHouse = new LoadBalancerHouse(_factory.Object);
|
||||||
}
|
_serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123, string.Empty);
|
||||||
|
}
|
||||||
[Fact]
|
|
||||||
public void should_store_load_balancer_on_first_request()
|
[Fact]
|
||||||
{
|
public void should_store_load_balancer_on_first_request()
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build();
|
{
|
||||||
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
.WithReRouteKey("test")
|
||||||
.Then(x => x.ThenItIsAdded())
|
.Build();
|
||||||
.BDDfy();
|
|
||||||
}
|
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||||
|
.Then(x => x.ThenItIsAdded())
|
||||||
[Fact]
|
.BDDfy();
|
||||||
public void should_not_store_load_balancer_on_second_request()
|
}
|
||||||
{
|
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build();
|
[Fact]
|
||||||
|
public void should_not_store_load_balancer_on_second_request()
|
||||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
{
|
||||||
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.Then(x => x.ThenItIsReturned())
|
.WithLoadBalancerOptions(new LoadBalancerOptions("FakeLoadBalancer", "", 0))
|
||||||
.BDDfy();
|
.WithReRouteKey("test")
|
||||||
}
|
.Build();
|
||||||
|
|
||||||
[Fact]
|
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||||
public void should_store_load_balancers_by_key()
|
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
||||||
{
|
.Then(x => x.ThenItIsReturned())
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build();
|
.BDDfy();
|
||||||
var reRouteTwo = new DownstreamReRouteBuilder().WithLoadBalancer("FakeRoundRobinLoadBalancer").WithReRouteKey("testtwo").Build();
|
}
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
[Fact]
|
||||||
.And(x => x.GivenThereIsALoadBalancer(reRouteTwo, new FakeRoundRobinLoadBalancer()))
|
public void should_store_load_balancers_by_key()
|
||||||
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
{
|
||||||
.Then(x => x.ThenTheLoadBalancerIs<FakeLoadBalancer>())
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.When(x => x.WhenWeGetTheLoadBalancer(reRouteTwo))
|
.WithLoadBalancerOptions(new LoadBalancerOptions("FakeLoadBalancer", "", 0))
|
||||||
.Then(x => x.ThenTheLoadBalancerIs<FakeRoundRobinLoadBalancer>())
|
.WithReRouteKey("test")
|
||||||
.BDDfy();
|
.Build();
|
||||||
}
|
|
||||||
|
var reRouteTwo = new DownstreamReRouteBuilder()
|
||||||
[Fact]
|
.WithLoadBalancerOptions(new LoadBalancerOptions("FakeRoundRobinLoadBalancer", "", 0))
|
||||||
public void should_return_error_if_exception()
|
.WithReRouteKey("testtwo")
|
||||||
{
|
.Build();
|
||||||
var reRoute = new DownstreamReRouteBuilder().Build();
|
|
||||||
|
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||||
this.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
.And(x => x.GivenThereIsALoadBalancer(reRouteTwo, new FakeRoundRobinLoadBalancer()))
|
||||||
.Then(x => x.ThenAnErrorIsReturned())
|
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
||||||
.BDDfy();
|
.Then(x => x.ThenTheLoadBalancerIs<FakeLoadBalancer>())
|
||||||
}
|
.When(x => x.WhenWeGetTheLoadBalancer(reRouteTwo))
|
||||||
|
.Then(x => x.ThenTheLoadBalancerIs<FakeRoundRobinLoadBalancer>())
|
||||||
[Fact]
|
.BDDfy();
|
||||||
public void should_get_new_load_balancer_if_reroute_load_balancer_has_changed()
|
}
|
||||||
{
|
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build();
|
[Fact]
|
||||||
|
public void should_return_error_if_exception()
|
||||||
var reRouteTwo = new DownstreamReRouteBuilder().WithLoadBalancer("LeastConnection").WithReRouteKey("test").Build();
|
{
|
||||||
|
var reRoute = new DownstreamReRouteBuilder().Build();
|
||||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
|
||||||
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
this.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
||||||
.Then(x => x.ThenTheLoadBalancerIs<FakeLoadBalancer>())
|
.Then(x => x.ThenAnErrorIsReturned())
|
||||||
.When(x => x.WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(reRouteTwo))
|
.BDDfy();
|
||||||
.Then(x => x.ThenTheLoadBalancerIs<LeastConnection>())
|
}
|
||||||
.BDDfy();
|
|
||||||
}
|
[Fact]
|
||||||
|
public void should_get_new_load_balancer_if_reroute_load_balancer_has_changed()
|
||||||
private void WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(DownstreamReRoute reRoute)
|
{
|
||||||
{
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
_reRoute = reRoute;
|
.WithLoadBalancerOptions(new LoadBalancerOptions("FakeLoadBalancer", "", 0))
|
||||||
_factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(new LeastConnection(null, null));
|
.WithReRouteKey("test")
|
||||||
_getResult = _loadBalancerHouse.Get(_reRoute, _serviceProviderConfig).Result;
|
.Build();
|
||||||
}
|
|
||||||
|
var reRouteTwo = new DownstreamReRouteBuilder()
|
||||||
private void ThenAnErrorIsReturned()
|
.WithLoadBalancerOptions(new LoadBalancerOptions("LeastConnection", "", 0))
|
||||||
{
|
.WithReRouteKey("test")
|
||||||
_getResult.IsError.ShouldBeTrue();
|
.Build();
|
||||||
_getResult.Errors[0].ShouldBeOfType<UnableToFindLoadBalancerError>();
|
|
||||||
}
|
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||||
|
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
||||||
private void ThenTheLoadBalancerIs<T>()
|
.Then(x => x.ThenTheLoadBalancerIs<FakeLoadBalancer>())
|
||||||
{
|
.When(x => x.WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(reRouteTwo))
|
||||||
_getResult.Data.ShouldBeOfType<T>();
|
.Then(x => x.ThenTheLoadBalancerIs<LeastConnection>())
|
||||||
}
|
.BDDfy();
|
||||||
|
}
|
||||||
private void ThenItIsAdded()
|
|
||||||
{
|
private void WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(DownstreamReRoute reRoute)
|
||||||
_getResult.IsError.ShouldBe(false);
|
{
|
||||||
_getResult.ShouldBeOfType<OkResponse<ILoadBalancer>>();
|
_reRoute = reRoute;
|
||||||
_getResult.Data.ShouldBe(_loadBalancer);
|
_factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(new LeastConnection(null, null));
|
||||||
_factory.Verify(x => x.Get(_reRoute, _serviceProviderConfig), Times.Once);
|
_getResult = _loadBalancerHouse.Get(_reRoute, _serviceProviderConfig).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenThereIsALoadBalancer(DownstreamReRoute reRoute, ILoadBalancer loadBalancer)
|
private void ThenAnErrorIsReturned()
|
||||||
{
|
{
|
||||||
_reRoute = reRoute;
|
_getResult.IsError.ShouldBeTrue();
|
||||||
_loadBalancer = loadBalancer;
|
_getResult.Errors[0].ShouldBeOfType<UnableToFindLoadBalancerError>();
|
||||||
_factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(loadBalancer);
|
}
|
||||||
_getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result;
|
|
||||||
}
|
private void ThenTheLoadBalancerIs<T>()
|
||||||
|
{
|
||||||
private void WhenWeGetTheLoadBalancer(DownstreamReRoute reRoute)
|
_getResult.Data.ShouldBeOfType<T>();
|
||||||
{
|
}
|
||||||
_getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result;
|
|
||||||
}
|
private void ThenItIsAdded()
|
||||||
|
{
|
||||||
private void ThenItIsReturned()
|
_getResult.IsError.ShouldBe(false);
|
||||||
{
|
_getResult.ShouldBeOfType<OkResponse<ILoadBalancer>>();
|
||||||
_getResult.Data.ShouldBe(_loadBalancer);
|
_getResult.Data.ShouldBe(_loadBalancer);
|
||||||
_factory.Verify(x => x.Get(_reRoute, _serviceProviderConfig), Times.Once);
|
_factory.Verify(x => x.Get(_reRoute, _serviceProviderConfig), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeLoadBalancer : ILoadBalancer
|
private void GivenThereIsALoadBalancer(DownstreamReRoute reRoute, ILoadBalancer loadBalancer)
|
||||||
{
|
{
|
||||||
public Task<Response<ServiceHostAndPort>> Lease()
|
_reRoute = reRoute;
|
||||||
{
|
_loadBalancer = loadBalancer;
|
||||||
throw new NotImplementedException();
|
_factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(loadBalancer);
|
||||||
}
|
_getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result;
|
||||||
|
}
|
||||||
public void Release(ServiceHostAndPort hostAndPort)
|
|
||||||
{
|
private void WhenWeGetTheLoadBalancer(DownstreamReRoute reRoute)
|
||||||
throw new NotImplementedException();
|
{
|
||||||
}
|
_getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeRoundRobinLoadBalancer : ILoadBalancer
|
private void ThenItIsReturned()
|
||||||
{
|
{
|
||||||
public Task<Response<ServiceHostAndPort>> Lease()
|
_getResult.Data.ShouldBe(_loadBalancer);
|
||||||
{
|
_factory.Verify(x => x.Get(_reRoute, _serviceProviderConfig), Times.Once);
|
||||||
throw new NotImplementedException();
|
}
|
||||||
}
|
|
||||||
|
class FakeLoadBalancer : ILoadBalancer
|
||||||
public void Release(ServiceHostAndPort hostAndPort)
|
{
|
||||||
{
|
public Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context)
|
||||||
throw new NotImplementedException();
|
{
|
||||||
}
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
public void Release(ServiceHostAndPort hostAndPort)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeRoundRobinLoadBalancer : ILoadBalancer
|
||||||
|
{
|
||||||
|
public Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Release(ServiceHostAndPort hostAndPort)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,193 +1,193 @@
|
|||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.LoadBalancer
|
namespace Ocelot.UnitTests.LoadBalancer
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Configuration.Builder;
|
using Ocelot.Configuration.Builder;
|
||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
using Ocelot.LoadBalancer.LoadBalancers;
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
using Ocelot.LoadBalancer.Middleware;
|
using Ocelot.LoadBalancer.Middleware;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Request.Middleware;
|
using Ocelot.Request.Middleware;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
public class LoadBalancerMiddlewareTests
|
public class LoadBalancerMiddlewareTests
|
||||||
{
|
{
|
||||||
private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse;
|
private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse;
|
||||||
private readonly Mock<ILoadBalancer> _loadBalancer;
|
private readonly Mock<ILoadBalancer> _loadBalancer;
|
||||||
private ServiceHostAndPort _hostAndPort;
|
private ServiceHostAndPort _hostAndPort;
|
||||||
private ErrorResponse<ILoadBalancer> _getLoadBalancerHouseError;
|
private ErrorResponse<ILoadBalancer> _getLoadBalancerHouseError;
|
||||||
private ErrorResponse<ServiceHostAndPort> _getHostAndPortError;
|
private ErrorResponse<ServiceHostAndPort> _getHostAndPortError;
|
||||||
private HttpRequestMessage _downstreamRequest;
|
private HttpRequestMessage _downstreamRequest;
|
||||||
private ServiceProviderConfiguration _config;
|
private ServiceProviderConfiguration _config;
|
||||||
private Mock<IOcelotLoggerFactory> _loggerFactory;
|
private Mock<IOcelotLoggerFactory> _loggerFactory;
|
||||||
private Mock<IOcelotLogger> _logger;
|
private Mock<IOcelotLogger> _logger;
|
||||||
private LoadBalancingMiddleware _middleware;
|
private LoadBalancingMiddleware _middleware;
|
||||||
private DownstreamContext _downstreamContext;
|
private DownstreamContext _downstreamContext;
|
||||||
private OcelotRequestDelegate _next;
|
private OcelotRequestDelegate _next;
|
||||||
|
|
||||||
public LoadBalancerMiddlewareTests()
|
public LoadBalancerMiddlewareTests()
|
||||||
{
|
{
|
||||||
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
|
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
|
||||||
_loadBalancer = new Mock<ILoadBalancer>();
|
_loadBalancer = new Mock<ILoadBalancer>();
|
||||||
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
|
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
|
||||||
_downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "http://test.com/");
|
_downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "http://test.com/");
|
||||||
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
|
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
|
||||||
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
||||||
_logger = new Mock<IOcelotLogger>();
|
_logger = new Mock<IOcelotLogger>();
|
||||||
_loggerFactory.Setup(x => x.CreateLogger<LoadBalancingMiddleware>()).Returns(_logger.Object);
|
_loggerFactory.Setup(x => x.CreateLogger<LoadBalancingMiddleware>()).Returns(_logger.Object);
|
||||||
_next = context => Task.CompletedTask;
|
_next = context => Task.CompletedTask;
|
||||||
_downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest);
|
_downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_call_scoped_data_repository_correctly()
|
public void should_call_scoped_data_repository_correctly()
|
||||||
{
|
{
|
||||||
var downstreamRoute = new DownstreamReRouteBuilder()
|
var downstreamRoute = new DownstreamReRouteBuilder()
|
||||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var serviceProviderConfig = new ServiceProviderConfigurationBuilder()
|
var serviceProviderConfig = new ServiceProviderConfigurationBuilder()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123"))
|
this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123"))
|
||||||
.And(x => GivenTheConfigurationIs(serviceProviderConfig))
|
.And(x => GivenTheConfigurationIs(serviceProviderConfig))
|
||||||
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue>()))
|
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue>()))
|
||||||
.And(x => x.GivenTheLoadBalancerHouseReturns())
|
.And(x => x.GivenTheLoadBalancerHouseReturns())
|
||||||
.And(x => x.GivenTheLoadBalancerReturns())
|
.And(x => x.GivenTheLoadBalancerReturns())
|
||||||
.When(x => x.WhenICallTheMiddleware())
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
.Then(x => x.ThenTheDownstreamUrlIsReplacedWith("http://127.0.0.1:80/abc?q=123"))
|
.Then(x => x.ThenTheDownstreamUrlIsReplacedWith("http://127.0.0.1:80/abc?q=123"))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_set_pipeline_error_if_cannot_get_load_balancer()
|
public void should_set_pipeline_error_if_cannot_get_load_balancer()
|
||||||
{
|
{
|
||||||
var downstreamRoute = new DownstreamReRouteBuilder()
|
var downstreamRoute = new DownstreamReRouteBuilder()
|
||||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var serviceProviderConfig = new ServiceProviderConfigurationBuilder()
|
var serviceProviderConfig = new ServiceProviderConfigurationBuilder()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123"))
|
this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123"))
|
||||||
.And(x => GivenTheConfigurationIs(serviceProviderConfig))
|
.And(x => GivenTheConfigurationIs(serviceProviderConfig))
|
||||||
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue>()))
|
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue>()))
|
||||||
.And(x => x.GivenTheLoadBalancerHouseReturnsAnError())
|
.And(x => x.GivenTheLoadBalancerHouseReturnsAnError())
|
||||||
.When(x => x.WhenICallTheMiddleware())
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
.Then(x => x.ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline())
|
.Then(x => x.ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline())
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_set_pipeline_error_if_cannot_get_least()
|
public void should_set_pipeline_error_if_cannot_get_least()
|
||||||
{
|
{
|
||||||
var downstreamRoute = new DownstreamReRouteBuilder()
|
var downstreamRoute = new DownstreamReRouteBuilder()
|
||||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var serviceProviderConfig = new ServiceProviderConfigurationBuilder()
|
var serviceProviderConfig = new ServiceProviderConfigurationBuilder()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123"))
|
this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123"))
|
||||||
.And(x => GivenTheConfigurationIs(serviceProviderConfig))
|
.And(x => GivenTheConfigurationIs(serviceProviderConfig))
|
||||||
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue>()))
|
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue>()))
|
||||||
.And(x => x.GivenTheLoadBalancerHouseReturns())
|
.And(x => x.GivenTheLoadBalancerHouseReturns())
|
||||||
.And(x => x.GivenTheLoadBalancerReturnsAnError())
|
.And(x => x.GivenTheLoadBalancerReturnsAnError())
|
||||||
.When(x => x.WhenICallTheMiddleware())
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
.Then(x => x.ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline())
|
.Then(x => x.ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline())
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WhenICallTheMiddleware()
|
private void WhenICallTheMiddleware()
|
||||||
{
|
{
|
||||||
_middleware = new LoadBalancingMiddleware(_next, _loggerFactory.Object, _loadBalancerHouse.Object);
|
_middleware = new LoadBalancingMiddleware(_next, _loggerFactory.Object, _loadBalancerHouse.Object);
|
||||||
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheConfigurationIs(ServiceProviderConfiguration config)
|
private void GivenTheConfigurationIs(ServiceProviderConfiguration config)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_downstreamContext.ServiceProviderConfiguration = config;
|
_downstreamContext.ServiceProviderConfiguration = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheDownStreamUrlIs(string downstreamUrl)
|
private void GivenTheDownStreamUrlIs(string downstreamUrl)
|
||||||
{
|
{
|
||||||
_downstreamRequest.RequestUri = new System.Uri(downstreamUrl);
|
_downstreamRequest.RequestUri = new System.Uri(downstreamUrl);
|
||||||
_downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest);
|
_downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheLoadBalancerReturnsAnError()
|
private void GivenTheLoadBalancerReturnsAnError()
|
||||||
{
|
{
|
||||||
_getHostAndPortError = new ErrorResponse<ServiceHostAndPort>(new List<Error>() { new ServicesAreNullError($"services were null for bah") });
|
_getHostAndPortError = new ErrorResponse<ServiceHostAndPort>(new List<Error>() { new ServicesAreNullError($"services were null for bah") });
|
||||||
_loadBalancer
|
_loadBalancer
|
||||||
.Setup(x => x.Lease())
|
.Setup(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||||
.ReturnsAsync(_getHostAndPortError);
|
.ReturnsAsync(_getHostAndPortError);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheLoadBalancerReturns()
|
private void GivenTheLoadBalancerReturns()
|
||||||
{
|
{
|
||||||
_hostAndPort = new ServiceHostAndPort("127.0.0.1", 80);
|
_hostAndPort = new ServiceHostAndPort("127.0.0.1", 80);
|
||||||
_loadBalancer
|
_loadBalancer
|
||||||
.Setup(x => x.Lease())
|
.Setup(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||||
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(_hostAndPort));
|
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(_hostAndPort));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheDownStreamRouteIs(DownstreamReRoute downstreamRoute, List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue> placeholder)
|
private void GivenTheDownStreamRouteIs(DownstreamReRoute downstreamRoute, List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue> placeholder)
|
||||||
{
|
{
|
||||||
_downstreamContext.TemplatePlaceholderNameAndValues = placeholder;
|
_downstreamContext.TemplatePlaceholderNameAndValues = placeholder;
|
||||||
_downstreamContext.DownstreamReRoute = downstreamRoute;
|
_downstreamContext.DownstreamReRoute = downstreamRoute;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheLoadBalancerHouseReturns()
|
private void GivenTheLoadBalancerHouseReturns()
|
||||||
{
|
{
|
||||||
_loadBalancerHouse
|
_loadBalancerHouse
|
||||||
.Setup(x => x.Get(It.IsAny<DownstreamReRoute>(), It.IsAny<ServiceProviderConfiguration>()))
|
.Setup(x => x.Get(It.IsAny<DownstreamReRoute>(), It.IsAny<ServiceProviderConfiguration>()))
|
||||||
.ReturnsAsync(new OkResponse<ILoadBalancer>(_loadBalancer.Object));
|
.ReturnsAsync(new OkResponse<ILoadBalancer>(_loadBalancer.Object));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheLoadBalancerHouseReturnsAnError()
|
private void GivenTheLoadBalancerHouseReturnsAnError()
|
||||||
{
|
{
|
||||||
_getLoadBalancerHouseError = new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
|
_getLoadBalancerHouseError = new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
|
||||||
{
|
{
|
||||||
new UnableToFindLoadBalancerError($"unabe to find load balancer for bah")
|
new UnableToFindLoadBalancerError($"unabe to find load balancer for bah")
|
||||||
});
|
});
|
||||||
|
|
||||||
_loadBalancerHouse
|
_loadBalancerHouse
|
||||||
.Setup(x => x.Get(It.IsAny<DownstreamReRoute>(), It.IsAny<ServiceProviderConfiguration>()))
|
.Setup(x => x.Get(It.IsAny<DownstreamReRoute>(), It.IsAny<ServiceProviderConfiguration>()))
|
||||||
.ReturnsAsync(_getLoadBalancerHouseError);
|
.ReturnsAsync(_getLoadBalancerHouseError);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline()
|
private void ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline()
|
||||||
{
|
{
|
||||||
_downstreamContext.IsError.ShouldBeTrue();
|
_downstreamContext.IsError.ShouldBeTrue();
|
||||||
_downstreamContext.Errors.ShouldBe(_getLoadBalancerHouseError.Errors);
|
_downstreamContext.Errors.ShouldBe(_getLoadBalancerHouseError.Errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenAnErrorSayingReleaseFailedIsSetOnThePipeline()
|
private void ThenAnErrorSayingReleaseFailedIsSetOnThePipeline()
|
||||||
{
|
{
|
||||||
_downstreamContext.IsError.ShouldBeTrue();
|
_downstreamContext.IsError.ShouldBeTrue();
|
||||||
_downstreamContext.Errors.ShouldBe(It.IsAny<List<Error>>());
|
_downstreamContext.Errors.ShouldBe(It.IsAny<List<Error>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline()
|
private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline()
|
||||||
{
|
{
|
||||||
_downstreamContext.IsError.ShouldBeTrue();
|
_downstreamContext.IsError.ShouldBeTrue();
|
||||||
_downstreamContext.Errors.ShouldBe(_getHostAndPortError.Errors);
|
_downstreamContext.Errors.ShouldBe(_getHostAndPortError.Errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheDownstreamUrlIsReplacedWith(string expectedUri)
|
private void ThenTheDownstreamUrlIsReplacedWith(string expectedUri)
|
||||||
{
|
{
|
||||||
_downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri);
|
_downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,75 +1,77 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Ocelot.LoadBalancer.LoadBalancers;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Responses;
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
using Ocelot.Values;
|
using Ocelot.Middleware;
|
||||||
using Shouldly;
|
using Ocelot.Responses;
|
||||||
using TestStack.BDDfy;
|
using Ocelot.Values;
|
||||||
using Xunit;
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
namespace Ocelot.UnitTests.LoadBalancer
|
using Xunit;
|
||||||
{
|
|
||||||
public class NoLoadBalancerTests
|
namespace Ocelot.UnitTests.LoadBalancer
|
||||||
{
|
{
|
||||||
private List<Service> _services;
|
public class NoLoadBalancerTests
|
||||||
private NoLoadBalancer _loadBalancer;
|
{
|
||||||
private Response<ServiceHostAndPort> _result;
|
private List<Service> _services;
|
||||||
|
private NoLoadBalancer _loadBalancer;
|
||||||
[Fact]
|
private Response<ServiceHostAndPort> _result;
|
||||||
public void should_return_host_and_port()
|
|
||||||
{
|
[Fact]
|
||||||
var hostAndPort = new ServiceHostAndPort("127.0.0.1", 80);
|
public void should_return_host_and_port()
|
||||||
|
{
|
||||||
var services = new List<Service>
|
var hostAndPort = new ServiceHostAndPort("127.0.0.1", 80);
|
||||||
{
|
|
||||||
new Service("product", hostAndPort, string.Empty, string.Empty, new string[0])
|
var services = new List<Service>
|
||||||
};
|
{
|
||||||
this.Given(x => x.GivenServices(services))
|
new Service("product", hostAndPort, string.Empty, string.Empty, new string[0])
|
||||||
.When(x => x.WhenIGetTheNextHostAndPort())
|
};
|
||||||
.Then(x => x.ThenTheHostAndPortIs(hostAndPort))
|
this.Given(x => x.GivenServices(services))
|
||||||
.BDDfy();
|
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||||
}
|
.Then(x => x.ThenTheHostAndPortIs(hostAndPort))
|
||||||
|
.BDDfy();
|
||||||
[Fact]
|
}
|
||||||
public void should_return_error_if_no_services()
|
|
||||||
{
|
[Fact]
|
||||||
var services = new List<Service>();
|
public void should_return_error_if_no_services()
|
||||||
|
{
|
||||||
this.Given(x => x.GivenServices(services))
|
var services = new List<Service>();
|
||||||
.When(x => x.WhenIGetTheNextHostAndPort())
|
|
||||||
.Then(x => x.ThenThereIsAnError())
|
this.Given(x => x.GivenServices(services))
|
||||||
.BDDfy();
|
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||||
}
|
.Then(x => x.ThenThereIsAnError())
|
||||||
|
.BDDfy();
|
||||||
[Fact]
|
}
|
||||||
public void should_return_error_if_null_services()
|
|
||||||
{
|
[Fact]
|
||||||
List<Service> services = null;
|
public void should_return_error_if_null_services()
|
||||||
|
{
|
||||||
this.Given(x => x.GivenServices(services))
|
List<Service> services = null;
|
||||||
.When(x => x.WhenIGetTheNextHostAndPort())
|
|
||||||
.Then(x => x.ThenThereIsAnError())
|
this.Given(x => x.GivenServices(services))
|
||||||
.BDDfy();
|
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||||
}
|
.Then(x => x.ThenThereIsAnError())
|
||||||
|
.BDDfy();
|
||||||
private void ThenThereIsAnError()
|
}
|
||||||
{
|
|
||||||
_result.IsError.ShouldBeTrue();
|
private void ThenThereIsAnError()
|
||||||
}
|
{
|
||||||
|
_result.IsError.ShouldBeTrue();
|
||||||
private void GivenServices(List<Service> services)
|
}
|
||||||
{
|
|
||||||
_services = services;
|
private void GivenServices(List<Service> services)
|
||||||
}
|
{
|
||||||
|
_services = services;
|
||||||
private void WhenIGetTheNextHostAndPort()
|
}
|
||||||
{
|
|
||||||
_loadBalancer = new NoLoadBalancer(_services);
|
private void WhenIGetTheNextHostAndPort()
|
||||||
_result = _loadBalancer.Lease().Result;
|
{
|
||||||
}
|
_loadBalancer = new NoLoadBalancer(_services);
|
||||||
|
_result = _loadBalancer.Lease(new DownstreamContext(new DefaultHttpContext())).Result;
|
||||||
private void ThenTheHostAndPortIs(ServiceHostAndPort expected)
|
}
|
||||||
{
|
|
||||||
_result.Data.ShouldBe(expected);
|
private void ThenTheHostAndPortIs(ServiceHostAndPort expected)
|
||||||
}
|
{
|
||||||
}
|
_result.Data.ShouldBe(expected);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,69 +1,74 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Ocelot.LoadBalancer.LoadBalancers;
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Middleware;
|
||||||
namespace Ocelot.UnitTests.LoadBalancer
|
using Microsoft.AspNetCore.Http;
|
||||||
{
|
|
||||||
public class RoundRobinTests
|
namespace Ocelot.UnitTests.LoadBalancer
|
||||||
{
|
{
|
||||||
private readonly RoundRobin _roundRobin;
|
public class RoundRobinTests
|
||||||
private readonly List<Service> _services;
|
{
|
||||||
private Response<ServiceHostAndPort> _hostAndPort;
|
private readonly RoundRobin _roundRobin;
|
||||||
|
private readonly List<Service> _services;
|
||||||
public RoundRobinTests()
|
private Response<ServiceHostAndPort> _hostAndPort;
|
||||||
{
|
private DownstreamContext _context;
|
||||||
_services = new List<Service>
|
|
||||||
{
|
public RoundRobinTests()
|
||||||
new Service("product", new ServiceHostAndPort("127.0.0.1", 5000), string.Empty, string.Empty, new string[0]),
|
{
|
||||||
new Service("product", new ServiceHostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0]),
|
_context = new DownstreamContext(new DefaultHttpContext());
|
||||||
new Service("product", new ServiceHostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0])
|
|
||||||
};
|
_services = new List<Service>
|
||||||
|
{
|
||||||
_roundRobin = new RoundRobin(() => Task.FromResult(_services));
|
new Service("product", new ServiceHostAndPort("127.0.0.1", 5000), string.Empty, string.Empty, new string[0]),
|
||||||
}
|
new Service("product", new ServiceHostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0]),
|
||||||
|
new Service("product", new ServiceHostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0])
|
||||||
[Fact]
|
};
|
||||||
public void should_get_next_address()
|
|
||||||
{
|
_roundRobin = new RoundRobin(() => Task.FromResult(_services));
|
||||||
this.Given(x => x.GivenIGetTheNextAddress())
|
}
|
||||||
.Then(x => x.ThenTheNextAddressIndexIs(0))
|
|
||||||
.Given(x => x.GivenIGetTheNextAddress())
|
[Fact]
|
||||||
.Then(x => x.ThenTheNextAddressIndexIs(1))
|
public void should_get_next_address()
|
||||||
.Given(x => x.GivenIGetTheNextAddress())
|
{
|
||||||
.Then(x => x.ThenTheNextAddressIndexIs(2))
|
this.Given(x => x.GivenIGetTheNextAddress())
|
||||||
.BDDfy();
|
.Then(x => x.ThenTheNextAddressIndexIs(0))
|
||||||
}
|
.Given(x => x.GivenIGetTheNextAddress())
|
||||||
|
.Then(x => x.ThenTheNextAddressIndexIs(1))
|
||||||
[Fact]
|
.Given(x => x.GivenIGetTheNextAddress())
|
||||||
public void should_go_back_to_first_address_after_finished_last()
|
.Then(x => x.ThenTheNextAddressIndexIs(2))
|
||||||
{
|
.BDDfy();
|
||||||
var stopWatch = Stopwatch.StartNew();
|
}
|
||||||
|
|
||||||
while (stopWatch.ElapsedMilliseconds < 1000)
|
[Fact]
|
||||||
{
|
public void should_go_back_to_first_address_after_finished_last()
|
||||||
var address = _roundRobin.Lease().Result;
|
{
|
||||||
address.Data.ShouldBe(_services[0].HostAndPort);
|
var stopWatch = Stopwatch.StartNew();
|
||||||
address = _roundRobin.Lease().Result;
|
|
||||||
address.Data.ShouldBe(_services[1].HostAndPort);
|
while (stopWatch.ElapsedMilliseconds < 1000)
|
||||||
address = _roundRobin.Lease().Result;
|
{
|
||||||
address.Data.ShouldBe(_services[2].HostAndPort);
|
var address = _roundRobin.Lease(_context).Result;
|
||||||
}
|
address.Data.ShouldBe(_services[0].HostAndPort);
|
||||||
}
|
address = _roundRobin.Lease(_context).Result;
|
||||||
|
address.Data.ShouldBe(_services[1].HostAndPort);
|
||||||
private void GivenIGetTheNextAddress()
|
address = _roundRobin.Lease(_context).Result;
|
||||||
{
|
address.Data.ShouldBe(_services[2].HostAndPort);
|
||||||
_hostAndPort = _roundRobin.Lease().Result;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheNextAddressIndexIs(int index)
|
private void GivenIGetTheNextAddress()
|
||||||
{
|
{
|
||||||
_hostAndPort.Data.ShouldBe(_services[index].HostAndPort);
|
_hostAndPort = _roundRobin.Lease(_context).Result;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
private void ThenTheNextAddressIndexIs(int index)
|
||||||
|
{
|
||||||
|
_hostAndPort.Data.ShouldBe(_services[index].HostAndPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -26,7 +26,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_store_qos_provider_on_first_request()
|
public void should_store_qos_provider_on_first_request()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build();
|
var reRoute = new DownstreamReRouteBuilder().WithQosKey("test").Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
||||||
.Then(x => x.ThenItIsAdded())
|
.Then(x => x.ThenItIsAdded())
|
||||||
@ -36,7 +36,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_not_store_qos_provider_on_first_request()
|
public void should_not_store_qos_provider_on_first_request()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build();
|
var reRoute = new DownstreamReRouteBuilder().WithQosKey("test").Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
||||||
.When(x => x.WhenWeGetTheQoSProvider(reRoute))
|
.When(x => x.WhenWeGetTheQoSProvider(reRoute))
|
||||||
@ -47,8 +47,8 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_store_qos_providers_by_key()
|
public void should_store_qos_providers_by_key()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build();
|
var reRoute = new DownstreamReRouteBuilder().WithQosKey("test").Build();
|
||||||
var reRouteTwo = new DownstreamReRouteBuilder().WithReRouteKey("testTwo").Build();
|
var reRouteTwo = new DownstreamReRouteBuilder().WithQosKey("testTwo").Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
||||||
.And(x => x.GivenThereIsAQoSProvider(reRouteTwo, new FakePollyQoSProvider()))
|
.And(x => x.GivenThereIsAQoSProvider(reRouteTwo, new FakePollyQoSProvider()))
|
||||||
@ -72,9 +72,9 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_get_new_qos_provider_if_reroute_qos_provider_has_changed()
|
public void should_get_new_qos_provider_if_reroute_qos_provider_has_changed()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build();
|
var reRoute = new DownstreamReRouteBuilder().WithQosKey("test").Build();
|
||||||
|
|
||||||
var reRouteTwo = new DownstreamReRouteBuilder().WithReRouteKey("test").WithIsQos(true).Build();
|
var reRouteTwo = new DownstreamReRouteBuilder().WithQosKey("test").WithIsQos(true).Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
|
||||||
.When(x => x.WhenWeGetTheQoSProvider(reRoute))
|
.When(x => x.WhenWeGetTheQoSProvider(reRoute))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user