mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-23 00:32:50 +08:00
Merge branch 'develop' of https://github.com/geffzhang/Ocelot into develop
This commit is contained in:
commit
456c2b9c89
@ -9,19 +9,24 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitignore = .gitignore
|
||||
appveyor.yml = appveyor.yml
|
||||
build-and-release-unstable.ps1 = build-and-release-unstable.ps1
|
||||
build-and-run-tests.ps1 = build-and-run-tests.ps1
|
||||
build.cake = build.cake
|
||||
build.ps1 = build.ps1
|
||||
build.readme.md = build.readme.md
|
||||
configuration-explanation.txt = configuration-explanation.txt
|
||||
configuration.yaml = configuration.yaml
|
||||
GitVersion.yml = GitVersion.yml
|
||||
global.json = global.json
|
||||
LICENSE.md = LICENSE.md
|
||||
Ocelot.nuspec = Ocelot.nuspec
|
||||
README.md = README.md
|
||||
release.ps1 = release.ps1
|
||||
ReleaseNotes.md = ReleaseNotes.md
|
||||
run-acceptance-tests.ps1 = run-acceptance-tests.ps1
|
||||
run-benchmarks.bat = run-benchmarks.bat
|
||||
run-benchmarks.ps1 = run-benchmarks.ps1
|
||||
run-unit-tests.ps1 = run-unit-tests.ps1
|
||||
version.ps1 = version.ps1
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot", "src\Ocelot\Ocelot.xproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}"
|
||||
|
43
README.md
43
README.md
@ -41,13 +41,13 @@ touch either via gitter or create an issue.
|
||||
## How to install
|
||||
|
||||
Ocelot is designed to work with ASP.NET core only and is currently
|
||||
built to netcoreapp1.4 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you.
|
||||
built to netcoreapp1.1 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you.
|
||||
|
||||
Install Ocelot and it's dependecies using nuget. At the moment
|
||||
all we have is the pre version. Once we have something working in
|
||||
a half decent way we will drop a version.
|
||||
|
||||
`Install-Package Ocelot -Pre`
|
||||
`Install-Package Ocelot`
|
||||
|
||||
All versions can be found [here](https://www.nuget.org/packages/Ocelot/)
|
||||
|
||||
@ -162,6 +162,44 @@ This means that when Ocelot tries to match the incoming upstream url with an ups
|
||||
evaluation will be case sensitive. This setting defaults to false so only set it if you want
|
||||
the ReRoute to be case sensitive is my advice!
|
||||
|
||||
|
||||
## Service Discovery
|
||||
|
||||
Ocelot allows you to specify a service discovery provider and will use this to find the host and port
|
||||
for the downstream service Ocelot is forwarding a request to. At the moment this is only supported in the
|
||||
GlobalConfiguration section which means the same service discovery provider will be used for all ReRoutes
|
||||
you specify a ServiceName for at ReRoute level.
|
||||
|
||||
In the future we can add a feature that allows ReRoute specfic configuration.
|
||||
|
||||
At the moment the only supported service discovery provider is Consul. The following is required in the
|
||||
GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default
|
||||
will be used.
|
||||
|
||||
"ServiceDiscoveryProvider":
|
||||
{
|
||||
"Provider":"Consul",
|
||||
"Host":"localhost",
|
||||
"Port":8500
|
||||
}
|
||||
|
||||
In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the
|
||||
ServiceName and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin
|
||||
and LeastConnection algorithm you can use. If no load balancer is specified Ocelot will not load balance requests.
|
||||
|
||||
{
|
||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||
"DownstreamScheme": "https",
|
||||
"UpstreamTemplate": "/posts/{postId}",
|
||||
"UpstreamHttpMethod": "Put",
|
||||
"ServiceName": "product"
|
||||
"LoadBalancer": "LeastConnection"
|
||||
}
|
||||
|
||||
When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balancer
|
||||
requests across any available services.
|
||||
|
||||
|
||||
## Authentication
|
||||
|
||||
Ocelot currently supports the use of bearer tokens with Identity Server (more providers to
|
||||
@ -389,3 +427,4 @@ that isnt available is annoying. Let alone it be null.
|
||||
You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1)
|
||||
|
||||
|
||||
|
||||
|
@ -3,6 +3,6 @@ configuration:
|
||||
- Release
|
||||
platform: Any CPU
|
||||
build_script:
|
||||
- ./build.ps1
|
||||
- build.ps1
|
||||
cache:
|
||||
- '%USERPROFILE%\.nuget\packages'
|
@ -42,8 +42,8 @@ var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package";
|
||||
|
||||
// internal build variables - don't change these.
|
||||
var releaseTag = "";
|
||||
var buildVersion = committedVersion;
|
||||
var committedVersion = "0.0.0-dev";
|
||||
var buildVersion = committedVersion;
|
||||
|
||||
var target = Argument("target", "Default");
|
||||
|
||||
|
101
build.sh
Executable file
101
build.sh
Executable file
@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##########################################################################
|
||||
# This is the Cake bootstrapper script for Linux and OS X.
|
||||
# This file was downloaded from https://github.com/cake-build/resources
|
||||
# Feel free to change this file to fit your needs.
|
||||
##########################################################################
|
||||
|
||||
# Define directories.
|
||||
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||
TOOLS_DIR=$SCRIPT_DIR/tools
|
||||
NUGET_EXE=$TOOLS_DIR/nuget.exe
|
||||
CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe
|
||||
PACKAGES_CONFIG=$TOOLS_DIR/packages.config
|
||||
PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum
|
||||
|
||||
# Define md5sum or md5 depending on Linux/OSX
|
||||
MD5_EXE=
|
||||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||
MD5_EXE="md5 -r"
|
||||
else
|
||||
MD5_EXE="md5sum"
|
||||
fi
|
||||
|
||||
# Define default arguments.
|
||||
SCRIPT="build.cake"
|
||||
TARGET="Default"
|
||||
CONFIGURATION="Release"
|
||||
VERBOSITY="verbose"
|
||||
DRYRUN=
|
||||
SHOW_VERSION=false
|
||||
SCRIPT_ARGUMENTS=()
|
||||
|
||||
# Parse arguments.
|
||||
for i in "$@"; do
|
||||
case $1 in
|
||||
-s|--script) SCRIPT="$2"; shift ;;
|
||||
-t|--target) TARGET="$2"; shift ;;
|
||||
-c|--configuration) CONFIGURATION="$2"; shift ;;
|
||||
-v|--verbosity) VERBOSITY="$2"; shift ;;
|
||||
-d|--dryrun) DRYRUN="-dryrun" ;;
|
||||
--version) SHOW_VERSION=true ;;
|
||||
--) shift; SCRIPT_ARGUMENTS+=("$@"); break ;;
|
||||
*) SCRIPT_ARGUMENTS+=("$1") ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Make sure the tools folder exist.
|
||||
if [ ! -d "$TOOLS_DIR" ]; then
|
||||
mkdir "$TOOLS_DIR"
|
||||
fi
|
||||
|
||||
# Make sure that packages.config exist.
|
||||
if [ ! -f "$TOOLS_DIR/packages.config" ]; then
|
||||
echo "Downloading packages.config..."
|
||||
curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "An error occured while downloading packages.config."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Download NuGet if it does not exist.
|
||||
if [ ! -f "$NUGET_EXE" ]; then
|
||||
echo "Downloading NuGet..."
|
||||
curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "An error occured while downloading nuget.exe."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Restore tools from NuGet.
|
||||
pushd "$TOOLS_DIR" >/dev/null
|
||||
if [ ! -f "$PACKAGES_CONFIG_MD5" ] || [ "$( cat "$PACKAGES_CONFIG_MD5" | sed 's/\r$//' )" != "$( $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' )" ]; then
|
||||
find . -type d ! -name . | xargs rm -rf
|
||||
fi
|
||||
|
||||
mono "$NUGET_EXE" install -ExcludeVersion
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Could not restore NuGet packages."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' >| "$PACKAGES_CONFIG_MD5"
|
||||
|
||||
popd >/dev/null
|
||||
|
||||
# Make sure that Cake has been installed.
|
||||
if [ ! -f "$CAKE_EXE" ]; then
|
||||
echo "Could not find Cake.exe at '$CAKE_EXE'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Start Cake
|
||||
if $SHOW_VERSION; then
|
||||
exec mono "$CAKE_EXE" -version
|
||||
else
|
||||
exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -configuration=$CONFIGURATION -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}"
|
||||
fi
|
@ -80,12 +80,24 @@
|
||||
# the caching a lot.
|
||||
"FileCacheOptions": { "TtlSeconds": 15 },
|
||||
# The value of this is used when matching the upstream template to an upstream url.
|
||||
"ReRouteIsCaseSensitive": false
|
||||
"ReRouteIsCaseSensitive": false,
|
||||
# Tells Ocelot the name of the service it is looking when making requests to service discovery
|
||||
# for hosts and ports
|
||||
"ServiceName": "product"
|
||||
# Tells Ocelot which load balancer to use when making downstream requests.
|
||||
"LoadBalancer": "RoundRobin"
|
||||
},
|
||||
# This section is meant to be for global configuration settings
|
||||
"GlobalConfiguration": {
|
||||
# If this is set it will override any route specific request id keys, behaves the same
|
||||
# otherwise
|
||||
"RequestIdKey": "OcRequestId",
|
||||
# If set Ocelot will try and use service discovery to locate downstream hosts and ports
|
||||
"ServiceDiscoveryProvider":
|
||||
{
|
||||
"Provider":"Consul",
|
||||
"Host":"localhost",
|
||||
"Port":8500
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ namespace Ocelot.Configuration.Builder
|
||||
{
|
||||
public class ReRouteBuilder
|
||||
{
|
||||
private string _loadBalancerKey;
|
||||
private string _downstreamPathTemplate;
|
||||
private string _upstreamTemplate;
|
||||
private string _upstreamTemplatePattern;
|
||||
@ -32,12 +33,21 @@ namespace Ocelot.Configuration.Builder
|
||||
private string _downstreamScheme;
|
||||
private string _downstreamHost;
|
||||
private int _dsPort;
|
||||
private string _loadBalancer;
|
||||
private string _serviceProviderHost;
|
||||
private int _serviceProviderPort;
|
||||
|
||||
public ReRouteBuilder()
|
||||
{
|
||||
_additionalScopes = new List<string>();
|
||||
}
|
||||
|
||||
public ReRouteBuilder WithLoadBalancer(string loadBalancer)
|
||||
{
|
||||
_loadBalancer = loadBalancer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReRouteBuilder WithDownstreamScheme(string downstreamScheme)
|
||||
{
|
||||
_downstreamScheme = downstreamScheme;
|
||||
@ -192,15 +202,31 @@ namespace Ocelot.Configuration.Builder
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReRouteBuilder WithLoadBalancerKey(string loadBalancerKey)
|
||||
{
|
||||
_loadBalancerKey = loadBalancerKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReRouteBuilder WithServiceProviderHost(string serviceProviderHost)
|
||||
{
|
||||
_serviceProviderHost = serviceProviderHost;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReRouteBuilder WithServiceProviderPort(int serviceProviderPort)
|
||||
{
|
||||
_serviceProviderPort = serviceProviderPort;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReRoute Build()
|
||||
{
|
||||
Func<HostAndPort> downstreamHostFunc = () => new HostAndPort(_downstreamHost, _dsPort);
|
||||
|
||||
return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern,
|
||||
_isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName,
|
||||
_requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement,
|
||||
_isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName,
|
||||
_useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, downstreamHostFunc, _downstreamScheme);
|
||||
_isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer,
|
||||
_downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Parser;
|
||||
using Ocelot.Configuration.Validator;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Utilities;
|
||||
using Ocelot.Values;
|
||||
@ -25,22 +27,28 @@ namespace Ocelot.Configuration.Creator
|
||||
|
||||
private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser;
|
||||
private readonly ILogger<FileOcelotConfigurationCreator> _logger;
|
||||
private readonly ILoadBalancerFactory _loadBalanceFactory;
|
||||
private readonly ILoadBalancerHouse _loadBalancerHouse;
|
||||
|
||||
public FileOcelotConfigurationCreator(
|
||||
IOptions<FileConfiguration> options,
|
||||
IConfigurationValidator configurationValidator,
|
||||
IClaimToThingConfigurationParser claimToThingConfigurationParser,
|
||||
ILogger<FileOcelotConfigurationCreator> logger)
|
||||
ILogger<FileOcelotConfigurationCreator> logger,
|
||||
ILoadBalancerFactory loadBalancerFactory,
|
||||
ILoadBalancerHouse loadBalancerHouse)
|
||||
{
|
||||
_loadBalanceFactory = loadBalancerFactory;
|
||||
_loadBalancerHouse = loadBalancerHouse;
|
||||
_options = options;
|
||||
_configurationValidator = configurationValidator;
|
||||
_claimToThingConfigurationParser = claimToThingConfigurationParser;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Response<IOcelotConfiguration> Create()
|
||||
public async Task<Response<IOcelotConfiguration>> Create()
|
||||
{
|
||||
var config = SetUpConfiguration();
|
||||
var config = await SetUpConfiguration();
|
||||
|
||||
return new OkResponse<IOcelotConfiguration>(config);
|
||||
}
|
||||
@ -49,7 +57,7 @@ namespace Ocelot.Configuration.Creator
|
||||
/// This method is meant to be tempoary to convert a config to an ocelot config...probably wont keep this but we will see
|
||||
/// will need a refactor at some point as its crap
|
||||
/// </summary>
|
||||
private IOcelotConfiguration SetUpConfiguration()
|
||||
private async Task<IOcelotConfiguration> SetUpConfiguration()
|
||||
{
|
||||
var response = _configurationValidator.IsValid(_options.Value);
|
||||
|
||||
@ -69,63 +77,81 @@ namespace Ocelot.Configuration.Creator
|
||||
|
||||
foreach (var reRoute in _options.Value.ReRoutes)
|
||||
{
|
||||
var ocelotReRoute = SetUpReRoute(reRoute, _options.Value.GlobalConfiguration);
|
||||
var ocelotReRoute = await SetUpReRoute(reRoute, _options.Value.GlobalConfiguration);
|
||||
reRoutes.Add(ocelotReRoute);
|
||||
}
|
||||
|
||||
return new OcelotConfiguration(reRoutes);
|
||||
}
|
||||
|
||||
private ReRoute SetUpReRoute(FileReRoute reRoute, FileGlobalConfiguration globalConfiguration)
|
||||
private async Task<ReRoute> SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
|
||||
{
|
||||
var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey);
|
||||
|
||||
var upstreamTemplate = BuildUpstreamTemplate(reRoute);
|
||||
var upstreamTemplate = BuildUpstreamTemplate(fileReRoute);
|
||||
|
||||
var isAuthenticated = !string.IsNullOrEmpty(reRoute.AuthenticationOptions?.Provider);
|
||||
var isAuthenticated = !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider);
|
||||
|
||||
var isAuthorised = reRoute.RouteClaimsRequirement?.Count > 0;
|
||||
var isAuthorised = fileReRoute.RouteClaimsRequirement?.Count > 0;
|
||||
|
||||
var isCached = reRoute.FileCacheOptions.TtlSeconds > 0;
|
||||
var isCached = fileReRoute.FileCacheOptions.TtlSeconds > 0;
|
||||
|
||||
var requestIdKey = globalRequestIdConfiguration
|
||||
? globalConfiguration.RequestIdKey
|
||||
: reRoute.RequestIdKey;
|
||||
: fileReRoute.RequestIdKey;
|
||||
|
||||
var useServiceDiscovery = !string.IsNullOrEmpty(reRoute.ServiceName)
|
||||
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Address)
|
||||
var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName)
|
||||
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider);
|
||||
|
||||
//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.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}";
|
||||
|
||||
Func<HostAndPort> downstreamHostAndPortFunc = () => new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort);
|
||||
ReRoute reRoute;
|
||||
|
||||
var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0;
|
||||
|
||||
var serviceProviderConfiguration = new ServiceProviderConfiguraion(fileReRoute.ServiceName,
|
||||
fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, useServiceDiscovery,
|
||||
globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Host,
|
||||
serviceProviderPort);
|
||||
|
||||
if (isAuthenticated)
|
||||
{
|
||||
var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider,
|
||||
reRoute.AuthenticationOptions.ProviderRootUrl, reRoute.AuthenticationOptions.ScopeName,
|
||||
reRoute.AuthenticationOptions.RequireHttps, reRoute.AuthenticationOptions.AdditionalScopes,
|
||||
reRoute.AuthenticationOptions.ScopeSecret);
|
||||
var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider,
|
||||
fileReRoute.AuthenticationOptions.ProviderRootUrl, fileReRoute.AuthenticationOptions.ScopeName,
|
||||
fileReRoute.AuthenticationOptions.RequireHttps, fileReRoute.AuthenticationOptions.AdditionalScopes,
|
||||
fileReRoute.AuthenticationOptions.ScopeSecret);
|
||||
|
||||
var claimsToHeaders = GetAddThingsToRequest(reRoute.AddHeadersToRequest);
|
||||
var claimsToClaims = GetAddThingsToRequest(reRoute.AddClaimsToRequest);
|
||||
var claimsToQueries = GetAddThingsToRequest(reRoute.AddQueriesToRequest);
|
||||
var claimsToHeaders = GetAddThingsToRequest(fileReRoute.AddHeadersToRequest);
|
||||
var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest);
|
||||
var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest);
|
||||
|
||||
return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate,
|
||||
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
||||
reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate),
|
||||
fileReRoute.UpstreamTemplate,
|
||||
fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
||||
authOptionsForRoute, claimsToHeaders, claimsToClaims,
|
||||
reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries,
|
||||
requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds),
|
||||
reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider,
|
||||
globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme);
|
||||
fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries,
|
||||
requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)
|
||||
, fileReRoute.DownstreamScheme,
|
||||
fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey,
|
||||
serviceProviderConfiguration);
|
||||
}
|
||||
else
|
||||
{
|
||||
reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate),
|
||||
fileReRoute.UpstreamTemplate,
|
||||
fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
||||
null, new List<ClaimToThing>(), new List<ClaimToThing>(),
|
||||
fileReRoute.RouteClaimsRequirement, isAuthorised, new List<ClaimToThing>(),
|
||||
requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds),
|
||||
fileReRoute.DownstreamScheme,
|
||||
fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey,
|
||||
serviceProviderConfiguration);
|
||||
}
|
||||
|
||||
return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate,
|
||||
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
||||
null, new List<ClaimToThing>(), new List<ClaimToThing>(),
|
||||
reRoute.RouteClaimsRequirement, isAuthorised, new List<ClaimToThing>(),
|
||||
requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds),
|
||||
reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider,
|
||||
globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme);
|
||||
var loadBalancer = await _loadBalanceFactory.Get(reRoute);
|
||||
_loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer);
|
||||
return reRoute;
|
||||
}
|
||||
|
||||
private string BuildUpstreamTemplate(FileReRoute reRoute)
|
||||
|
@ -1,9 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public interface IOcelotConfigurationCreator
|
||||
{
|
||||
Response<IOcelotConfiguration> Create();
|
||||
Task<Response<IOcelotConfiguration>> Create();
|
||||
}
|
||||
}
|
@ -29,5 +29,6 @@ namespace Ocelot.Configuration.File
|
||||
public string DownstreamScheme {get;set;}
|
||||
public string DownstreamHost {get;set;}
|
||||
public int DownstreamPort { get; set; }
|
||||
public string LoadBalancer {get;set;}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ namespace Ocelot.Configuration.File
|
||||
public class FileServiceDiscoveryProvider
|
||||
{
|
||||
public string Provider {get;set;}
|
||||
public string Address {get;set;}
|
||||
public string Host {get;set;}
|
||||
public int Port { get; set; }
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
using Ocelot.Responses;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Configuration.Provider
|
||||
{
|
||||
public interface IOcelotConfigurationProvider
|
||||
{
|
||||
Response<IOcelotConfiguration> Get();
|
||||
Task<Response<IOcelotConfiguration>> Get();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ocelot.Configuration.Creator;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration.Creator;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Ocelot.Responses;
|
||||
|
||||
@ -19,7 +20,7 @@ namespace Ocelot.Configuration.Provider
|
||||
_creator = creator;
|
||||
}
|
||||
|
||||
public Response<IOcelotConfiguration> Get()
|
||||
public async Task<Response<IOcelotConfiguration>> Get()
|
||||
{
|
||||
var repoConfig = _repo.Get();
|
||||
|
||||
@ -30,7 +31,7 @@ namespace Ocelot.Configuration.Provider
|
||||
|
||||
if (repoConfig.Data == null)
|
||||
{
|
||||
var creatorConfig = _creator.Create();
|
||||
var creatorConfig = await _creator.Create();
|
||||
|
||||
if (creatorConfig.IsError)
|
||||
{
|
||||
|
@ -6,12 +6,23 @@ namespace Ocelot.Configuration
|
||||
{
|
||||
public class ReRoute
|
||||
{
|
||||
public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern,
|
||||
bool isAuthenticated, AuthenticationOptions authenticationOptions, List<ClaimToThing> configurationHeaderExtractorProperties,
|
||||
List<ClaimToThing> claimsToClaims, Dictionary<string, string> routeClaimsRequirement, bool isAuthorised, List<ClaimToThing> claimsToQueries,
|
||||
string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery,
|
||||
string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func<HostAndPort> downstreamHostAndPort, string downstreamScheme)
|
||||
public ReRoute(DownstreamPathTemplate downstreamPathTemplate,
|
||||
string upstreamTemplate, string upstreamHttpMethod,
|
||||
string upstreamTemplatePattern,
|
||||
bool isAuthenticated, AuthenticationOptions authenticationOptions,
|
||||
List<ClaimToThing> configurationHeaderExtractorProperties,
|
||||
List<ClaimToThing> claimsToClaims,
|
||||
Dictionary<string, string> routeClaimsRequirement, bool isAuthorised,
|
||||
List<ClaimToThing> claimsToQueries,
|
||||
string requestIdKey, bool isCached, CacheOptions fileCacheOptions,
|
||||
string downstreamScheme, string loadBalancer, string downstreamHost,
|
||||
int downstreamPort, string loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion)
|
||||
{
|
||||
LoadBalancerKey = loadBalancerKey;
|
||||
ServiceProviderConfiguraion = serviceProviderConfiguraion;
|
||||
LoadBalancer = loadBalancer;
|
||||
DownstreamHost = downstreamHost;
|
||||
DownstreamPort = downstreamPort;
|
||||
DownstreamPathTemplate = downstreamPathTemplate;
|
||||
UpstreamTemplate = upstreamTemplate;
|
||||
UpstreamHttpMethod = upstreamHttpMethod;
|
||||
@ -29,14 +40,10 @@ namespace Ocelot.Configuration
|
||||
?? new List<ClaimToThing>();
|
||||
ClaimsToHeaders = configurationHeaderExtractorProperties
|
||||
?? new List<ClaimToThing>();
|
||||
ServiceName = serviceName;
|
||||
UseServiceDiscovery = useServiceDiscovery;
|
||||
ServiceDiscoveryProvider = serviceDiscoveryProvider;
|
||||
ServiceDiscoveryAddress = serviceDiscoveryAddress;
|
||||
DownstreamHostAndPort = downstreamHostAndPort;
|
||||
DownstreamScheme = downstreamScheme;
|
||||
}
|
||||
|
||||
public string LoadBalancerKey {get;private set;}
|
||||
public DownstreamPathTemplate DownstreamPathTemplate { get; private set; }
|
||||
public string UpstreamTemplate { get; private set; }
|
||||
public string UpstreamTemplatePattern { get; private set; }
|
||||
@ -51,11 +58,10 @@ namespace Ocelot.Configuration
|
||||
public string RequestIdKey { get; private set; }
|
||||
public bool IsCached { get; private set; }
|
||||
public CacheOptions FileCacheOptions { get; private set; }
|
||||
public string ServiceName { get; private set;}
|
||||
public bool UseServiceDiscovery { get; private set;}
|
||||
public string ServiceDiscoveryProvider { get; private set;}
|
||||
public string ServiceDiscoveryAddress { get; private set;}
|
||||
public Func<HostAndPort> DownstreamHostAndPort {get;private set;}
|
||||
public string DownstreamScheme {get;private set;}
|
||||
public string LoadBalancer {get;private set;}
|
||||
public string DownstreamHost { get; private set; }
|
||||
public int DownstreamPort { get; private set; }
|
||||
public ServiceProviderConfiguraion ServiceProviderConfiguraion { get; private set; }
|
||||
}
|
||||
}
|
25
src/Ocelot/Configuration/ServiceProviderConfiguraion.cs
Normal file
25
src/Ocelot/Configuration/ServiceProviderConfiguraion.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace Ocelot.Configuration
|
||||
{
|
||||
public class ServiceProviderConfiguraion
|
||||
{
|
||||
public ServiceProviderConfiguraion(string serviceName, string downstreamHost,
|
||||
int downstreamPort, bool useServiceDiscovery, string serviceDiscoveryProvider, string serviceProviderHost, int serviceProviderPort)
|
||||
{
|
||||
ServiceName = serviceName;
|
||||
DownstreamHost = downstreamHost;
|
||||
DownstreamPort = downstreamPort;
|
||||
UseServiceDiscovery = useServiceDiscovery;
|
||||
ServiceDiscoveryProvider = serviceDiscoveryProvider;
|
||||
ServiceProviderHost = serviceProviderHost;
|
||||
ServiceProviderPort = serviceProviderPort;
|
||||
}
|
||||
|
||||
public string ServiceName { get; }
|
||||
public string DownstreamHost { get; }
|
||||
public int DownstreamPort { get; }
|
||||
public bool UseServiceDiscovery { get; }
|
||||
public string ServiceDiscoveryProvider { get; }
|
||||
public string ServiceProviderHost { get; private set; }
|
||||
public int ServiceProviderPort { get; private set; }
|
||||
}
|
||||
}
|
@ -23,11 +23,13 @@ using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
||||
using Ocelot.Headers;
|
||||
using Ocelot.Infrastructure.Claims.Parser;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.QueryStrings;
|
||||
using Ocelot.Request.Builder;
|
||||
using Ocelot.Requester;
|
||||
using Ocelot.Responder;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
|
||||
namespace Ocelot.DependencyInjection
|
||||
{
|
||||
@ -59,6 +61,9 @@ namespace Ocelot.DependencyInjection
|
||||
{
|
||||
services.AddMvcCore().AddJsonFormatters();
|
||||
services.AddLogging();
|
||||
services.AddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
|
||||
services.AddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
|
||||
services.AddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
|
||||
services.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
|
||||
services.AddSingleton<IUrlBuilder, UrlBuilder>();
|
||||
services.AddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration.Provider;
|
||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||
using Ocelot.Errors;
|
||||
@ -21,9 +22,9 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
||||
_urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder;
|
||||
}
|
||||
|
||||
public Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod)
|
||||
public async Task<Response<DownstreamRoute>> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod)
|
||||
{
|
||||
var configuration = _configProvider.Get();
|
||||
var configuration = await _configProvider.Get();
|
||||
|
||||
var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase));
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
using Ocelot.Responses;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.DownstreamRouteFinder.Finder
|
||||
{
|
||||
public interface IDownstreamRouteFinder
|
||||
{
|
||||
Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod);
|
||||
Task<Response<DownstreamRoute>> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod);
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
|
||||
|
||||
_logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath);
|
||||
|
||||
var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method);
|
||||
var downstreamRoute = await _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method);
|
||||
|
||||
if (downstreamRoute.IsError)
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.DownstreamUrlCreator.Middleware
|
||||
{
|
||||
@ -46,7 +47,7 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
|
||||
|
||||
var dsScheme = DownstreamRoute.ReRoute.DownstreamScheme;
|
||||
|
||||
var dsHostAndPort = DownstreamRoute.ReRoute.DownstreamHostAndPort();
|
||||
var dsHostAndPort = HostAndPort;
|
||||
|
||||
var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort);
|
||||
|
||||
|
@ -41,13 +41,13 @@ namespace Ocelot.Errors.Middleware
|
||||
|
||||
var message = CreateMessage(context, e);
|
||||
_logger.LogError(message, e);
|
||||
await SetInternalServerErrorOnResponse(context);
|
||||
SetInternalServerErrorOnResponse(context);
|
||||
}
|
||||
|
||||
_logger.LogDebug("ocelot pipeline finished");
|
||||
}
|
||||
|
||||
private async Task SetInternalServerErrorOnResponse(HttpContext context)
|
||||
private void SetInternalServerErrorOnResponse(HttpContext context)
|
||||
{
|
||||
context.Response.OnStarting(x =>
|
||||
{
|
||||
|
@ -21,6 +21,10 @@
|
||||
DownstreamPathTemplateContainsSchemeError,
|
||||
DownstreamPathNullOrEmptyError,
|
||||
DownstreamSchemeNullOrEmptyError,
|
||||
DownstreamHostNullOrEmptyError
|
||||
DownstreamHostNullOrEmptyError,
|
||||
ServicesAreNullError,
|
||||
ServicesAreEmptyError,
|
||||
UnableToFindServiceDiscoveryProviderError,
|
||||
UnableToFindLoadBalancerError
|
||||
}
|
||||
}
|
||||
|
24
src/Ocelot/Infrastructure/Extensions/StringExtensions.cs
Normal file
24
src/Ocelot/Infrastructure/Extensions/StringExtensions.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace Ocelot.Infrastructure.Extensions
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
public static string TrimStart(this string source, string trim, StringComparison stringComparison = StringComparison.Ordinal)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string s = source;
|
||||
while (s.StartsWith(trim, stringComparison))
|
||||
{
|
||||
s = s.Substring(trim.Length);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
13
src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs
Normal file
13
src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public interface ILoadBalancer
|
||||
{
|
||||
Task<Response<HostAndPort>> Lease();
|
||||
void Release(HostAndPort hostAndPort);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public interface ILoadBalancerFactory
|
||||
{
|
||||
Task<ILoadBalancer> Get(ReRoute reRoute);
|
||||
}
|
||||
}
|
10
src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs
Normal file
10
src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public interface ILoadBalancerHouse
|
||||
{
|
||||
Response<ILoadBalancer> Get(string key);
|
||||
Response Add(string key, ILoadBalancer loadBalancer);
|
||||
}
|
||||
}
|
15
src/Ocelot/LoadBalancer/LoadBalancers/Lease.cs
Normal file
15
src/Ocelot/LoadBalancer/LoadBalancers/Lease.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class Lease
|
||||
{
|
||||
public Lease(HostAndPort hostAndPort, int connections)
|
||||
{
|
||||
HostAndPort = hostAndPort;
|
||||
Connections = connections;
|
||||
}
|
||||
public HostAndPort HostAndPort { get; private set; }
|
||||
public int Connections { get; private set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Errors;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class LeastConnectionLoadBalancer : ILoadBalancer
|
||||
{
|
||||
private readonly Func<Task<List<Service>>> _services;
|
||||
private readonly List<Lease> _leases;
|
||||
private readonly string _serviceName;
|
||||
private static readonly object _syncLock = new object();
|
||||
|
||||
public LeastConnectionLoadBalancer(Func<Task<List<Service>>> services, string serviceName)
|
||||
{
|
||||
_services = services;
|
||||
_serviceName = serviceName;
|
||||
_leases = new List<Lease>();
|
||||
}
|
||||
|
||||
public async Task<Response<HostAndPort>> Lease()
|
||||
{
|
||||
var services = await _services.Invoke();
|
||||
|
||||
if (services == null)
|
||||
{
|
||||
return new ErrorResponse<HostAndPort>(new List<Error>() { new ServicesAreNullError($"services were null for {_serviceName}") });
|
||||
}
|
||||
|
||||
if (!services.Any())
|
||||
{
|
||||
return new ErrorResponse<HostAndPort>(new List<Error>() { new ServicesAreEmptyError($"services were empty for {_serviceName}") });
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
var leaseWithLeastConnections = GetLeaseWithLeastConnections();
|
||||
|
||||
_leases.Remove(leaseWithLeastConnections);
|
||||
|
||||
leaseWithLeastConnections = AddConnection(leaseWithLeastConnections);
|
||||
|
||||
_leases.Add(leaseWithLeastConnections);
|
||||
|
||||
return new OkResponse<HostAndPort>(new HostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort));
|
||||
}
|
||||
}
|
||||
|
||||
public void Release(HostAndPort hostAndPort)
|
||||
{
|
||||
lock(_syncLock)
|
||||
{
|
||||
var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost
|
||||
&& l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort);
|
||||
|
||||
if (matchingLease != null)
|
||||
{
|
||||
var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1);
|
||||
|
||||
_leases.Remove(matchingLease);
|
||||
|
||||
_leases.Add(replacementLease);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Lease AddConnection(Lease lease)
|
||||
{
|
||||
return new Lease(lease.HostAndPort, lease.Connections + 1);
|
||||
}
|
||||
|
||||
private Lease GetLeaseWithLeastConnections()
|
||||
{
|
||||
//now get the service with the least connections?
|
||||
Lease leaseWithLeastConnections = null;
|
||||
|
||||
for (var i = 0; i < _leases.Count; i++)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
leaseWithLeastConnections = _leases[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_leases[i].Connections < leaseWithLeastConnections.Connections)
|
||||
{
|
||||
leaseWithLeastConnections = _leases[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return leaseWithLeastConnections;
|
||||
}
|
||||
|
||||
private Response UpdateServices(List<Service> services)
|
||||
{
|
||||
if (_leases.Count > 0)
|
||||
{
|
||||
var leasesToRemove = new List<Lease>();
|
||||
|
||||
foreach (var lease in _leases)
|
||||
{
|
||||
var match = services.FirstOrDefault(s => s.HostAndPort.DownstreamHost == lease.HostAndPort.DownstreamHost
|
||||
&& s.HostAndPort.DownstreamPort == lease.HostAndPort.DownstreamPort);
|
||||
|
||||
if (match == null)
|
||||
{
|
||||
leasesToRemove.Add(lease);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var lease in leasesToRemove)
|
||||
{
|
||||
_leases.Remove(lease);
|
||||
}
|
||||
|
||||
foreach (var service in services)
|
||||
{
|
||||
var exists = _leases.FirstOrDefault(l => l.HostAndPort.ToString() == service.HostAndPort.ToString());
|
||||
|
||||
if (exists == null)
|
||||
{
|
||||
_leases.Add(new Lease(service.HostAndPort, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var service in services)
|
||||
{
|
||||
_leases.Add(new Lease(service.HostAndPort, 0));
|
||||
}
|
||||
}
|
||||
|
||||
return new OkResponse();
|
||||
}
|
||||
}
|
||||
}
|
39
src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs
Normal file
39
src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class LoadBalancerFactory : ILoadBalancerFactory
|
||||
{
|
||||
private readonly IServiceDiscoveryProviderFactory _serviceProviderFactory;
|
||||
public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFactory)
|
||||
{
|
||||
_serviceProviderFactory = serviceProviderFactory;
|
||||
}
|
||||
|
||||
public async Task<ILoadBalancer> Get(ReRoute reRoute)
|
||||
{
|
||||
var serviceConfig = new ServiceProviderConfiguraion(
|
||||
reRoute.ServiceProviderConfiguraion.ServiceName,
|
||||
reRoute.ServiceProviderConfiguraion.DownstreamHost,
|
||||
reRoute.ServiceProviderConfiguraion.DownstreamPort,
|
||||
reRoute.ServiceProviderConfiguraion.UseServiceDiscovery,
|
||||
reRoute.ServiceProviderConfiguraion.ServiceDiscoveryProvider,
|
||||
reRoute.ServiceProviderConfiguraion.ServiceProviderHost,
|
||||
reRoute.ServiceProviderConfiguraion.ServiceProviderPort);
|
||||
|
||||
var serviceProvider = _serviceProviderFactory.Get(serviceConfig);
|
||||
|
||||
switch (reRoute.LoadBalancer)
|
||||
{
|
||||
case "RoundRobin":
|
||||
return new RoundRobinLoadBalancer(await serviceProvider.Get());
|
||||
case "LeastConnection":
|
||||
return new LeastConnectionLoadBalancer(async () => await serviceProvider.Get(), reRoute.ServiceProviderConfiguraion.ServiceName);
|
||||
default:
|
||||
return new NoLoadBalancer(await serviceProvider.Get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
45
src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs
Normal file
45
src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class LoadBalancerHouse : ILoadBalancerHouse
|
||||
{
|
||||
private readonly Dictionary<string, ILoadBalancer> _loadBalancers;
|
||||
|
||||
public LoadBalancerHouse()
|
||||
{
|
||||
_loadBalancers = new Dictionary<string, ILoadBalancer>();
|
||||
}
|
||||
|
||||
public Response<ILoadBalancer> Get(string key)
|
||||
{
|
||||
ILoadBalancer loadBalancer;
|
||||
|
||||
if(_loadBalancers.TryGetValue(key, out loadBalancer))
|
||||
{
|
||||
return new OkResponse<ILoadBalancer>(_loadBalancers[key]);
|
||||
}
|
||||
|
||||
return new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
|
||||
{
|
||||
new UnableToFindLoadBalancerError($"unabe to find load balancer for {key}")
|
||||
});
|
||||
}
|
||||
|
||||
public Response Add(string key, ILoadBalancer loadBalancer)
|
||||
{
|
||||
if (!_loadBalancers.ContainsKey(key))
|
||||
{
|
||||
_loadBalancers.Add(key, loadBalancer);
|
||||
}
|
||||
|
||||
_loadBalancers.Remove(key);
|
||||
_loadBalancers.Add(key, loadBalancer);
|
||||
return new OkResponse();
|
||||
}
|
||||
}
|
||||
}
|
28
src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs
Normal file
28
src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class NoLoadBalancer : ILoadBalancer
|
||||
{
|
||||
private readonly List<Service> _services;
|
||||
|
||||
public NoLoadBalancer(List<Service> services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task<Response<HostAndPort>> Lease()
|
||||
{
|
||||
var service = await Task.FromResult(_services.FirstOrDefault());
|
||||
return new OkResponse<HostAndPort>(service.HostAndPort);
|
||||
}
|
||||
|
||||
public void Release(HostAndPort hostAndPort)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class RoundRobinLoadBalancer : ILoadBalancer
|
||||
{
|
||||
private readonly List<Service> _services;
|
||||
private int _last;
|
||||
|
||||
public RoundRobinLoadBalancer(List<Service> services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task<Response<HostAndPort>> Lease()
|
||||
{
|
||||
if (_last >= _services.Count)
|
||||
{
|
||||
_last = 0;
|
||||
}
|
||||
|
||||
var next = await Task.FromResult(_services[_last]);
|
||||
_last++;
|
||||
return new OkResponse<HostAndPort>(next.HostAndPort);
|
||||
}
|
||||
|
||||
public void Release(HostAndPort hostAndPort)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using Ocelot.Errors;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class ServicesAreEmptyError : Error
|
||||
{
|
||||
public ServicesAreEmptyError(string message)
|
||||
: base(message, OcelotErrorCode.ServicesAreEmptyError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using Ocelot.Errors;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class ServicesAreNullError : Error
|
||||
{
|
||||
public ServicesAreNullError(string message)
|
||||
: base(message, OcelotErrorCode.ServicesAreNullError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using Ocelot.Errors;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class UnableToFindLoadBalancerError : Errors.Error
|
||||
{
|
||||
public UnableToFindLoadBalancerError(string message)
|
||||
: base(message, OcelotErrorCode.UnableToFindLoadBalancerError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.QueryStrings.Middleware;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
|
||||
namespace Ocelot.LoadBalancer.Middleware
|
||||
{
|
||||
public class LoadBalancingMiddleware : OcelotMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IOcelotLogger _logger;
|
||||
private readonly ILoadBalancerHouse _loadBalancerHouse;
|
||||
|
||||
public LoadBalancingMiddleware(RequestDelegate next,
|
||||
IOcelotLoggerFactory loggerFactory,
|
||||
IRequestScopedDataRepository requestScopedDataRepository,
|
||||
ILoadBalancerHouse loadBalancerHouse)
|
||||
: base(requestScopedDataRepository)
|
||||
{
|
||||
_next = next;
|
||||
_logger = loggerFactory.CreateLogger<QueryStringBuilderMiddleware>();
|
||||
_loadBalancerHouse = loadBalancerHouse;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
_logger.LogDebug("started calling load balancing middleware");
|
||||
|
||||
var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey);
|
||||
if(loadBalancer.IsError)
|
||||
{
|
||||
SetPipelineError(loadBalancer.Errors);
|
||||
return;
|
||||
}
|
||||
|
||||
var hostAndPort = await loadBalancer.Data.Lease();
|
||||
if(hostAndPort.IsError)
|
||||
{
|
||||
SetPipelineError(hostAndPort.Errors);
|
||||
return;
|
||||
}
|
||||
|
||||
SetHostAndPortForThisRequest(hostAndPort.Data);
|
||||
|
||||
_logger.LogDebug("calling next middleware");
|
||||
|
||||
try
|
||||
{
|
||||
await _next.Invoke(context);
|
||||
|
||||
loadBalancer.Data.Release(hostAndPort.Data);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
loadBalancer.Data.Release(hostAndPort.Data);
|
||||
|
||||
_logger.LogDebug("error calling next middleware, exception will be thrown to global handler");
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.LogDebug("succesfully called next middleware");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace Ocelot.LoadBalancer.Middleware
|
||||
{
|
||||
public static class LoadBalancingMiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseLoadBalancingMiddleware(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<LoadBalancingMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using System.Net.Http;
|
||||
using Ocelot.DownstreamRouteFinder;
|
||||
using Ocelot.Errors;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.Middleware
|
||||
{
|
||||
@ -69,6 +70,20 @@ namespace Ocelot.Middleware
|
||||
}
|
||||
}
|
||||
|
||||
public HostAndPort HostAndPort
|
||||
{
|
||||
get
|
||||
{
|
||||
var hostAndPort = _requestScopedDataRepository.Get<HostAndPort>("HostAndPort");
|
||||
return hostAndPort.Data;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetHostAndPortForThisRequest(HostAndPort hostAndPort)
|
||||
{
|
||||
_requestScopedDataRepository.Add("HostAndPort", hostAndPort);
|
||||
}
|
||||
|
||||
public void SetDownstreamRouteForThisRequest(DownstreamRoute downstreamRoute)
|
||||
{
|
||||
_requestScopedDataRepository.Add("DownstreamRoute", downstreamRoute);
|
||||
|
@ -18,6 +18,8 @@ namespace Ocelot.Middleware
|
||||
using System.Threading.Tasks;
|
||||
using Authorisation.Middleware;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Ocelot.Configuration.Provider;
|
||||
using Ocelot.LoadBalancer.Middleware;
|
||||
|
||||
public static class OcelotMiddlewareExtensions
|
||||
{
|
||||
@ -28,6 +30,7 @@ namespace Ocelot.Middleware
|
||||
/// <returns></returns>
|
||||
public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder)
|
||||
{
|
||||
CreateConfiguration(builder);
|
||||
builder.UseOcelot(new OcelotMiddlewareConfiguration());
|
||||
return builder;
|
||||
}
|
||||
@ -40,6 +43,8 @@ namespace Ocelot.Middleware
|
||||
/// <returns></returns>
|
||||
public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration)
|
||||
{
|
||||
CreateConfiguration(builder);
|
||||
|
||||
// This is registered to catch any global exceptions that are not handled
|
||||
builder.UseExceptionHandlerMiddleware();
|
||||
|
||||
@ -98,6 +103,9 @@ namespace Ocelot.Middleware
|
||||
// Now we can run any query string transformation logic
|
||||
builder.UseQueryStringBuilderMiddleware();
|
||||
|
||||
// Get the load balancer for this request
|
||||
builder.UseLoadBalancingMiddleware();
|
||||
|
||||
// This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used
|
||||
builder.UseDownstreamUrlCreatorMiddleware();
|
||||
|
||||
@ -114,6 +122,18 @@ namespace Ocelot.Middleware
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void CreateConfiguration(IApplicationBuilder builder)
|
||||
{
|
||||
var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider));
|
||||
|
||||
var config = configProvider.Get();
|
||||
|
||||
if(config == null)
|
||||
{
|
||||
throw new Exception("Unable to start Ocelot: configuration was null");
|
||||
}
|
||||
}
|
||||
|
||||
private static void UseIfNotNull(this IApplicationBuilder builder, Func<HttpContext, Func<Task>, Task> middleware)
|
||||
{
|
||||
if (middleware != null)
|
||||
|
@ -24,7 +24,7 @@ namespace Ocelot.Responder
|
||||
_removeOutputHeaders = removeOutputHeaders;
|
||||
}
|
||||
|
||||
public async Task<Response> SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response)
|
||||
public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response)
|
||||
{
|
||||
_removeOutputHeaders.Remove(response.Headers);
|
||||
|
||||
@ -56,7 +56,6 @@ namespace Ocelot.Responder
|
||||
{
|
||||
await stream.CopyToAsync(context.Response.Body);
|
||||
}
|
||||
return new OkResponse();
|
||||
}
|
||||
|
||||
private static void AddHeaderIfDoesntExist(HttpContext context, KeyValuePair<string, IEnumerable<string>> httpResponseHeader)
|
||||
@ -67,14 +66,13 @@ namespace Ocelot.Responder
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Response> SetErrorResponseOnContext(HttpContext context, int statusCode)
|
||||
public void SetErrorResponseOnContext(HttpContext context, int statusCode)
|
||||
{
|
||||
context.Response.OnStarting(x =>
|
||||
{
|
||||
context.Response.StatusCode = statusCode;
|
||||
return Task.CompletedTask;
|
||||
}, context);
|
||||
return new OkResponse();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,12 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Responder
|
||||
{
|
||||
public interface IHttpResponder
|
||||
{
|
||||
Task<Response> SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response);
|
||||
Task<Response> SetErrorResponseOnContext(HttpContext context, int statusCode);
|
||||
Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response);
|
||||
void SetErrorResponseOnContext(HttpContext context, int statusCode);
|
||||
}
|
||||
}
|
||||
|
@ -46,34 +46,27 @@ namespace Ocelot.Responder.Middleware
|
||||
|
||||
_logger.LogDebug("received errors setting error response");
|
||||
|
||||
await SetErrorResponse(context, errors);
|
||||
SetErrorResponse(context, errors);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("no pipeline error, setting response");
|
||||
|
||||
var setResponse = await _responder.SetResponseOnHttpContext(context, HttpResponseMessage);
|
||||
|
||||
if (setResponse.IsError)
|
||||
{
|
||||
_logger.LogDebug("error setting response, returning error to client");
|
||||
|
||||
await SetErrorResponse(context, setResponse.Errors);
|
||||
}
|
||||
await _responder.SetResponseOnHttpContext(context, HttpResponseMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetErrorResponse(HttpContext context, List<Error> errors)
|
||||
private void SetErrorResponse(HttpContext context, List<Error> errors)
|
||||
{
|
||||
var statusCode = _codeMapper.Map(errors);
|
||||
|
||||
if (!statusCode.IsError)
|
||||
{
|
||||
await _responder.SetErrorResponseOnContext(context, statusCode.Data);
|
||||
_responder.SetErrorResponseOnContext(context, statusCode.Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _responder.SetErrorResponseOnContext(context, 500);
|
||||
_responder.SetErrorResponseOnContext(context, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs
Normal file
21
src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
public class ConfigurationServiceProvider : IServiceDiscoveryProvider
|
||||
{
|
||||
private readonly List<Service> _services;
|
||||
|
||||
public ConfigurationServiceProvider(List<Service> services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task<List<Service>> Get()
|
||||
{
|
||||
return await Task.FromResult(_services);
|
||||
}
|
||||
}
|
||||
}
|
16
src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs
Normal file
16
src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
public class ConsulRegistryConfiguration
|
||||
{
|
||||
public ConsulRegistryConfiguration(string hostName, int port, string serviceName)
|
||||
{
|
||||
HostName = hostName;
|
||||
Port = port;
|
||||
ServiceName = serviceName;
|
||||
}
|
||||
|
||||
public string ServiceName { get; private set; }
|
||||
public string HostName { get; private set; }
|
||||
public int Port { get; private set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Consul;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider
|
||||
{
|
||||
private readonly ConsulRegistryConfiguration _configuration;
|
||||
private readonly ConsulClient _consul;
|
||||
private const string VersionPrefix = "version-";
|
||||
|
||||
public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration consulRegistryConfiguration)
|
||||
{
|
||||
var consulHost = string.IsNullOrEmpty(consulRegistryConfiguration?.HostName) ? "localhost" : consulRegistryConfiguration.HostName;
|
||||
var consulPort = consulRegistryConfiguration?.Port ?? 8500;
|
||||
_configuration = new ConsulRegistryConfiguration(consulHost, consulPort, consulRegistryConfiguration?.ServiceName);
|
||||
|
||||
_consul = new ConsulClient(config =>
|
||||
{
|
||||
config.Address = new Uri($"http://{_configuration.HostName}:{_configuration.Port}");
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<List<Service>> Get()
|
||||
{
|
||||
var queryResult = await _consul.Health.Service(_configuration.ServiceName, string.Empty, true);
|
||||
|
||||
var services = queryResult.Response.Select(BuildService);
|
||||
|
||||
return services.ToList();
|
||||
}
|
||||
|
||||
private Service BuildService(ServiceEntry serviceEntry)
|
||||
{
|
||||
return new Service(
|
||||
serviceEntry.Service.Service,
|
||||
new HostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port),
|
||||
serviceEntry.Service.ID,
|
||||
GetVersionFromStrings(serviceEntry.Service.Tags),
|
||||
serviceEntry.Service.Tags ?? Enumerable.Empty<string>());
|
||||
}
|
||||
|
||||
private string GetVersionFromStrings(IEnumerable<string> strings)
|
||||
{
|
||||
return strings
|
||||
?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal))
|
||||
.TrimStart(VersionPrefix);
|
||||
}
|
||||
}
|
||||
}
|
11
src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs
Normal file
11
src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
public interface IServiceDiscoveryProvider
|
||||
{
|
||||
Task<List<Service>> Get();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using Ocelot.Configuration;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
public interface IServiceDiscoveryProviderFactory
|
||||
{
|
||||
IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory
|
||||
{
|
||||
public IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig)
|
||||
{
|
||||
if (serviceConfig.UseServiceDiscovery)
|
||||
{
|
||||
return GetServiceDiscoveryProvider(serviceConfig.ServiceName, serviceConfig.ServiceDiscoveryProvider, serviceConfig.ServiceProviderHost, serviceConfig.ServiceProviderPort);
|
||||
}
|
||||
|
||||
var services = new List<Service>()
|
||||
{
|
||||
new Service(serviceConfig.ServiceName,
|
||||
new HostAndPort(serviceConfig.DownstreamHost, serviceConfig.DownstreamPort),
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
new string[0])
|
||||
};
|
||||
|
||||
return new ConfigurationServiceProvider(services);
|
||||
}
|
||||
|
||||
private IServiceDiscoveryProvider GetServiceDiscoveryProvider(string serviceName, string serviceProviderName, string providerHostName, int providerPort)
|
||||
{
|
||||
var consulRegistryConfiguration = new ConsulRegistryConfiguration(providerHostName, providerPort, serviceName);
|
||||
return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using Ocelot.Errors;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
public class UnableToFindServiceDiscoveryProviderError : Error
|
||||
{
|
||||
public UnableToFindServiceDiscoveryProviderError(string message)
|
||||
: base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
{
|
||||
public HostAndPort(string downstreamHost, int downstreamPort)
|
||||
{
|
||||
DownstreamHost = downstreamHost;
|
||||
DownstreamHost = downstreamHost?.Trim('/');
|
||||
DownstreamPort = downstreamPort;
|
||||
}
|
||||
|
||||
|
29
src/Ocelot/Values/Service.cs
Normal file
29
src/Ocelot/Values/Service.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ocelot.Values
|
||||
{
|
||||
public class Service
|
||||
{
|
||||
public Service(string name,
|
||||
HostAndPort hostAndPort,
|
||||
string id,
|
||||
string version,
|
||||
IEnumerable<string> tags)
|
||||
{
|
||||
Name = name;
|
||||
HostAndPort = hostAndPort;
|
||||
Id = id;
|
||||
Version = version;
|
||||
Tags = tags;
|
||||
}
|
||||
public string Id { get; private set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public string Version { get; private set; }
|
||||
|
||||
public IEnumerable<string> Tags { get; private set; }
|
||||
|
||||
public HostAndPort HostAndPort { get; private set; }
|
||||
}
|
||||
}
|
@ -27,7 +27,8 @@
|
||||
"Microsoft.NETCore.App": "1.1.0",
|
||||
"CacheManager.Core": "0.9.2",
|
||||
"CacheManager.Microsoft.Extensions.Configuration": "0.9.2",
|
||||
"CacheManager.Microsoft.Extensions.Logging": "0.9.2"
|
||||
"CacheManager.Microsoft.Extensions.Logging": "0.9.2",
|
||||
"Consul": "0.7.2.1"
|
||||
},
|
||||
"runtimes": {
|
||||
"win10-x64": {},
|
||||
@ -35,7 +36,7 @@
|
||||
"win7-x64": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"netcoreapp1.4": {
|
||||
"netcoreapp1.1": {
|
||||
"imports": [
|
||||
]
|
||||
}
|
||||
|
BIN
test/.DS_Store
vendored
Normal file
BIN
test/.DS_Store
vendored
Normal file
Binary file not shown.
220
test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs
Normal file
220
test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs
Normal file
@ -0,0 +1,220 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Consul;
|
||||
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 ServiceDiscoveryTests : IDisposable
|
||||
{
|
||||
private IWebHost _builderOne;
|
||||
private IWebHost _builderTwo;
|
||||
private IWebHost _fakeConsulBuilder;
|
||||
private readonly Steps _steps;
|
||||
private readonly List<ServiceEntry> _serviceEntries;
|
||||
private int _counterOne;
|
||||
private int _counterTwo;
|
||||
private static readonly object _syncLock = new object();
|
||||
|
||||
public ServiceDiscoveryTests()
|
||||
{
|
||||
_steps = new Steps();
|
||||
_serviceEntries = new List<ServiceEntry>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_use_service_discovery_and_load_balance_request()
|
||||
{
|
||||
var serviceName = "product";
|
||||
var downstreamServiceOneUrl = "http://localhost:50879";
|
||||
var downstreamServiceTwoUrl = "http://localhost:50880";
|
||||
var fakeConsulServiceDiscoveryUrl = "http://localhost:8500";
|
||||
var serviceEntryOne = new ServiceEntry()
|
||||
{
|
||||
Service = new AgentService()
|
||||
{
|
||||
Service = serviceName,
|
||||
Address = "localhost",
|
||||
Port = 50879,
|
||||
ID = Guid.NewGuid().ToString(),
|
||||
Tags = new string[0]
|
||||
},
|
||||
};
|
||||
var serviceEntryTwo = new ServiceEntry()
|
||||
{
|
||||
Service = new AgentService()
|
||||
{
|
||||
Service = serviceName,
|
||||
Address = "localhost",
|
||||
Port = 50880,
|
||||
ID = Guid.NewGuid().ToString(),
|
||||
Tags = new string[0]
|
||||
},
|
||||
};
|
||||
|
||||
var configuration = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamPathTemplate = "/",
|
||||
DownstreamScheme = "http",
|
||||
UpstreamTemplate = "/",
|
||||
UpstreamHttpMethod = "Get",
|
||||
ServiceName = serviceName,
|
||||
LoadBalancer = "LeastConnection",
|
||||
}
|
||||
},
|
||||
GlobalConfiguration = new FileGlobalConfiguration()
|
||||
{
|
||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
||||
{
|
||||
Provider = "Consul",
|
||||
Host = "localhost",
|
||||
Port = 8500
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
|
||||
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
|
||||
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
|
||||
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
|
||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||
.And(x => _steps.GivenOcelotIsRunning())
|
||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50))
|
||||
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50))
|
||||
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void ThenBothServicesCalledRealisticAmountOfTimes()
|
||||
{
|
||||
_counterOne.ShouldBe(25);
|
||||
_counterTwo.ShouldBe(25);
|
||||
}
|
||||
|
||||
private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected)
|
||||
{
|
||||
var total = _counterOne + _counterTwo;
|
||||
total.ShouldBe(expected);
|
||||
}
|
||||
|
||||
private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries)
|
||||
{
|
||||
foreach(var serviceEntry in serviceEntries)
|
||||
{
|
||||
_serviceEntries.Add(serviceEntry);
|
||||
}
|
||||
}
|
||||
|
||||
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url)
|
||||
{
|
||||
_fakeConsulBuilder = new WebHostBuilder()
|
||||
.UseUrls(url)
|
||||
.UseKestrel()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseIISIntegration()
|
||||
.UseUrls(url)
|
||||
.Configure(app =>
|
||||
{
|
||||
app.Run(async context =>
|
||||
{
|
||||
if(context.Request.Path.Value == "/v1/health/service/product")
|
||||
{
|
||||
await context.Response.WriteJsonAsync(_serviceEntries);
|
||||
}
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
|
||||
_fakeConsulBuilder.Start();
|
||||
}
|
||||
|
||||
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 (System.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();
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CacheManager.Core;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
@ -29,6 +31,12 @@ namespace Ocelot.AcceptanceTests
|
||||
private BearerToken _token;
|
||||
public HttpClient OcelotClient => _ocelotClient;
|
||||
public string RequestIdKey = "OcRequestId";
|
||||
private Random _random;
|
||||
|
||||
public Steps()
|
||||
{
|
||||
_random = new Random();
|
||||
}
|
||||
|
||||
public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
|
||||
{
|
||||
@ -153,6 +161,28 @@ namespace Ocelot.AcceptanceTests
|
||||
_response = _ocelotClient.GetAsync(url).Result;
|
||||
}
|
||||
|
||||
public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times)
|
||||
{
|
||||
var tasks = new Task[times];
|
||||
|
||||
for (int i = 0; i < times; i++)
|
||||
{
|
||||
var urlCopy = url;
|
||||
tasks[i] = GetForServiceDiscoveryTest(urlCopy);
|
||||
Thread.Sleep(_random.Next(40,60));
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks);
|
||||
}
|
||||
|
||||
private async Task GetForServiceDiscoveryTest(string url)
|
||||
{
|
||||
var response = await _ocelotClient.GetAsync(url);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
int count = int.Parse(content);
|
||||
count.ShouldBeGreaterThan(0);
|
||||
}
|
||||
|
||||
public void WhenIGetUrlOnTheApiGateway(string url, string requestId)
|
||||
{
|
||||
_ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId);
|
||||
|
@ -4,15 +4,13 @@
|
||||
|
||||
public static class TestConfiguration
|
||||
{
|
||||
public static double Version => 1.4;
|
||||
public static double Version => 1.1;
|
||||
public static string ConfigurationPath => GetConfigurationPath();
|
||||
|
||||
public static string GetConfigurationPath()
|
||||
{
|
||||
var osArchitecture = RuntimeInformation.OSArchitecture.ToString();
|
||||
|
||||
var oSDescription = string.Empty;
|
||||
|
||||
if(RuntimeInformation.OSDescription.ToLower().Contains("darwin"))
|
||||
{
|
||||
return FormatConfigurationPath("osx.10.11", osArchitecture);
|
||||
|
@ -1 +1 @@
|
||||
{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Address":null}}}
|
||||
{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}}
|
||||
|
@ -32,7 +32,8 @@
|
||||
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
|
||||
"Microsoft.NETCore.App": "1.1.0",
|
||||
"Shouldly": "2.8.2",
|
||||
"TestStack.BDDfy": "4.3.2"
|
||||
"TestStack.BDDfy": "4.3.2",
|
||||
"Consul": "0.7.2.1"
|
||||
},
|
||||
"runtimes": {
|
||||
"win10-x64": {},
|
||||
@ -40,7 +41,7 @@
|
||||
"win7-x64": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"netcoreapp1.4": {
|
||||
"netcoreapp1.1": {
|
||||
"imports": [
|
||||
]
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
"dependencies": {
|
||||
"Ocelot": "0.0.0-dev",
|
||||
"BenchmarkDotNet": "0.10.1"
|
||||
"BenchmarkDotNet": "0.10.2"
|
||||
},
|
||||
"runtimes": {
|
||||
"win10-x64": {},
|
||||
@ -14,7 +14,7 @@
|
||||
"win7-x64": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"netcoreapp1.4": {
|
||||
"netcoreapp1.1": {
|
||||
"imports": [
|
||||
]
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
"win7-x64": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"netcoreapp1.4": {
|
||||
"netcoreapp1.1": {
|
||||
"imports": [
|
||||
]
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using Ocelot.Configuration.Creator;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Parser;
|
||||
using Ocelot.Configuration.Validator;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Responses;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
@ -24,6 +25,9 @@ namespace Ocelot.UnitTests.Configuration
|
||||
private readonly Mock<IClaimToThingConfigurationParser> _configParser;
|
||||
private readonly Mock<ILogger<FileOcelotConfigurationCreator>> _logger;
|
||||
private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator;
|
||||
private readonly Mock<ILoadBalancerFactory> _loadBalancerFactory;
|
||||
private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse;
|
||||
private readonly Mock<ILoadBalancer> _loadBalancer;
|
||||
|
||||
public FileConfigurationCreatorTests()
|
||||
{
|
||||
@ -31,8 +35,37 @@ namespace Ocelot.UnitTests.Configuration
|
||||
_configParser = new Mock<IClaimToThingConfigurationParser>();
|
||||
_validator = new Mock<IConfigurationValidator>();
|
||||
_fileConfig = new Mock<IOptions<FileConfiguration>>();
|
||||
_loadBalancerFactory = new Mock<ILoadBalancerFactory>();
|
||||
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
|
||||
_loadBalancer = new Mock<ILoadBalancer>();
|
||||
_ocelotConfigurationCreator = new FileOcelotConfigurationCreator(
|
||||
_fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object);
|
||||
_fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object,
|
||||
_loadBalancerFactory.Object, _loadBalancerHouse.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_create_load_balancer()
|
||||
{
|
||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamHost = "127.0.0.1",
|
||||
UpstreamTemplate = "/api/products/{productId}",
|
||||
DownstreamPathTemplate = "/products/{productId}",
|
||||
UpstreamHttpMethod = "Get",
|
||||
}
|
||||
},
|
||||
}))
|
||||
.And(x => x.GivenTheConfigIsValid())
|
||||
.And(x => x.GivenTheLoadBalancerFactoryReturns())
|
||||
.When(x => x.WhenICreateTheConfig())
|
||||
.Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly())
|
||||
.And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly())
|
||||
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -66,6 +99,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_use_downstream_scheme()
|
||||
{
|
||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||
@ -117,7 +151,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
|
||||
{
|
||||
Provider = "consul",
|
||||
Address = "127.0.0.1"
|
||||
Host = "127.0.0.1"
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -376,6 +410,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
}))
|
||||
.And(x => x.GivenTheConfigIsValid())
|
||||
.And(x => x.GivenTheConfigHeaderExtractorReturns(new ClaimToThing("CustomerId", "CustomerId", "", 0)))
|
||||
.And(x => x.GivenTheLoadBalancerFactoryReturns())
|
||||
.When(x => x.WhenICreateTheConfig())
|
||||
.Then(x => x.ThenTheReRoutesAre(expected))
|
||||
.And(x => x.ThenTheAuthenticationOptionsAre(expected))
|
||||
@ -430,6 +465,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
}
|
||||
}))
|
||||
.And(x => x.GivenTheConfigIsValid())
|
||||
.And(x => x.GivenTheLoadBalancerFactoryReturns())
|
||||
.When(x => x.WhenICreateTheConfig())
|
||||
.Then(x => x.ThenTheReRoutesAre(expected))
|
||||
.And(x => x.ThenTheAuthenticationOptionsAre(expected))
|
||||
@ -543,7 +579,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
|
||||
private void WhenICreateTheConfig()
|
||||
{
|
||||
_config = _ocelotConfigurationCreator.Create();
|
||||
_config = _ocelotConfigurationCreator.Create().Result;
|
||||
}
|
||||
|
||||
private void ThenTheReRoutesAre(List<ReRoute> expectedReRoutes)
|
||||
@ -576,5 +612,24 @@ namespace Ocelot.UnitTests.Configuration
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void GivenTheLoadBalancerFactoryReturns()
|
||||
{
|
||||
_loadBalancerFactory
|
||||
.Setup(x => x.Get(It.IsAny<ReRoute>()))
|
||||
.ReturnsAsync(_loadBalancer.Object);
|
||||
}
|
||||
|
||||
private void TheLoadBalancerFactoryIsCalledCorrectly()
|
||||
{
|
||||
_loadBalancerFactory
|
||||
.Verify(x => x.Get(It.IsAny<ReRoute>()), Times.Once);
|
||||
}
|
||||
|
||||
private void ThenTheLoadBalancerHouseIsCalledCorrectly()
|
||||
{
|
||||
_loadBalancerHouse
|
||||
.Verify(x => x.Add(It.IsAny<string>(), _loadBalancer.Object), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
{
|
||||
_creator
|
||||
.Setup(x => x.Create())
|
||||
.Returns(config);
|
||||
.ReturnsAsync(config);
|
||||
}
|
||||
|
||||
private void GivenTheRepoReturns(Response<IOcelotConfiguration> config)
|
||||
@ -93,7 +93,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
|
||||
private void WhenIGetTheConfig()
|
||||
{
|
||||
_result = _ocelotConfigurationProvider.Get();
|
||||
_result = _ocelotConfigurationProvider.Get().Result;
|
||||
}
|
||||
|
||||
private void TheFollowingIsReturned(Response<IOcelotConfiguration> expected)
|
||||
|
@ -84,7 +84,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
||||
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
|
||||
_downstreamRouteFinder
|
||||
.Setup(x => x.FindDownstreamRoute(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Returns(_downstreamRoute);
|
||||
.ReturnsAsync(_downstreamRoute);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -159,7 +159,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
||||
_reRoutesConfig = reRoutesConfig;
|
||||
_mockConfig
|
||||
.Setup(x => x.Get())
|
||||
.Returns(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(_reRoutesConfig)));
|
||||
.ReturnsAsync(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(_reRoutesConfig)));
|
||||
}
|
||||
|
||||
private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath)
|
||||
@ -169,7 +169,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
||||
|
||||
private void WhenICallTheFinder()
|
||||
{
|
||||
_result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod);
|
||||
_result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod).Result;
|
||||
}
|
||||
|
||||
private void ThenTheFollowingIsReturned(DownstreamRoute expected)
|
||||
|
@ -36,6 +36,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
|
||||
private HttpResponseMessage _result;
|
||||
private OkResponse<DownstreamPath> _downstreamPath;
|
||||
private OkResponse<DownstreamUrl> _downstreamUrl;
|
||||
private HostAndPort _hostAndPort;
|
||||
|
||||
public DownstreamUrlCreatorMiddlewareTests()
|
||||
{
|
||||
@ -69,14 +70,25 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
|
||||
[Fact]
|
||||
public void should_call_dependencies_correctly()
|
||||
{
|
||||
var hostAndPort = new HostAndPort("127.0.0.1", 80);
|
||||
|
||||
this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build())))
|
||||
.And(x => x.GivenTheHostAndPortIs(hostAndPort))
|
||||
.And(x => x.TheUrlReplacerReturns("/api/products/1"))
|
||||
.And(x => x.TheUrlBuilderReturns("http://www.bbc.co.uk/api/products/1"))
|
||||
.And(x => x.TheUrlBuilderReturns("http://127.0.0.1:80/api/products/1"))
|
||||
.When(x => x.WhenICallTheMiddleware())
|
||||
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenTheHostAndPortIs(HostAndPort hostAndPort)
|
||||
{
|
||||
_hostAndPort = hostAndPort;
|
||||
_scopedRepository
|
||||
.Setup(x => x.Get<HostAndPort>("HostAndPort"))
|
||||
.Returns(new OkResponse<HostAndPort>(_hostAndPort));
|
||||
}
|
||||
|
||||
private void TheUrlBuilderReturns(string dsUrl)
|
||||
{
|
||||
_downstreamUrl = new OkResponse<DownstreamUrl>(new DownstreamUrl(dsUrl));
|
||||
|
238
test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs
Normal file
238
test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs
Normal file
@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
public class LeastConnectionTests
|
||||
{
|
||||
private HostAndPort _hostAndPort;
|
||||
private Response<HostAndPort> _result;
|
||||
private LeastConnectionLoadBalancer _leastConnection;
|
||||
private List<Service> _services;
|
||||
private Random _random;
|
||||
|
||||
public LeastConnectionTests()
|
||||
{
|
||||
_random = new Random();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_be_able_to_lease_and_release_concurrently()
|
||||
{
|
||||
var serviceName = "products";
|
||||
|
||||
var availableServices = new List<Service>
|
||||
{
|
||||
new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
_services = availableServices;
|
||||
_leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName);
|
||||
|
||||
var tasks = new Task[100];
|
||||
|
||||
for(var i = 0; i < tasks.Length; i++)
|
||||
{
|
||||
tasks[i] = LeaseDelayAndRelease();
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks);
|
||||
}
|
||||
|
||||
private async Task LeaseDelayAndRelease()
|
||||
{
|
||||
var hostAndPort = await _leastConnection.Lease();
|
||||
await Task.Delay(_random.Next(1, 100));
|
||||
_leastConnection.Release(hostAndPort.Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_get_next_url()
|
||||
{
|
||||
var serviceName = "products";
|
||||
|
||||
var hostAndPort = new HostAndPort("localhost", 80);
|
||||
|
||||
var availableServices = new List<Service>
|
||||
{
|
||||
new Service(serviceName, hostAndPort, string.Empty, string.Empty, new string[0])
|
||||
};
|
||||
|
||||
this.Given(x => x.GivenAHostAndPort(hostAndPort))
|
||||
.And(x => x.GivenTheLoadBalancerStarts(availableServices, serviceName))
|
||||
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||
.Then(x => x.ThenTheNextHostAndPortIsReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_serve_from_service_with_least_connections()
|
||||
{
|
||||
var serviceName = "products";
|
||||
|
||||
var availableServices = new List<Service>
|
||||
{
|
||||
new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new HostAndPort("127.0.0.3", 80), string.Empty, string.Empty, new string[0])
|
||||
};
|
||||
|
||||
_services = availableServices;
|
||||
_leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName);
|
||||
|
||||
var response = _leastConnection.Lease().Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease().Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease().Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[2].HostAndPort.DownstreamHost);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_build_connections_per_service()
|
||||
{
|
||||
var serviceName = "products";
|
||||
|
||||
var availableServices = new List<Service>
|
||||
{
|
||||
new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
_services = availableServices;
|
||||
_leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName);
|
||||
|
||||
var response = _leastConnection.Lease().Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease().Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease().Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease().Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_release_connection()
|
||||
{
|
||||
var serviceName = "products";
|
||||
|
||||
var availableServices = new List<Service>
|
||||
{
|
||||
new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
_services = availableServices;
|
||||
_leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName);
|
||||
|
||||
var response = _leastConnection.Lease().Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease().Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease().Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease().Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||
|
||||
//release this so 2 should have 1 connection and we should get 2 back as our next host and port
|
||||
_leastConnection.Release(availableServices[1].HostAndPort);
|
||||
|
||||
response = _leastConnection.Lease().Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_error_if_services_are_null()
|
||||
{
|
||||
var serviceName = "products";
|
||||
|
||||
var hostAndPort = new HostAndPort("localhost", 80);
|
||||
this.Given(x => x.GivenAHostAndPort(hostAndPort))
|
||||
.And(x => x.GivenTheLoadBalancerStarts(null, serviceName))
|
||||
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||
.Then(x => x.ThenServiceAreNullErrorIsReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_error_if_services_are_empty()
|
||||
{
|
||||
var serviceName = "products";
|
||||
|
||||
var hostAndPort = new HostAndPort("localhost", 80);
|
||||
this.Given(x => x.GivenAHostAndPort(hostAndPort))
|
||||
.And(x => x.GivenTheLoadBalancerStarts(new List<Service>(), serviceName))
|
||||
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||
.Then(x => x.ThenServiceAreEmptyErrorIsReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void ThenServiceAreNullErrorIsReturned()
|
||||
{
|
||||
_result.IsError.ShouldBeTrue();
|
||||
_result.Errors[0].ShouldBeOfType<ServicesAreNullError>();
|
||||
}
|
||||
|
||||
private void ThenServiceAreEmptyErrorIsReturned()
|
||||
{
|
||||
_result.IsError.ShouldBeTrue();
|
||||
_result.Errors[0].ShouldBeOfType<ServicesAreEmptyError>();
|
||||
}
|
||||
|
||||
private void GivenTheLoadBalancerStarts(List<Service> services, string serviceName)
|
||||
{
|
||||
_services = services;
|
||||
_leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName);
|
||||
}
|
||||
|
||||
private void WhenTheLoadBalancerStarts(List<Service> services, string serviceName)
|
||||
{
|
||||
GivenTheLoadBalancerStarts(services, serviceName);
|
||||
}
|
||||
|
||||
private void GivenAHostAndPort(HostAndPort hostAndPort)
|
||||
{
|
||||
_hostAndPort = hostAndPort;
|
||||
}
|
||||
|
||||
private void WhenIGetTheNextHostAndPort()
|
||||
{
|
||||
_result = _leastConnection.Lease().Result;
|
||||
}
|
||||
|
||||
private void ThenTheNextHostAndPortIsReturned()
|
||||
{
|
||||
_result.Data.DownstreamHost.ShouldBe(_hostAndPort.DownstreamHost);
|
||||
_result.Data.DownstreamPort.ShouldBe(_hostAndPort.DownstreamPort);
|
||||
}
|
||||
}
|
||||
}
|
110
test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs
Normal file
110
test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs
Normal file
@ -0,0 +1,110 @@
|
||||
using Moq;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
public class LoadBalancerFactoryTests
|
||||
{
|
||||
private ReRoute _reRoute;
|
||||
private LoadBalancerFactory _factory;
|
||||
private ILoadBalancer _result;
|
||||
private Mock<IServiceDiscoveryProviderFactory> _serviceProviderFactory;
|
||||
private Mock<IServiceDiscoveryProvider> _serviceProvider;
|
||||
|
||||
public LoadBalancerFactoryTests()
|
||||
{
|
||||
_serviceProviderFactory = new Mock<IServiceDiscoveryProviderFactory>();
|
||||
_serviceProvider = new Mock<IServiceDiscoveryProvider>();
|
||||
_factory = new LoadBalancerFactory(_serviceProviderFactory.Object);
|
||||
}
|
||||
|
||||
private void GivenTheServiceProviderFactoryReturns()
|
||||
{
|
||||
_serviceProviderFactory
|
||||
.Setup(x => x.Get(It.IsAny<ServiceProviderConfiguraion>()))
|
||||
.Returns(_serviceProvider.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_no_load_balancer()
|
||||
{
|
||||
var reRoute = new ReRouteBuilder()
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenAReRoute(reRoute))
|
||||
.And(x => x.GivenTheServiceProviderFactoryReturns())
|
||||
.When(x => x.WhenIGetTheLoadBalancer())
|
||||
.Then(x => x.ThenTheLoadBalancerIsReturned<NoLoadBalancer>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_round_robin_load_balancer()
|
||||
{
|
||||
var reRoute = new ReRouteBuilder()
|
||||
.WithLoadBalancer("RoundRobin")
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenAReRoute(reRoute))
|
||||
.And(x => x.GivenTheServiceProviderFactoryReturns())
|
||||
.When(x => x.WhenIGetTheLoadBalancer())
|
||||
.Then(x => x.ThenTheLoadBalancerIsReturned<RoundRobinLoadBalancer>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_round_least_connection_balancer()
|
||||
{
|
||||
var reRoute = new ReRouteBuilder()
|
||||
.WithLoadBalancer("LeastConnection")
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenAReRoute(reRoute))
|
||||
.And(x => x.GivenTheServiceProviderFactoryReturns())
|
||||
.When(x => x.WhenIGetTheLoadBalancer())
|
||||
.Then(x => x.ThenTheLoadBalancerIsReturned<LeastConnectionLoadBalancer>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_call_service_provider()
|
||||
{
|
||||
var reRoute = new ReRouteBuilder()
|
||||
.WithLoadBalancer("RoundRobin")
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenAReRoute(reRoute))
|
||||
.And(x => x.GivenTheServiceProviderFactoryReturns())
|
||||
.When(x => x.WhenIGetTheLoadBalancer())
|
||||
.Then(x => x.ThenTheServiceProviderIsCalledCorrectly())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void ThenTheServiceProviderIsCalledCorrectly()
|
||||
{
|
||||
_serviceProviderFactory
|
||||
.Verify(x => x.Get(It.IsAny<ServiceProviderConfiguraion>()), Times.Once);
|
||||
}
|
||||
|
||||
private void GivenAReRoute(ReRoute reRoute)
|
||||
{
|
||||
_reRoute = reRoute;
|
||||
}
|
||||
|
||||
private void WhenIGetTheLoadBalancer()
|
||||
{
|
||||
_result = _factory.Get(_reRoute).Result;
|
||||
}
|
||||
|
||||
private void ThenTheLoadBalancerIsReturned<T>()
|
||||
{
|
||||
_result.ShouldBeOfType<T>();
|
||||
}
|
||||
}
|
||||
}
|
136
test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs
Normal file
136
test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs
Normal file
@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
public class LoadBalancerHouseTests
|
||||
{
|
||||
private ILoadBalancer _loadBalancer;
|
||||
private readonly LoadBalancerHouse _loadBalancerHouse;
|
||||
private Response _addResult;
|
||||
private Response<ILoadBalancer> _getResult;
|
||||
private string _key;
|
||||
|
||||
public LoadBalancerHouseTests()
|
||||
{
|
||||
_loadBalancerHouse = new LoadBalancerHouse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_store_load_balancer()
|
||||
{
|
||||
var key = "test";
|
||||
|
||||
this.Given(x => x.GivenThereIsALoadBalancer(key, new FakeLoadBalancer()))
|
||||
.When(x => x.WhenIAddTheLoadBalancer())
|
||||
.Then(x => x.ThenItIsAdded())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_get_load_balancer()
|
||||
{
|
||||
var key = "test";
|
||||
|
||||
this.Given(x => x.GivenThereIsALoadBalancer(key, new FakeLoadBalancer()))
|
||||
.When(x => x.WhenWeGetTheLoadBalancer(key))
|
||||
.Then(x => x.ThenItIsReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_store_load_balancers_by_key()
|
||||
{
|
||||
var key = "test";
|
||||
var keyTwo = "testTwo";
|
||||
|
||||
this.Given(x => x.GivenThereIsALoadBalancer(key, new FakeLoadBalancer()))
|
||||
.And(x => x.GivenThereIsALoadBalancer(keyTwo, new FakeRoundRobinLoadBalancer()))
|
||||
.When(x => x.WhenWeGetTheLoadBalancer(key))
|
||||
.Then(x => x.ThenTheLoadBalancerIs<FakeLoadBalancer>())
|
||||
.When(x => x.WhenWeGetTheLoadBalancer(keyTwo))
|
||||
.Then(x => x.ThenTheLoadBalancerIs<FakeRoundRobinLoadBalancer>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_error_if_no_load_balancer_with_key()
|
||||
{
|
||||
this.When(x => x.WhenWeGetTheLoadBalancer("test"))
|
||||
.Then(x => x.ThenAnErrorIsReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void ThenAnErrorIsReturned()
|
||||
{
|
||||
_getResult.IsError.ShouldBeTrue();
|
||||
_getResult.Errors[0].ShouldBeOfType<UnableToFindLoadBalancerError>();
|
||||
}
|
||||
|
||||
private void ThenTheLoadBalancerIs<T>()
|
||||
{
|
||||
_getResult.Data.ShouldBeOfType<T>();
|
||||
}
|
||||
|
||||
private void ThenItIsAdded()
|
||||
{
|
||||
_addResult.IsError.ShouldBe(false);
|
||||
_addResult.ShouldBeOfType<OkResponse>();
|
||||
}
|
||||
|
||||
private void WhenIAddTheLoadBalancer()
|
||||
{
|
||||
_addResult = _loadBalancerHouse.Add(_key, _loadBalancer);
|
||||
}
|
||||
|
||||
|
||||
private void GivenThereIsALoadBalancer(string key, ILoadBalancer loadBalancer)
|
||||
{
|
||||
_key = key;
|
||||
_loadBalancer = loadBalancer;
|
||||
WhenIAddTheLoadBalancer();
|
||||
}
|
||||
|
||||
private void WhenWeGetTheLoadBalancer(string key)
|
||||
{
|
||||
_getResult = _loadBalancerHouse.Get(key);
|
||||
}
|
||||
|
||||
private void ThenItIsReturned()
|
||||
{
|
||||
_getResult.Data.ShouldBe(_loadBalancer);
|
||||
}
|
||||
|
||||
class FakeLoadBalancer : ILoadBalancer
|
||||
{
|
||||
public Task<Response<HostAndPort>> Lease()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Release(HostAndPort hostAndPort)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
class FakeRoundRobinLoadBalancer : ILoadBalancer
|
||||
{
|
||||
public Task<Response<HostAndPort>> Lease()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Release(HostAndPort hostAndPort)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.DownstreamRouteFinder;
|
||||
using Ocelot.Errors;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.LoadBalancer.Middleware;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
public class LoadBalancerMiddlewareTests
|
||||
{
|
||||
private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse;
|
||||
private readonly Mock<IRequestScopedDataRepository> _scopedRepository;
|
||||
private readonly Mock<ILoadBalancer> _loadBalancer;
|
||||
private readonly string _url;
|
||||
private readonly TestServer _server;
|
||||
private readonly HttpClient _client;
|
||||
private HttpResponseMessage _result;
|
||||
private HostAndPort _hostAndPort;
|
||||
private OkResponse<Ocelot.Request.Request> _request;
|
||||
private OkResponse<string> _downstreamUrl;
|
||||
private OkResponse<DownstreamRoute> _downstreamRoute;
|
||||
private ErrorResponse<ILoadBalancer> _getLoadBalancerHouseError;
|
||||
private ErrorResponse<HostAndPort> _getHostAndPortError;
|
||||
|
||||
public LoadBalancerMiddlewareTests()
|
||||
{
|
||||
_url = "http://localhost:51879";
|
||||
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
|
||||
_scopedRepository = new Mock<IRequestScopedDataRepository>();
|
||||
_loadBalancer = new Mock<ILoadBalancer>();
|
||||
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
|
||||
var builder = new WebHostBuilder()
|
||||
.ConfigureServices(x =>
|
||||
{
|
||||
x.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
|
||||
x.AddLogging();
|
||||
x.AddSingleton(_loadBalancerHouse.Object);
|
||||
x.AddSingleton(_scopedRepository.Object);
|
||||
})
|
||||
.UseUrls(_url)
|
||||
.UseKestrel()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseIISIntegration()
|
||||
.UseUrls(_url)
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseLoadBalancingMiddleware();
|
||||
});
|
||||
|
||||
_server = new TestServer(builder);
|
||||
_client = _server.CreateClient();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_call_scoped_data_repository_correctly()
|
||||
{
|
||||
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
|
||||
new ReRouteBuilder()
|
||||
.Build());
|
||||
|
||||
this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
|
||||
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
|
||||
.And(x => x.GivenTheLoadBalancerHouseReturns())
|
||||
.And(x => x.GivenTheLoadBalancerReturns())
|
||||
.When(x => x.WhenICallTheMiddleware())
|
||||
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_set_pipeline_error_if_cannot_get_load_balancer()
|
||||
{
|
||||
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
|
||||
new ReRouteBuilder()
|
||||
.Build());
|
||||
|
||||
this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
|
||||
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
|
||||
.And(x => x.GivenTheLoadBalancerHouseReturnsAnError())
|
||||
.When(x => x.WhenICallTheMiddleware())
|
||||
.Then(x => x.ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_set_pipeline_error_if_cannot_get_least()
|
||||
{
|
||||
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
|
||||
new ReRouteBuilder()
|
||||
.Build());
|
||||
|
||||
this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
|
||||
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
|
||||
.And(x => x.GivenTheLoadBalancerHouseReturns())
|
||||
.And(x => x.GivenTheLoadBalancerReturnsAnError())
|
||||
.When(x => x.WhenICallTheMiddleware())
|
||||
.Then(x => x.ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenTheLoadBalancerReturnsAnError()
|
||||
{
|
||||
_getHostAndPortError = new ErrorResponse<HostAndPort>(new List<Error>() { new ServicesAreNullError($"services were null for bah") });
|
||||
_loadBalancer
|
||||
.Setup(x => x.Lease())
|
||||
.ReturnsAsync(_getHostAndPortError);
|
||||
}
|
||||
|
||||
private void GivenTheLoadBalancerReturns()
|
||||
{
|
||||
_hostAndPort = new HostAndPort("127.0.0.1", 80);
|
||||
_loadBalancer
|
||||
.Setup(x => x.Lease())
|
||||
.ReturnsAsync(new OkResponse<HostAndPort>(_hostAndPort));
|
||||
}
|
||||
|
||||
private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute)
|
||||
{
|
||||
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
|
||||
_scopedRepository
|
||||
.Setup(x => x.Get<DownstreamRoute>(It.IsAny<string>()))
|
||||
.Returns(_downstreamRoute);
|
||||
}
|
||||
|
||||
private void GivenTheLoadBalancerHouseReturns()
|
||||
{
|
||||
_loadBalancerHouse
|
||||
.Setup(x => x.Get(It.IsAny<string>()))
|
||||
.Returns(new OkResponse<ILoadBalancer>(_loadBalancer.Object));
|
||||
}
|
||||
|
||||
|
||||
private void GivenTheLoadBalancerHouseReturnsAnError()
|
||||
{
|
||||
_getLoadBalancerHouseError = new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
|
||||
{
|
||||
new UnableToFindLoadBalancerError($"unabe to find load balancer for bah")
|
||||
});
|
||||
|
||||
_loadBalancerHouse
|
||||
.Setup(x => x.Get(It.IsAny<string>()))
|
||||
.Returns(_getLoadBalancerHouseError);
|
||||
}
|
||||
|
||||
private void ThenTheScopedDataRepositoryIsCalledCorrectly()
|
||||
{
|
||||
_scopedRepository
|
||||
.Verify(x => x.Add("HostAndPort", _hostAndPort), Times.Once());
|
||||
}
|
||||
|
||||
private void ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline()
|
||||
{
|
||||
_scopedRepository
|
||||
.Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once);
|
||||
|
||||
_scopedRepository
|
||||
.Verify(x => x.Add("OcelotMiddlewareErrors", _getLoadBalancerHouseError.Errors), Times.Once);
|
||||
}
|
||||
|
||||
private void ThenAnErrorSayingReleaseFailedIsSetOnThePipeline()
|
||||
{
|
||||
_scopedRepository
|
||||
.Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once);
|
||||
|
||||
_scopedRepository
|
||||
.Verify(x => x.Add("OcelotMiddlewareErrors", It.IsAny<List<Error>>()), Times.Once);
|
||||
}
|
||||
|
||||
private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline()
|
||||
{
|
||||
_scopedRepository
|
||||
.Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once);
|
||||
|
||||
_scopedRepository
|
||||
.Verify(x => x.Add("OcelotMiddlewareErrors", _getHostAndPortError.Errors), Times.Once);
|
||||
}
|
||||
|
||||
private void WhenICallTheMiddleware()
|
||||
{
|
||||
_result = _client.GetAsync(_url).Result;
|
||||
}
|
||||
|
||||
private void GivenTheDownStreamUrlIs(string downstreamUrl)
|
||||
{
|
||||
_downstreamUrl = new OkResponse<string>(downstreamUrl);
|
||||
_scopedRepository
|
||||
.Setup(x => x.Get<string>(It.IsAny<string>()))
|
||||
.Returns(_downstreamUrl);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_client.Dispose();
|
||||
_server.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
48
test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs
Normal file
48
test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
public class NoLoadBalancerTests
|
||||
{
|
||||
private List<Service> _services;
|
||||
private NoLoadBalancer _loadBalancer;
|
||||
private Response<HostAndPort> _result;
|
||||
|
||||
[Fact]
|
||||
public void should_return_host_and_port()
|
||||
{
|
||||
var hostAndPort = new HostAndPort("127.0.0.1", 80);
|
||||
|
||||
var services = new List<Service>
|
||||
{
|
||||
new Service("product", hostAndPort, string.Empty, string.Empty, new string[0])
|
||||
};
|
||||
this.Given(x => x.GivenServices(services))
|
||||
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||
.Then(x => x.ThenTheHostAndPortIs(hostAndPort))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenServices(List<Service> services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
private void WhenIGetTheNextHostAndPort()
|
||||
{
|
||||
_loadBalancer = new NoLoadBalancer(_services);
|
||||
_result = _loadBalancer.Lease().Result;
|
||||
}
|
||||
|
||||
private void ThenTheHostAndPortIs(HostAndPort expected)
|
||||
{
|
||||
_result.Data.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
}
|
68
test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs
Normal file
68
test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
public class RoundRobinTests
|
||||
{
|
||||
private readonly RoundRobinLoadBalancer _roundRobin;
|
||||
private readonly List<Service> _services;
|
||||
private Response<HostAndPort> _hostAndPort;
|
||||
|
||||
public RoundRobinTests()
|
||||
{
|
||||
_services = new List<Service>
|
||||
{
|
||||
new Service("product", new HostAndPort("127.0.0.1", 5000), string.Empty, string.Empty, new string[0]),
|
||||
new Service("product", new HostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0]),
|
||||
new Service("product", new HostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0])
|
||||
};
|
||||
|
||||
_roundRobin = new RoundRobinLoadBalancer(_services);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_get_next_address()
|
||||
{
|
||||
this.Given(x => x.GivenIGetTheNextAddress())
|
||||
.Then(x => x.ThenTheNextAddressIndexIs(0))
|
||||
.Given(x => x.GivenIGetTheNextAddress())
|
||||
.Then(x => x.ThenTheNextAddressIndexIs(1))
|
||||
.Given(x => x.GivenIGetTheNextAddress())
|
||||
.Then(x => x.ThenTheNextAddressIndexIs(2))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_go_back_to_first_address_after_finished_last()
|
||||
{
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
|
||||
while (stopWatch.ElapsedMilliseconds < 1000)
|
||||
{
|
||||
var address = _roundRobin.Lease().Result;
|
||||
address.Data.ShouldBe(_services[0].HostAndPort);
|
||||
address = _roundRobin.Lease().Result;
|
||||
address.Data.ShouldBe(_services[1].HostAndPort);
|
||||
address = _roundRobin.Lease().Result;
|
||||
address.Data.ShouldBe(_services[2].HostAndPort);
|
||||
}
|
||||
}
|
||||
|
||||
private void GivenIGetTheNextAddress()
|
||||
{
|
||||
_hostAndPort = _roundRobin.Lease().Result;
|
||||
}
|
||||
|
||||
private void ThenTheNextAddressIndexIs(int index)
|
||||
{
|
||||
_hostAndPort.Data.ShouldBe(_services[index].HostAndPort);
|
||||
}
|
||||
}
|
||||
}
|
@ -62,19 +62,11 @@ namespace Ocelot.UnitTests.Responder
|
||||
{
|
||||
this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage()))
|
||||
.And(x => x.GivenThereAreNoPipelineErrors())
|
||||
.And(x => x.GivenTheResponderReturns())
|
||||
.When(x => x.WhenICallTheMiddleware())
|
||||
.Then(x => x.ThenThereAreNoErrors())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenTheResponderReturns()
|
||||
{
|
||||
_responder
|
||||
.Setup(x => x.SetResponseOnHttpContext(It.IsAny<HttpContext>(), It.IsAny<HttpResponseMessage>()))
|
||||
.ReturnsAsync(new OkResponse());
|
||||
}
|
||||
|
||||
private void GivenThereAreNoPipelineErrors()
|
||||
{
|
||||
_scopedRepository
|
||||
|
@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
using Ocelot.Values;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.ServiceDiscovery
|
||||
{
|
||||
public class ConfigurationServiceProviderTests
|
||||
{
|
||||
private ConfigurationServiceProvider _serviceProvider;
|
||||
private HostAndPort _hostAndPort;
|
||||
private List<Service> _result;
|
||||
private List<Service> _expected;
|
||||
|
||||
[Fact]
|
||||
public void should_return_services()
|
||||
{
|
||||
var hostAndPort = new HostAndPort("127.0.0.1", 80);
|
||||
|
||||
var services = new List<Service>
|
||||
{
|
||||
new Service("product", hostAndPort, string.Empty, string.Empty, new string[0])
|
||||
};
|
||||
|
||||
this.Given(x => x.GivenServices(services))
|
||||
.When(x => x.WhenIGetTheService())
|
||||
.Then(x => x.ThenTheFollowingIsReturned(services))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenServices(List<Service> services)
|
||||
{
|
||||
_expected = services;
|
||||
}
|
||||
|
||||
private void WhenIGetTheService()
|
||||
{
|
||||
_serviceProvider = new ConfigurationServiceProvider(_expected);
|
||||
_result = _serviceProvider.Get().Result;
|
||||
}
|
||||
|
||||
private void ThenTheFollowingIsReturned(List<Service> services)
|
||||
{
|
||||
_result[0].HostAndPort.DownstreamHost.ShouldBe(services[0].HostAndPort.DownstreamHost);
|
||||
|
||||
_result[0].HostAndPort.DownstreamPort.ShouldBe(services[0].HostAndPort.DownstreamPort);
|
||||
|
||||
_result[0].Name.ShouldBe(services[0].Name);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.ServiceDiscovery
|
||||
{
|
||||
public class ServiceProviderFactoryTests
|
||||
{
|
||||
private ServiceProviderConfiguraion _serviceConfig;
|
||||
private IServiceDiscoveryProvider _result;
|
||||
private readonly ServiceDiscoveryProviderFactory _factory;
|
||||
|
||||
public ServiceProviderFactoryTests()
|
||||
{
|
||||
_factory = new ServiceDiscoveryProviderFactory();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_no_service_provider()
|
||||
{
|
||||
var serviceConfig = new ServiceProviderConfiguraion("product", "127.0.0.1", 80, false, "Does not matter", string.Empty, 0);
|
||||
|
||||
this.Given(x => x.GivenTheReRoute(serviceConfig))
|
||||
.When(x => x.WhenIGetTheServiceProvider())
|
||||
.Then(x => x.ThenTheServiceProviderIs<ConfigurationServiceProvider>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_consul_service_provider()
|
||||
{
|
||||
var serviceConfig = new ServiceProviderConfiguraion("product", string.Empty, 0, true, "Consul", string.Empty, 0);
|
||||
|
||||
this.Given(x => x.GivenTheReRoute(serviceConfig))
|
||||
.When(x => x.WhenIGetTheServiceProvider())
|
||||
.Then(x => x.ThenTheServiceProviderIs<ConsulServiceDiscoveryProvider>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenTheReRoute(ServiceProviderConfiguraion serviceConfig)
|
||||
{
|
||||
_serviceConfig = serviceConfig;
|
||||
}
|
||||
|
||||
private void WhenIGetTheServiceProvider()
|
||||
{
|
||||
_result = _factory.Get(_serviceConfig);
|
||||
}
|
||||
|
||||
private void ThenTheServiceProviderIs<T>()
|
||||
{
|
||||
_result.ShouldBeOfType<T>();
|
||||
}
|
||||
}
|
||||
}
|
136
test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs
Normal file
136
test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs
Normal file
@ -0,0 +1,136 @@
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Values;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.ServiceDiscovery
|
||||
{
|
||||
public class ServiceRegistryTests
|
||||
{
|
||||
private Service _service;
|
||||
private List<Service> _services;
|
||||
private ServiceRegistry _serviceRegistry;
|
||||
private ServiceRepository _serviceRepository;
|
||||
|
||||
public ServiceRegistryTests()
|
||||
{
|
||||
_serviceRepository = new ServiceRepository();
|
||||
_serviceRegistry = new ServiceRegistry(_serviceRepository);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_register_service()
|
||||
{
|
||||
this.Given(x => x.GivenAServiceToRegister("product", "localhost:5000", 80))
|
||||
.When(x => x.WhenIRegisterTheService())
|
||||
.Then(x => x.ThenTheServiceIsRegistered())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
public void should_lookup_service()
|
||||
{
|
||||
this.Given(x => x.GivenAServiceIsRegistered("product", "localhost:600", 80))
|
||||
.When(x => x.WhenILookupTheService("product"))
|
||||
.Then(x => x.ThenTheServiceDetailsAreReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void ThenTheServiceDetailsAreReturned()
|
||||
{
|
||||
_services[0].HostAndPort.DownstreamHost.ShouldBe(_service.HostAndPort.DownstreamHost);
|
||||
_services[0].HostAndPort.DownstreamPort.ShouldBe(_service.HostAndPort.DownstreamPort);
|
||||
_services[0].Name.ShouldBe(_service.Name);
|
||||
}
|
||||
|
||||
private void WhenILookupTheService(string name)
|
||||
{
|
||||
_services = _serviceRegistry.Lookup(name);
|
||||
}
|
||||
|
||||
private void GivenAServiceIsRegistered(string name, string address, int port)
|
||||
{
|
||||
_service = new Service(name, new HostAndPort(address, port), string.Empty, string.Empty, new string[0]);
|
||||
_serviceRepository.Set(_service);
|
||||
}
|
||||
|
||||
private void GivenAServiceToRegister(string name, string address, int port)
|
||||
{
|
||||
_service = new Service(name, new HostAndPort(address, port), string.Empty, string.Empty, new string[0]);
|
||||
}
|
||||
|
||||
private void WhenIRegisterTheService()
|
||||
{
|
||||
_serviceRegistry.Register(_service);
|
||||
}
|
||||
|
||||
private void ThenTheServiceIsRegistered()
|
||||
{
|
||||
var serviceNameAndAddress = _serviceRepository.Get(_service.Name);
|
||||
serviceNameAndAddress[0].HostAndPort.DownstreamHost.ShouldBe(_service.HostAndPort.DownstreamHost);
|
||||
serviceNameAndAddress[0].HostAndPort.DownstreamPort.ShouldBe(_service.HostAndPort.DownstreamPort);
|
||||
serviceNameAndAddress[0].Name.ShouldBe(_service.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IServiceRegistry
|
||||
{
|
||||
void Register(Service serviceNameAndAddress);
|
||||
List<Service> Lookup(string name);
|
||||
}
|
||||
|
||||
public class ServiceRegistry : IServiceRegistry
|
||||
{
|
||||
private readonly IServiceRepository _repository;
|
||||
public ServiceRegistry(IServiceRepository repository)
|
||||
{
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
public void Register(Service serviceNameAndAddress)
|
||||
{
|
||||
_repository.Set(serviceNameAndAddress);
|
||||
}
|
||||
|
||||
public List<Service> Lookup(string name)
|
||||
{
|
||||
return _repository.Get(name);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IServiceRepository
|
||||
{
|
||||
List<Service> Get(string serviceName);
|
||||
void Set(Service serviceNameAndAddress);
|
||||
}
|
||||
|
||||
public class ServiceRepository : IServiceRepository
|
||||
{
|
||||
private Dictionary<string, List<Service>> _registeredServices;
|
||||
|
||||
public ServiceRepository()
|
||||
{
|
||||
_registeredServices = new Dictionary<string, List<Service>>();
|
||||
}
|
||||
|
||||
public List<Service> Get(string serviceName)
|
||||
{
|
||||
return _registeredServices[serviceName];
|
||||
}
|
||||
|
||||
public void Set(Service serviceNameAndAddress)
|
||||
{
|
||||
List<Service> services;
|
||||
if(_registeredServices.TryGetValue(serviceNameAndAddress.Name, out services))
|
||||
{
|
||||
services.Add(serviceNameAndAddress);
|
||||
_registeredServices[serviceNameAndAddress.Name] = services;
|
||||
}
|
||||
else
|
||||
{
|
||||
_registeredServices[serviceNameAndAddress.Name] = new List<Service>(){ serviceNameAndAddress };
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@
|
||||
"win7-x64": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"netcoreapp1.4": {
|
||||
"netcoreapp1.1": {
|
||||
"imports": [
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user