mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 06:22:50 +08:00
Merge pull request #28 from TomPallister/feature/servicediscovery
Feature/servicediscovery
This commit is contained in:
commit
9e9303c25f
@ -9,19 +9,24 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.gitignore = .gitignore
|
.gitignore = .gitignore
|
||||||
appveyor.yml = appveyor.yml
|
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-and-run-tests.ps1 = build-and-run-tests.ps1
|
||||||
build.cake = build.cake
|
build.cake = build.cake
|
||||||
build.ps1 = build.ps1
|
build.ps1 = build.ps1
|
||||||
|
build.readme.md = build.readme.md
|
||||||
configuration-explanation.txt = configuration-explanation.txt
|
configuration-explanation.txt = configuration-explanation.txt
|
||||||
|
configuration.yaml = configuration.yaml
|
||||||
|
GitVersion.yml = GitVersion.yml
|
||||||
global.json = global.json
|
global.json = global.json
|
||||||
LICENSE.md = LICENSE.md
|
LICENSE.md = LICENSE.md
|
||||||
Ocelot.nuspec = Ocelot.nuspec
|
Ocelot.nuspec = Ocelot.nuspec
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
release.ps1 = release.ps1
|
release.ps1 = release.ps1
|
||||||
|
ReleaseNotes.md = ReleaseNotes.md
|
||||||
run-acceptance-tests.ps1 = run-acceptance-tests.ps1
|
run-acceptance-tests.ps1 = run-acceptance-tests.ps1
|
||||||
run-benchmarks.bat = run-benchmarks.bat
|
|
||||||
run-benchmarks.ps1 = run-benchmarks.ps1
|
run-benchmarks.ps1 = run-benchmarks.ps1
|
||||||
run-unit-tests.ps1 = run-unit-tests.ps1
|
run-unit-tests.ps1 = run-unit-tests.ps1
|
||||||
|
version.ps1 = version.ps1
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot", "src\Ocelot\Ocelot.xproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot", "src\Ocelot\Ocelot.xproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}"
|
||||||
|
41
README.md
41
README.md
@ -41,7 +41,7 @@ touch either via gitter or create an issue.
|
|||||||
## How to install
|
## How to install
|
||||||
|
|
||||||
Ocelot is designed to work with ASP.NET core only and is currently
|
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
|
Install Ocelot and it's dependecies using nuget. At the moment
|
||||||
all we have is the pre version. Once we have something working in
|
all we have is the pre version. Once we have something working in
|
||||||
@ -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
|
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!
|
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
|
## Authentication
|
||||||
|
|
||||||
Ocelot currently supports the use of bearer tokens with Identity Server (more providers to
|
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)
|
You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,6 +3,6 @@ configuration:
|
|||||||
- Release
|
- Release
|
||||||
platform: Any CPU
|
platform: Any CPU
|
||||||
build_script:
|
build_script:
|
||||||
- ./build.ps1
|
- build.ps1
|
||||||
cache:
|
cache:
|
||||||
- '%USERPROFILE%\.nuget\packages'
|
- '%USERPROFILE%\.nuget\packages'
|
@ -42,8 +42,8 @@ var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package";
|
|||||||
|
|
||||||
// internal build variables - don't change these.
|
// internal build variables - don't change these.
|
||||||
var releaseTag = "";
|
var releaseTag = "";
|
||||||
var buildVersion = committedVersion;
|
|
||||||
var committedVersion = "0.0.0-dev";
|
var committedVersion = "0.0.0-dev";
|
||||||
|
var buildVersion = committedVersion;
|
||||||
|
|
||||||
var target = Argument("target", "Default");
|
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.
|
# the caching a lot.
|
||||||
"FileCacheOptions": { "TtlSeconds": 15 },
|
"FileCacheOptions": { "TtlSeconds": 15 },
|
||||||
# The value of this is used when matching the upstream template to an upstream url.
|
# 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
|
# This section is meant to be for global configuration settings
|
||||||
"GlobalConfiguration": {
|
"GlobalConfiguration": {
|
||||||
# If this is set it will override any route specific request id keys, behaves the same
|
# If this is set it will override any route specific request id keys, behaves the same
|
||||||
# otherwise
|
# otherwise
|
||||||
"RequestIdKey": "OcRequestId",
|
"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
|
public class ReRouteBuilder
|
||||||
{
|
{
|
||||||
|
private string _loadBalancerKey;
|
||||||
private string _downstreamPathTemplate;
|
private string _downstreamPathTemplate;
|
||||||
private string _upstreamTemplate;
|
private string _upstreamTemplate;
|
||||||
private string _upstreamTemplatePattern;
|
private string _upstreamTemplatePattern;
|
||||||
@ -32,12 +33,21 @@ namespace Ocelot.Configuration.Builder
|
|||||||
private string _downstreamScheme;
|
private string _downstreamScheme;
|
||||||
private string _downstreamHost;
|
private string _downstreamHost;
|
||||||
private int _dsPort;
|
private int _dsPort;
|
||||||
|
private string _loadBalancer;
|
||||||
|
private string _serviceProviderHost;
|
||||||
|
private int _serviceProviderPort;
|
||||||
|
|
||||||
public ReRouteBuilder()
|
public ReRouteBuilder()
|
||||||
{
|
{
|
||||||
_additionalScopes = new List<string>();
|
_additionalScopes = new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithLoadBalancer(string loadBalancer)
|
||||||
|
{
|
||||||
|
_loadBalancer = loadBalancer;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public ReRouteBuilder WithDownstreamScheme(string downstreamScheme)
|
public ReRouteBuilder WithDownstreamScheme(string downstreamScheme)
|
||||||
{
|
{
|
||||||
_downstreamScheme = downstreamScheme;
|
_downstreamScheme = downstreamScheme;
|
||||||
@ -192,15 +202,31 @@ namespace Ocelot.Configuration.Builder
|
|||||||
return this;
|
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()
|
public ReRoute Build()
|
||||||
{
|
{
|
||||||
Func<HostAndPort> downstreamHostFunc = () => new HostAndPort(_downstreamHost, _dsPort);
|
|
||||||
|
|
||||||
return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern,
|
return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern,
|
||||||
_isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName,
|
_isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName,
|
||||||
_requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement,
|
_requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement,
|
||||||
_isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName,
|
_isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer,
|
||||||
_useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, downstreamHostFunc, _downstreamScheme);
|
_downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Configuration.Parser;
|
using Ocelot.Configuration.Parser;
|
||||||
using Ocelot.Configuration.Validator;
|
using Ocelot.Configuration.Validator;
|
||||||
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Utilities;
|
using Ocelot.Utilities;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
@ -25,22 +27,28 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser;
|
private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser;
|
||||||
private readonly ILogger<FileOcelotConfigurationCreator> _logger;
|
private readonly ILogger<FileOcelotConfigurationCreator> _logger;
|
||||||
|
private readonly ILoadBalancerFactory _loadBalanceFactory;
|
||||||
|
private readonly ILoadBalancerHouse _loadBalancerHouse;
|
||||||
|
|
||||||
public FileOcelotConfigurationCreator(
|
public FileOcelotConfigurationCreator(
|
||||||
IOptions<FileConfiguration> options,
|
IOptions<FileConfiguration> options,
|
||||||
IConfigurationValidator configurationValidator,
|
IConfigurationValidator configurationValidator,
|
||||||
IClaimToThingConfigurationParser claimToThingConfigurationParser,
|
IClaimToThingConfigurationParser claimToThingConfigurationParser,
|
||||||
ILogger<FileOcelotConfigurationCreator> logger)
|
ILogger<FileOcelotConfigurationCreator> logger,
|
||||||
|
ILoadBalancerFactory loadBalancerFactory,
|
||||||
|
ILoadBalancerHouse loadBalancerHouse)
|
||||||
{
|
{
|
||||||
|
_loadBalanceFactory = loadBalancerFactory;
|
||||||
|
_loadBalancerHouse = loadBalancerHouse;
|
||||||
_options = options;
|
_options = options;
|
||||||
_configurationValidator = configurationValidator;
|
_configurationValidator = configurationValidator;
|
||||||
_claimToThingConfigurationParser = claimToThingConfigurationParser;
|
_claimToThingConfigurationParser = claimToThingConfigurationParser;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response<IOcelotConfiguration> Create()
|
public async Task<Response<IOcelotConfiguration>> Create()
|
||||||
{
|
{
|
||||||
var config = SetUpConfiguration();
|
var config = await SetUpConfiguration();
|
||||||
|
|
||||||
return new OkResponse<IOcelotConfiguration>(config);
|
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
|
/// 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
|
/// will need a refactor at some point as its crap
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IOcelotConfiguration SetUpConfiguration()
|
private async Task<IOcelotConfiguration> SetUpConfiguration()
|
||||||
{
|
{
|
||||||
var response = _configurationValidator.IsValid(_options.Value);
|
var response = _configurationValidator.IsValid(_options.Value);
|
||||||
|
|
||||||
@ -69,63 +77,81 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
foreach (var reRoute in _options.Value.ReRoutes)
|
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);
|
reRoutes.Add(ocelotReRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OcelotConfiguration(reRoutes);
|
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 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
|
var requestIdKey = globalRequestIdConfiguration
|
||||||
? globalConfiguration.RequestIdKey
|
? globalConfiguration.RequestIdKey
|
||||||
: reRoute.RequestIdKey;
|
: fileReRoute.RequestIdKey;
|
||||||
|
|
||||||
var useServiceDiscovery = !string.IsNullOrEmpty(reRoute.ServiceName)
|
var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName)
|
||||||
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Address)
|
|
||||||
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider);
|
&& !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)
|
if (isAuthenticated)
|
||||||
{
|
{
|
||||||
var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider,
|
var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider,
|
||||||
reRoute.AuthenticationOptions.ProviderRootUrl, reRoute.AuthenticationOptions.ScopeName,
|
fileReRoute.AuthenticationOptions.ProviderRootUrl, fileReRoute.AuthenticationOptions.ScopeName,
|
||||||
reRoute.AuthenticationOptions.RequireHttps, reRoute.AuthenticationOptions.AdditionalScopes,
|
fileReRoute.AuthenticationOptions.RequireHttps, fileReRoute.AuthenticationOptions.AdditionalScopes,
|
||||||
reRoute.AuthenticationOptions.ScopeSecret);
|
fileReRoute.AuthenticationOptions.ScopeSecret);
|
||||||
|
|
||||||
var claimsToHeaders = GetAddThingsToRequest(reRoute.AddHeadersToRequest);
|
var claimsToHeaders = GetAddThingsToRequest(fileReRoute.AddHeadersToRequest);
|
||||||
var claimsToClaims = GetAddThingsToRequest(reRoute.AddClaimsToRequest);
|
var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest);
|
||||||
var claimsToQueries = GetAddThingsToRequest(reRoute.AddQueriesToRequest);
|
var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest);
|
||||||
|
|
||||||
return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate,
|
reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate),
|
||||||
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
fileReRoute.UpstreamTemplate,
|
||||||
|
fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
||||||
authOptionsForRoute, claimsToHeaders, claimsToClaims,
|
authOptionsForRoute, claimsToHeaders, claimsToClaims,
|
||||||
reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries,
|
fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries,
|
||||||
requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds),
|
requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)
|
||||||
reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider,
|
, fileReRoute.DownstreamScheme,
|
||||||
globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.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,
|
var loadBalancer = await _loadBalanceFactory.Get(reRoute);
|
||||||
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
|
_loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer);
|
||||||
null, new List<ClaimToThing>(), new List<ClaimToThing>(),
|
return reRoute;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildUpstreamTemplate(FileReRoute reRoute)
|
private string BuildUpstreamTemplate(FileReRoute reRoute)
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Creator
|
namespace Ocelot.Configuration.Creator
|
||||||
{
|
{
|
||||||
public interface IOcelotConfigurationCreator
|
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 DownstreamScheme {get;set;}
|
||||||
public string DownstreamHost {get;set;}
|
public string DownstreamHost {get;set;}
|
||||||
public int DownstreamPort { get; set; }
|
public int DownstreamPort { get; set; }
|
||||||
|
public string LoadBalancer {get;set;}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,6 +3,7 @@ namespace Ocelot.Configuration.File
|
|||||||
public class FileServiceDiscoveryProvider
|
public class FileServiceDiscoveryProvider
|
||||||
{
|
{
|
||||||
public string Provider {get;set;}
|
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
|
namespace Ocelot.Configuration.Provider
|
||||||
{
|
{
|
||||||
public interface IOcelotConfigurationProvider
|
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.Configuration.Repository;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ namespace Ocelot.Configuration.Provider
|
|||||||
_creator = creator;
|
_creator = creator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response<IOcelotConfiguration> Get()
|
public async Task<Response<IOcelotConfiguration>> Get()
|
||||||
{
|
{
|
||||||
var repoConfig = _repo.Get();
|
var repoConfig = _repo.Get();
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ namespace Ocelot.Configuration.Provider
|
|||||||
|
|
||||||
if (repoConfig.Data == null)
|
if (repoConfig.Data == null)
|
||||||
{
|
{
|
||||||
var creatorConfig = _creator.Create();
|
var creatorConfig = await _creator.Create();
|
||||||
|
|
||||||
if (creatorConfig.IsError)
|
if (creatorConfig.IsError)
|
||||||
{
|
{
|
||||||
|
@ -6,12 +6,23 @@ namespace Ocelot.Configuration
|
|||||||
{
|
{
|
||||||
public class ReRoute
|
public class ReRoute
|
||||||
{
|
{
|
||||||
public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern,
|
public ReRoute(DownstreamPathTemplate downstreamPathTemplate,
|
||||||
bool isAuthenticated, AuthenticationOptions authenticationOptions, List<ClaimToThing> configurationHeaderExtractorProperties,
|
string upstreamTemplate, string upstreamHttpMethod,
|
||||||
List<ClaimToThing> claimsToClaims, Dictionary<string, string> routeClaimsRequirement, bool isAuthorised, List<ClaimToThing> claimsToQueries,
|
string upstreamTemplatePattern,
|
||||||
string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery,
|
bool isAuthenticated, AuthenticationOptions authenticationOptions,
|
||||||
string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func<HostAndPort> downstreamHostAndPort, string downstreamScheme)
|
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;
|
DownstreamPathTemplate = downstreamPathTemplate;
|
||||||
UpstreamTemplate = upstreamTemplate;
|
UpstreamTemplate = upstreamTemplate;
|
||||||
UpstreamHttpMethod = upstreamHttpMethod;
|
UpstreamHttpMethod = upstreamHttpMethod;
|
||||||
@ -29,14 +40,10 @@ namespace Ocelot.Configuration
|
|||||||
?? new List<ClaimToThing>();
|
?? new List<ClaimToThing>();
|
||||||
ClaimsToHeaders = configurationHeaderExtractorProperties
|
ClaimsToHeaders = configurationHeaderExtractorProperties
|
||||||
?? new List<ClaimToThing>();
|
?? new List<ClaimToThing>();
|
||||||
ServiceName = serviceName;
|
|
||||||
UseServiceDiscovery = useServiceDiscovery;
|
|
||||||
ServiceDiscoveryProvider = serviceDiscoveryProvider;
|
|
||||||
ServiceDiscoveryAddress = serviceDiscoveryAddress;
|
|
||||||
DownstreamHostAndPort = downstreamHostAndPort;
|
|
||||||
DownstreamScheme = downstreamScheme;
|
DownstreamScheme = downstreamScheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string LoadBalancerKey {get;private set;}
|
||||||
public DownstreamPathTemplate DownstreamPathTemplate { get; private set; }
|
public DownstreamPathTemplate DownstreamPathTemplate { get; private set; }
|
||||||
public string UpstreamTemplate { get; private set; }
|
public string UpstreamTemplate { get; private set; }
|
||||||
public string UpstreamTemplatePattern { get; private set; }
|
public string UpstreamTemplatePattern { get; private set; }
|
||||||
@ -51,11 +58,10 @@ namespace Ocelot.Configuration
|
|||||||
public string RequestIdKey { get; private set; }
|
public string RequestIdKey { get; private set; }
|
||||||
public bool IsCached { get; private set; }
|
public bool IsCached { get; private set; }
|
||||||
public CacheOptions FileCacheOptions { 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 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.Headers;
|
||||||
using Ocelot.Infrastructure.Claims.Parser;
|
using Ocelot.Infrastructure.Claims.Parser;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.QueryStrings;
|
using Ocelot.QueryStrings;
|
||||||
using Ocelot.Request.Builder;
|
using Ocelot.Request.Builder;
|
||||||
using Ocelot.Requester;
|
using Ocelot.Requester;
|
||||||
using Ocelot.Responder;
|
using Ocelot.Responder;
|
||||||
|
using Ocelot.ServiceDiscovery;
|
||||||
|
|
||||||
namespace Ocelot.DependencyInjection
|
namespace Ocelot.DependencyInjection
|
||||||
{
|
{
|
||||||
@ -59,6 +61,9 @@ namespace Ocelot.DependencyInjection
|
|||||||
{
|
{
|
||||||
services.AddMvcCore().AddJsonFormatters();
|
services.AddMvcCore().AddJsonFormatters();
|
||||||
services.AddLogging();
|
services.AddLogging();
|
||||||
|
services.AddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
|
||||||
|
services.AddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
|
||||||
|
services.AddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
|
||||||
services.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
|
services.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
|
||||||
services.AddSingleton<IUrlBuilder, UrlBuilder>();
|
services.AddSingleton<IUrlBuilder, UrlBuilder>();
|
||||||
services.AddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
|
services.AddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Configuration.Provider;
|
using Ocelot.Configuration.Provider;
|
||||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
@ -21,9 +22,9 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
|||||||
_urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder;
|
_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));
|
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
|
namespace Ocelot.DownstreamRouteFinder.Finder
|
||||||
{
|
{
|
||||||
public interface IDownstreamRouteFinder
|
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);
|
_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)
|
if (downstreamRoute.IsError)
|
||||||
{
|
{
|
||||||
|
@ -6,6 +6,7 @@ using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
|||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.DownstreamUrlCreator.Middleware
|
namespace Ocelot.DownstreamUrlCreator.Middleware
|
||||||
{
|
{
|
||||||
@ -46,7 +47,7 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
|
|||||||
|
|
||||||
var dsScheme = DownstreamRoute.ReRoute.DownstreamScheme;
|
var dsScheme = DownstreamRoute.ReRoute.DownstreamScheme;
|
||||||
|
|
||||||
var dsHostAndPort = DownstreamRoute.ReRoute.DownstreamHostAndPort();
|
var dsHostAndPort = HostAndPort;
|
||||||
|
|
||||||
var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort);
|
var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort);
|
||||||
|
|
||||||
|
@ -41,13 +41,13 @@ namespace Ocelot.Errors.Middleware
|
|||||||
|
|
||||||
var message = CreateMessage(context, e);
|
var message = CreateMessage(context, e);
|
||||||
_logger.LogError(message, e);
|
_logger.LogError(message, e);
|
||||||
await SetInternalServerErrorOnResponse(context);
|
SetInternalServerErrorOnResponse(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("ocelot pipeline finished");
|
_logger.LogDebug("ocelot pipeline finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetInternalServerErrorOnResponse(HttpContext context)
|
private void SetInternalServerErrorOnResponse(HttpContext context)
|
||||||
{
|
{
|
||||||
context.Response.OnStarting(x =>
|
context.Response.OnStarting(x =>
|
||||||
{
|
{
|
||||||
|
@ -21,6 +21,10 @@
|
|||||||
DownstreamPathTemplateContainsSchemeError,
|
DownstreamPathTemplateContainsSchemeError,
|
||||||
DownstreamPathNullOrEmptyError,
|
DownstreamPathNullOrEmptyError,
|
||||||
DownstreamSchemeNullOrEmptyError,
|
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.DownstreamRouteFinder;
|
||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.Middleware
|
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)
|
public void SetDownstreamRouteForThisRequest(DownstreamRoute downstreamRoute)
|
||||||
{
|
{
|
||||||
_requestScopedDataRepository.Add("DownstreamRoute", downstreamRoute);
|
_requestScopedDataRepository.Add("DownstreamRoute", downstreamRoute);
|
||||||
|
@ -18,6 +18,8 @@ namespace Ocelot.Middleware
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Authorisation.Middleware;
|
using Authorisation.Middleware;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Configuration.Provider;
|
||||||
|
using Ocelot.LoadBalancer.Middleware;
|
||||||
|
|
||||||
public static class OcelotMiddlewareExtensions
|
public static class OcelotMiddlewareExtensions
|
||||||
{
|
{
|
||||||
@ -28,6 +30,7 @@ namespace Ocelot.Middleware
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder)
|
public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
|
CreateConfiguration(builder);
|
||||||
builder.UseOcelot(new OcelotMiddlewareConfiguration());
|
builder.UseOcelot(new OcelotMiddlewareConfiguration());
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
@ -40,6 +43,8 @@ namespace Ocelot.Middleware
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration)
|
public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration)
|
||||||
{
|
{
|
||||||
|
CreateConfiguration(builder);
|
||||||
|
|
||||||
// This is registered to catch any global exceptions that are not handled
|
// This is registered to catch any global exceptions that are not handled
|
||||||
builder.UseExceptionHandlerMiddleware();
|
builder.UseExceptionHandlerMiddleware();
|
||||||
|
|
||||||
@ -98,6 +103,9 @@ namespace Ocelot.Middleware
|
|||||||
// Now we can run any query string transformation logic
|
// Now we can run any query string transformation logic
|
||||||
builder.UseQueryStringBuilderMiddleware();
|
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
|
// This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used
|
||||||
builder.UseDownstreamUrlCreatorMiddleware();
|
builder.UseDownstreamUrlCreatorMiddleware();
|
||||||
|
|
||||||
@ -114,6 +122,18 @@ namespace Ocelot.Middleware
|
|||||||
return builder;
|
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)
|
private static void UseIfNotNull(this IApplicationBuilder builder, Func<HttpContext, Func<Task>, Task> middleware)
|
||||||
{
|
{
|
||||||
if (middleware != null)
|
if (middleware != null)
|
||||||
|
@ -24,7 +24,7 @@ namespace Ocelot.Responder
|
|||||||
_removeOutputHeaders = removeOutputHeaders;
|
_removeOutputHeaders = removeOutputHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Response> SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response)
|
public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response)
|
||||||
{
|
{
|
||||||
_removeOutputHeaders.Remove(response.Headers);
|
_removeOutputHeaders.Remove(response.Headers);
|
||||||
|
|
||||||
@ -56,7 +56,6 @@ namespace Ocelot.Responder
|
|||||||
{
|
{
|
||||||
await stream.CopyToAsync(context.Response.Body);
|
await stream.CopyToAsync(context.Response.Body);
|
||||||
}
|
}
|
||||||
return new OkResponse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddHeaderIfDoesntExist(HttpContext context, KeyValuePair<string, IEnumerable<string>> httpResponseHeader)
|
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.OnStarting(x =>
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = statusCode;
|
context.Response.StatusCode = statusCode;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}, context);
|
}, context);
|
||||||
return new OkResponse();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,13 +1,12 @@
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Responses;
|
|
||||||
|
|
||||||
namespace Ocelot.Responder
|
namespace Ocelot.Responder
|
||||||
{
|
{
|
||||||
public interface IHttpResponder
|
public interface IHttpResponder
|
||||||
{
|
{
|
||||||
Task<Response> SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response);
|
Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response);
|
||||||
Task<Response> SetErrorResponseOnContext(HttpContext context, int statusCode);
|
void SetErrorResponseOnContext(HttpContext context, int statusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,34 +46,27 @@ namespace Ocelot.Responder.Middleware
|
|||||||
|
|
||||||
_logger.LogDebug("received errors setting error response");
|
_logger.LogDebug("received errors setting error response");
|
||||||
|
|
||||||
await SetErrorResponse(context, errors);
|
SetErrorResponse(context, errors);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogDebug("no pipeline error, setting response");
|
_logger.LogDebug("no pipeline error, setting response");
|
||||||
|
|
||||||
var setResponse = await _responder.SetResponseOnHttpContext(context, HttpResponseMessage);
|
await _responder.SetResponseOnHttpContext(context, HttpResponseMessage);
|
||||||
|
|
||||||
if (setResponse.IsError)
|
|
||||||
{
|
|
||||||
_logger.LogDebug("error setting response, returning error to client");
|
|
||||||
|
|
||||||
await SetErrorResponse(context, setResponse.Errors);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetErrorResponse(HttpContext context, List<Error> errors)
|
private void SetErrorResponse(HttpContext context, List<Error> errors)
|
||||||
{
|
{
|
||||||
var statusCode = _codeMapper.Map(errors);
|
var statusCode = _codeMapper.Map(errors);
|
||||||
|
|
||||||
if (!statusCode.IsError)
|
if (!statusCode.IsError)
|
||||||
{
|
{
|
||||||
await _responder.SetErrorResponseOnContext(context, statusCode.Data);
|
_responder.SetErrorResponseOnContext(context, statusCode.Data);
|
||||||
}
|
}
|
||||||
else
|
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)
|
public HostAndPort(string downstreamHost, int downstreamPort)
|
||||||
{
|
{
|
||||||
DownstreamHost = downstreamHost;
|
DownstreamHost = downstreamHost?.Trim('/');
|
||||||
DownstreamPort = downstreamPort;
|
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",
|
"Microsoft.NETCore.App": "1.1.0",
|
||||||
"CacheManager.Core": "0.9.2",
|
"CacheManager.Core": "0.9.2",
|
||||||
"CacheManager.Microsoft.Extensions.Configuration": "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": {
|
"runtimes": {
|
||||||
"win10-x64": {},
|
"win10-x64": {},
|
||||||
@ -35,7 +36,7 @@
|
|||||||
"win7-x64": {}
|
"win7-x64": {}
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"netcoreapp1.4": {
|
"netcoreapp1.1": {
|
||||||
"imports": [
|
"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;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using CacheManager.Core;
|
using CacheManager.Core;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.TestHost;
|
using Microsoft.AspNetCore.TestHost;
|
||||||
@ -29,6 +31,12 @@ namespace Ocelot.AcceptanceTests
|
|||||||
private BearerToken _token;
|
private BearerToken _token;
|
||||||
public HttpClient OcelotClient => _ocelotClient;
|
public HttpClient OcelotClient => _ocelotClient;
|
||||||
public string RequestIdKey = "OcRequestId";
|
public string RequestIdKey = "OcRequestId";
|
||||||
|
private Random _random;
|
||||||
|
|
||||||
|
public Steps()
|
||||||
|
{
|
||||||
|
_random = new Random();
|
||||||
|
}
|
||||||
|
|
||||||
public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
|
public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
|
||||||
{
|
{
|
||||||
@ -153,6 +161,28 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_response = _ocelotClient.GetAsync(url).Result;
|
_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)
|
public void WhenIGetUrlOnTheApiGateway(string url, string requestId)
|
||||||
{
|
{
|
||||||
_ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId);
|
_ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId);
|
||||||
|
@ -4,15 +4,13 @@
|
|||||||
|
|
||||||
public static class TestConfiguration
|
public static class TestConfiguration
|
||||||
{
|
{
|
||||||
public static double Version => 1.4;
|
public static double Version => 1.1;
|
||||||
public static string ConfigurationPath => GetConfigurationPath();
|
public static string ConfigurationPath => GetConfigurationPath();
|
||||||
|
|
||||||
public static string GetConfigurationPath()
|
public static string GetConfigurationPath()
|
||||||
{
|
{
|
||||||
var osArchitecture = RuntimeInformation.OSArchitecture.ToString();
|
var osArchitecture = RuntimeInformation.OSArchitecture.ToString();
|
||||||
|
|
||||||
var oSDescription = string.Empty;
|
|
||||||
|
|
||||||
if(RuntimeInformation.OSDescription.ToLower().Contains("darwin"))
|
if(RuntimeInformation.OSDescription.ToLower().Contains("darwin"))
|
||||||
{
|
{
|
||||||
return FormatConfigurationPath("osx.10.11", osArchitecture);
|
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.AspNetCore.Server.Kestrel": "1.1.0",
|
||||||
"Microsoft.NETCore.App": "1.1.0",
|
"Microsoft.NETCore.App": "1.1.0",
|
||||||
"Shouldly": "2.8.2",
|
"Shouldly": "2.8.2",
|
||||||
"TestStack.BDDfy": "4.3.2"
|
"TestStack.BDDfy": "4.3.2",
|
||||||
|
"Consul": "0.7.2.1"
|
||||||
},
|
},
|
||||||
"runtimes": {
|
"runtimes": {
|
||||||
"win10-x64": {},
|
"win10-x64": {},
|
||||||
@ -40,7 +41,7 @@
|
|||||||
"win7-x64": {}
|
"win7-x64": {}
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"netcoreapp1.4": {
|
"netcoreapp1.1": {
|
||||||
"imports": [
|
"imports": [
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Ocelot": "0.0.0-dev",
|
"Ocelot": "0.0.0-dev",
|
||||||
"BenchmarkDotNet": "0.10.1"
|
"BenchmarkDotNet": "0.10.2"
|
||||||
},
|
},
|
||||||
"runtimes": {
|
"runtimes": {
|
||||||
"win10-x64": {},
|
"win10-x64": {},
|
||||||
@ -14,7 +14,7 @@
|
|||||||
"win7-x64": {}
|
"win7-x64": {}
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"netcoreapp1.4": {
|
"netcoreapp1.1": {
|
||||||
"imports": [
|
"imports": [
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"win7-x64": {}
|
"win7-x64": {}
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"netcoreapp1.4": {
|
"netcoreapp1.1": {
|
||||||
"imports": [
|
"imports": [
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using Ocelot.Configuration.Creator;
|
|||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Configuration.Parser;
|
using Ocelot.Configuration.Parser;
|
||||||
using Ocelot.Configuration.Validator;
|
using Ocelot.Configuration.Validator;
|
||||||
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
@ -24,6 +25,9 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
private readonly Mock<IClaimToThingConfigurationParser> _configParser;
|
private readonly Mock<IClaimToThingConfigurationParser> _configParser;
|
||||||
private readonly Mock<ILogger<FileOcelotConfigurationCreator>> _logger;
|
private readonly Mock<ILogger<FileOcelotConfigurationCreator>> _logger;
|
||||||
private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator;
|
private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator;
|
||||||
|
private readonly Mock<ILoadBalancerFactory> _loadBalancerFactory;
|
||||||
|
private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse;
|
||||||
|
private readonly Mock<ILoadBalancer> _loadBalancer;
|
||||||
|
|
||||||
public FileConfigurationCreatorTests()
|
public FileConfigurationCreatorTests()
|
||||||
{
|
{
|
||||||
@ -31,8 +35,37 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
_configParser = new Mock<IClaimToThingConfigurationParser>();
|
_configParser = new Mock<IClaimToThingConfigurationParser>();
|
||||||
_validator = new Mock<IConfigurationValidator>();
|
_validator = new Mock<IConfigurationValidator>();
|
||||||
_fileConfig = new Mock<IOptions<FileConfiguration>>();
|
_fileConfig = new Mock<IOptions<FileConfiguration>>();
|
||||||
|
_loadBalancerFactory = new Mock<ILoadBalancerFactory>();
|
||||||
|
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
|
||||||
|
_loadBalancer = new Mock<ILoadBalancer>();
|
||||||
_ocelotConfigurationCreator = new FileOcelotConfigurationCreator(
|
_ocelotConfigurationCreator = new FileOcelotConfigurationCreator(
|
||||||
_fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object);
|
_fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object,
|
||||||
|
_loadBalancerFactory.Object, _loadBalancerHouse.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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]
|
[Fact]
|
||||||
@ -66,6 +99,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
public void should_use_downstream_scheme()
|
public void should_use_downstream_scheme()
|
||||||
{
|
{
|
||||||
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
|
||||||
@ -117,7 +151,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
|
||||||
{
|
{
|
||||||
Provider = "consul",
|
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.GivenTheConfigIsValid())
|
||||||
.And(x => x.GivenTheConfigHeaderExtractorReturns(new ClaimToThing("CustomerId", "CustomerId", "", 0)))
|
.And(x => x.GivenTheConfigHeaderExtractorReturns(new ClaimToThing("CustomerId", "CustomerId", "", 0)))
|
||||||
|
.And(x => x.GivenTheLoadBalancerFactoryReturns())
|
||||||
.When(x => x.WhenICreateTheConfig())
|
.When(x => x.WhenICreateTheConfig())
|
||||||
.Then(x => x.ThenTheReRoutesAre(expected))
|
.Then(x => x.ThenTheReRoutesAre(expected))
|
||||||
.And(x => x.ThenTheAuthenticationOptionsAre(expected))
|
.And(x => x.ThenTheAuthenticationOptionsAre(expected))
|
||||||
@ -430,6 +465,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.And(x => x.GivenTheConfigIsValid())
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
|
.And(x => x.GivenTheLoadBalancerFactoryReturns())
|
||||||
.When(x => x.WhenICreateTheConfig())
|
.When(x => x.WhenICreateTheConfig())
|
||||||
.Then(x => x.ThenTheReRoutesAre(expected))
|
.Then(x => x.ThenTheReRoutesAre(expected))
|
||||||
.And(x => x.ThenTheAuthenticationOptionsAre(expected))
|
.And(x => x.ThenTheAuthenticationOptionsAre(expected))
|
||||||
@ -543,7 +579,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
private void WhenICreateTheConfig()
|
private void WhenICreateTheConfig()
|
||||||
{
|
{
|
||||||
_config = _ocelotConfigurationCreator.Create();
|
_config = _ocelotConfigurationCreator.Create().Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheReRoutesAre(List<ReRoute> expectedReRoutes)
|
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
|
_creator
|
||||||
.Setup(x => x.Create())
|
.Setup(x => x.Create())
|
||||||
.Returns(config);
|
.ReturnsAsync(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheRepoReturns(Response<IOcelotConfiguration> config)
|
private void GivenTheRepoReturns(Response<IOcelotConfiguration> config)
|
||||||
@ -93,7 +93,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
private void WhenIGetTheConfig()
|
private void WhenIGetTheConfig()
|
||||||
{
|
{
|
||||||
_result = _ocelotConfigurationProvider.Get();
|
_result = _ocelotConfigurationProvider.Get().Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TheFollowingIsReturned(Response<IOcelotConfiguration> expected)
|
private void TheFollowingIsReturned(Response<IOcelotConfiguration> expected)
|
||||||
|
@ -84,7 +84,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
|||||||
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
|
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
|
||||||
_downstreamRouteFinder
|
_downstreamRouteFinder
|
||||||
.Setup(x => x.FindDownstreamRoute(It.IsAny<string>(), It.IsAny<string>()))
|
.Setup(x => x.FindDownstreamRoute(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Returns(_downstreamRoute);
|
.ReturnsAsync(_downstreamRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -159,7 +159,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
|||||||
_reRoutesConfig = reRoutesConfig;
|
_reRoutesConfig = reRoutesConfig;
|
||||||
_mockConfig
|
_mockConfig
|
||||||
.Setup(x => x.Get())
|
.Setup(x => x.Get())
|
||||||
.Returns(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(_reRoutesConfig)));
|
.ReturnsAsync(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(_reRoutesConfig)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath)
|
private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath)
|
||||||
@ -169,7 +169,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
|||||||
|
|
||||||
private void WhenICallTheFinder()
|
private void WhenICallTheFinder()
|
||||||
{
|
{
|
||||||
_result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod);
|
_result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheFollowingIsReturned(DownstreamRoute expected)
|
private void ThenTheFollowingIsReturned(DownstreamRoute expected)
|
||||||
|
@ -36,6 +36,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
|
|||||||
private HttpResponseMessage _result;
|
private HttpResponseMessage _result;
|
||||||
private OkResponse<DownstreamPath> _downstreamPath;
|
private OkResponse<DownstreamPath> _downstreamPath;
|
||||||
private OkResponse<DownstreamUrl> _downstreamUrl;
|
private OkResponse<DownstreamUrl> _downstreamUrl;
|
||||||
|
private HostAndPort _hostAndPort;
|
||||||
|
|
||||||
public DownstreamUrlCreatorMiddlewareTests()
|
public DownstreamUrlCreatorMiddlewareTests()
|
||||||
{
|
{
|
||||||
@ -69,14 +70,25 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_call_dependencies_correctly()
|
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())))
|
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.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())
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
|
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
|
||||||
.BDDfy();
|
.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)
|
private void TheUrlBuilderReturns(string dsUrl)
|
||||||
{
|
{
|
||||||
_downstreamUrl = new OkResponse<DownstreamUrl>(new DownstreamUrl(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()))
|
this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage()))
|
||||||
.And(x => x.GivenThereAreNoPipelineErrors())
|
.And(x => x.GivenThereAreNoPipelineErrors())
|
||||||
.And(x => x.GivenTheResponderReturns())
|
|
||||||
.When(x => x.WhenICallTheMiddleware())
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
.Then(x => x.ThenThereAreNoErrors())
|
.Then(x => x.ThenThereAreNoErrors())
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheResponderReturns()
|
|
||||||
{
|
|
||||||
_responder
|
|
||||||
.Setup(x => x.SetResponseOnHttpContext(It.IsAny<HttpContext>(), It.IsAny<HttpResponseMessage>()))
|
|
||||||
.ReturnsAsync(new OkResponse());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenThereAreNoPipelineErrors()
|
private void GivenThereAreNoPipelineErrors()
|
||||||
{
|
{
|
||||||
_scopedRepository
|
_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": {}
|
"win7-x64": {}
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"netcoreapp1.4": {
|
"netcoreapp1.1": {
|
||||||
"imports": [
|
"imports": [
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user