From 6bf2d4677cfa2ccd44f0b72ea4df9de11b06204f Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 22 Jan 2017 20:22:04 +0000 Subject: [PATCH 01/29] started adding loadbalancers --- push-to-nuget.bat | 2 +- .../Ocelot.AcceptanceTests/configuration.json | 2 +- test/Ocelot.UnitTests/RoundRobinTests.cs | 81 +++++++++++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 test/Ocelot.UnitTests/RoundRobinTests.cs diff --git a/push-to-nuget.bat b/push-to-nuget.bat index 7200d09ce..73d3bf7ec 100644 --- a/push-to-nuget.bat +++ b/push-to-nuget.bat @@ -4,7 +4,7 @@ echo Packing Ocelot Version %1 nuget pack .\Ocelot.nuspec -version %1 echo Publishing Ocelot - nuget push Ocelot.%1.nupkg -ApiKey %2 -Source https://www.nuget.org/api/v2/package +nuget push Ocelot.%1.nupkg -ApiKey %2 -Source https://www.nuget.org/api/v2/package diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index 984f851cf..f28abefe3 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamTemplate":"http://localhost: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}],"GlobalConfiguration":{"RequestIdKey":null}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"/","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}}} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/RoundRobinTests.cs b/test/Ocelot.UnitTests/RoundRobinTests.cs new file mode 100644 index 000000000..82689b626 --- /dev/null +++ b/test/Ocelot.UnitTests/RoundRobinTests.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Ocelot.Values; +using Shouldly; +using Xunit; + +namespace Ocelot.UnitTests +{ + public class RoundRobinTests + { + private readonly RoundRobin _roundRobin; + private readonly List _hostAndPorts; + + public RoundRobinTests() + { + _hostAndPorts = new List + { + new HostAndPort("127.0.0.1", 5000), + new HostAndPort("127.0.0.1", 5001), + new HostAndPort("127.0.0.1", 5001) + }; + + _roundRobin = new RoundRobin(_hostAndPorts); + } + + [Fact] + public void should_get_next_address() + { + var address = _roundRobin.Next(); + address.ShouldBe(_hostAndPorts[0]); + address = _roundRobin.Next(); + address.ShouldBe(_hostAndPorts[1]); + address = _roundRobin.Next(); + address.ShouldBe(_hostAndPorts[2]); + } + + [Fact] + public void should_go_back_to_first_address_after_finished_last() + { + var stopWatch = Stopwatch.StartNew(); + + while (stopWatch.ElapsedMilliseconds < 1000) + { + var address = _roundRobin.Next(); + address.ShouldBe(_hostAndPorts[0]); + address = _roundRobin.Next(); + address.ShouldBe(_hostAndPorts[1]); + address = _roundRobin.Next(); + address.ShouldBe(_hostAndPorts[2]); + } + } + } + + public interface ILoadBalancer + { + HostAndPort Next(); + } + + public class RoundRobin : ILoadBalancer + { + private readonly List _hostAndPorts; + private int _last; + + public RoundRobin(List hostAndPorts) + { + _hostAndPorts = hostAndPorts; + } + + public HostAndPort Next() + { + if (_last >= _hostAndPorts.Count) + { + _last = 0; + } + + var next = _hostAndPorts[_last]; + _last++; + return next; + } + } +} From cdad892a9606191fb3cd68a90a01665dde00a624 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 23 Jan 2017 12:09:54 +0000 Subject: [PATCH 02/29] hacking away --- test/Ocelot.UnitTests/ServiceRegistryTests.cs | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 test/Ocelot.UnitTests/ServiceRegistryTests.cs diff --git a/test/Ocelot.UnitTests/ServiceRegistryTests.cs b/test/Ocelot.UnitTests/ServiceRegistryTests.cs new file mode 100644 index 000000000..8866f1ae1 --- /dev/null +++ b/test/Ocelot.UnitTests/ServiceRegistryTests.cs @@ -0,0 +1,143 @@ +using System.Collections.Generic; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests +{ + public class ServiceRegistryTests + { + private Service _service; + private List _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")) + .When(x => x.WhenIRegisterTheService()) + .Then(x => x.ThenTheServiceIsRegistered()) + .BDDfy(); + } + + public void should_lookup_service() + { + this.Given(x => x.GivenAServiceIsRegistered("product", "localhost:600")) + .When(x => x.WhenILookupTheService("product")) + .Then(x => x.ThenTheServiceDetailsAreReturned()) + .BDDfy(); + } + + private void ThenTheServiceDetailsAreReturned() + { + _services[0].Address.ShouldBe(_service.Address); + _services[0].Name.ShouldBe(_service.Name); + } + + private void WhenILookupTheService(string name) + { + _services = _serviceRegistry.Lookup(name); + } + + private void GivenAServiceIsRegistered(string name, string address) + { + _service = new Service(name, address); + _serviceRepository.Set(_service); + } + + private void GivenAServiceToRegister(string name, string address) + { + _service = new Service(name, address); + } + + private void WhenIRegisterTheService() + { + _serviceRegistry.Register(_service); + } + + private void ThenTheServiceIsRegistered() + { + var serviceNameAndAddress = _serviceRepository.Get(_service.Name); + serviceNameAndAddress[0].Address.ShouldBe(_service.Address); + serviceNameAndAddress[0].Name.ShouldBe(_service.Name); + } + } + + public interface IServiceRegistry + { + void Register(Service serviceNameAndAddress); + List 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 Lookup(string name) + { + return _repository.Get(name); + } + } + + public class Service + { + public Service(string name, string address) + { + Name = name; + Address = address; + } + public string Name {get; private set;} + public string Address {get; private set;} + } + + public interface IServiceRepository + { + List Get(string serviceName); + void Set(Service serviceNameAndAddress); + } + + public class ServiceRepository : IServiceRepository + { + private Dictionary> _registeredServices; + + public ServiceRepository() + { + _registeredServices = new Dictionary>(); + } + + public List Get(string serviceName) + { + return _registeredServices[serviceName]; + } + + public void Set(Service serviceNameAndAddress) + { + List services; + if(_registeredServices.TryGetValue(serviceNameAndAddress.Name, out services)) + { + services.Add(serviceNameAndAddress); + _registeredServices[serviceNameAndAddress.Name] = services; + } + else + { + _registeredServices[serviceNameAndAddress.Name] = new List(){ serviceNameAndAddress }; + } + + } + } +} \ No newline at end of file From c3a47f66c88f9af98685becea2b70b4092fa921b Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 23 Jan 2017 12:13:24 +0000 Subject: [PATCH 03/29] merge --- .DS_Store | Bin 0 -> 6148 bytes Ocelot.sln | 2 + README.md | 11 +- appveyor.yml | 2 +- configuration-explanation.txt | 19 ++- .../Configuration/Builder/ReRouteBuilder.cs | 18 ++- .../Creator/FileOcelotConfigurationCreator.cs | 11 +- src/Ocelot/Configuration/File/FileReRoute.cs | 3 +- src/Ocelot/Configuration/ReRoute.cs | 13 +- .../DownstreamPathTemplateAlreadyUsedError.cs | 11 ++ ...wnstreamPathTemplateContainsSchemeError.cs | 12 ++ .../DownstreamTemplateAlreadyUsedError.cs | 11 -- .../DownstreamTemplateContainsHostError.cs | 12 -- .../DownstreamTemplateContainsSchemeError.cs | 12 -- .../Validator/FileConfigurationValidator.cs | 16 +-- .../ServiceCollectionExtensions.cs | 4 +- .../DownstreamRouteFinderMiddleware.cs | 2 +- .../DownstreamHostNullOrEmptyError.cs | 12 ++ .../DownstreamPathNullOrEmptyError.cs | 12 ++ .../DownstreamSchemeNullOrEmptyError.cs | 12 ++ .../DownstreamUrlCreator/IUrlBuilder.cs | 12 ++ .../DownstreamUrlCreatorMiddleware.cs | 39 ++++-- src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs | 47 +++++++ .../DownstreamUrlTemplateVariableReplacer.cs | 14 +- ...wnstreamUrlPathTemplateVariableReplacer.cs | 5 +- src/Ocelot/Errors/OcelotErrorCode.cs | 8 +- src/Ocelot/Values/DownstreamPath.cs | 12 ++ src/Ocelot/Values/DownstreamPathTemplate.cs | 12 ++ .../DownstreamUrl.cs | 4 +- src/Ocelot/Values/HostAndPort.cs | 14 ++ test/.DS_Store | Bin 0 -> 6148 bytes .../AuthenticationTests.cs | 41 ++++-- .../AuthorisationTests.cs | 10 +- test/Ocelot.AcceptanceTests/CachingTests.cs | 10 +- .../CaseSensitiveRoutingTests.cs | 30 ++++- .../ClaimsToHeadersForwardingTests.cs | 5 +- .../ClaimsToQueryStringForwardingTests.cs | 5 +- .../CustomMiddlewareTests.cs | 30 ++++- test/Ocelot.AcceptanceTests/RequestIdTests.cs | 15 ++- .../ReturnsErrorTests.cs | 2 +- test/Ocelot.AcceptanceTests/RoutingTests.cs | 91 ++++++++++++- test/Ocelot.ManualTest/Program.cs | 1 - test/Ocelot.ManualTest/configuration.json | 100 +++++++++++--- .../Claims/ClaimsBuilderMiddlewareTests.cs | 2 +- .../ConfigurationValidationTests.cs | 47 ++----- .../FileConfigurationCreatorTests.cs | 58 ++++---- .../InMemoryConfigurationRepositoryTests.cs | 10 +- .../DownstreamRouteFinderMiddlewareTests.cs | 2 +- .../DownstreamRouteFinderTests.cs | 14 +- .../DownstreamUrlCreatorMiddlewareTests.cs | 33 +++-- .../DownstreamUrlCreator/UrlBuilderTests.cs | 124 ++++++++++++++++++ ...eamUrlPathTemplateVariableReplacerTests.cs | 25 ++-- ...ttpRequestHeadersBuilderMiddlewareTests.cs | 2 +- .../QueryStringBuilderMiddlewareTests.cs | 2 +- .../RequestId/RequestIdMiddlewareTests.cs | 4 +- 55 files changed, 767 insertions(+), 258 deletions(-) create mode 100644 .DS_Store create mode 100644 src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs create mode 100644 src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs delete mode 100644 src/Ocelot/Configuration/Validator/DownstreamTemplateAlreadyUsedError.cs delete mode 100644 src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs delete mode 100644 src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs create mode 100644 src/Ocelot/Values/DownstreamPath.cs create mode 100644 src/Ocelot/Values/DownstreamPathTemplate.cs rename src/Ocelot/{DownstreamUrlCreator/UrlTemplateReplacer => Values}/DownstreamUrl.cs (74%) create mode 100644 src/Ocelot/Values/HostAndPort.cs create mode 100644 test/.DS_Store create mode 100644 test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a038c8775e5005f62d3b7c1adf4590d7305cf4fe GIT binary patch literal 6148 zcmeHKOHRWu5S^h}6tU@&rLWK%L{&IJE`XGvvS^#Cq+RnSr=s=fB0APXTBp6FA zAwI#dG>nSSKv-RY>dIDPu)1SCm|bZY71f>CiVwET-^B~3?1&$dIdQ4zy))np3>lc{ zbSC%z13sB-kw0|tnKR%F{4)l)sOoBgP1)T#+Me9C0qqe@MC_srAQ%sR0x*$t(l9DY6j@L1K>ra)gm~u+oPmKacPc|U literal 0 HcmV?d00001 diff --git a/Ocelot.sln b/Ocelot.sln index 5ebd0660a..0165beb06 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -17,8 +17,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Ocelot.nuspec = Ocelot.nuspec push-to-nuget.bat = push-to-nuget.bat README.md = README.md + run-acceptance-tests.bat = run-acceptance-tests.bat run-benchmarks.bat = run-benchmarks.bat run-tests.bat = run-tests.bat + run-unit-tests.bat = run-unit-tests.bat EndProjectSection EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot", "src\Ocelot\Ocelot.xproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}" diff --git a/README.md b/README.md index dd5f1043a..249a99a31 100644 --- a/README.md +++ b/README.md @@ -136,17 +136,20 @@ In order to set up a ReRoute you need to add one to the json array called ReRout the following. { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamPort": 80, + "DownstreamHost" "localhost" "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Put" } -The DownstreamTemplate is the URL that this request will be forwarded to. +The DownstreamPathTemplate,Scheme, Port and Host make the URL that this request will be forwarded to. The UpstreamTemplate is the URL that Ocelot will use to identity which -DownstreamTemplate to use for a given request. Finally the UpstreamHttpMethod is used so +DownstreamPathTemplate to use for a given request. Finally the UpstreamHttpMethod is used so Ocelot can distinguish between requests to the same URL and is obviously needed to work :) In Ocelot you can add placeholders for variables to your Templates in the form of {something}. -The placeholder needs to be in both the DownstreamTemplate and UpstreamTemplate. If it is +The placeholder needs to be in both the DownstreamPathTemplate and UpstreamTemplate. If it is Ocelot will attempt to replace the placeholder with the correct variable value from the Upstream URL when the request comes in. diff --git a/appveyor.yml b/appveyor.yml index 69ddc117e..831107225 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,6 +7,6 @@ build_script: test_script: - run-tests.bat after_test: -- push-to-nuget.bat %appveyor_build_version%-rc1 %nugetApiKey% +- push-to-nuget.bat %appveyor_build_version% %nugetApiKey% cache: - '%USERPROFILE%\.nuget\packages' \ No newline at end of file diff --git a/configuration-explanation.txt b/configuration-explanation.txt index 898be89fb..ad0204699 100644 --- a/configuration-explanation.txt +++ b/configuration-explanation.txt @@ -1,11 +1,20 @@ { "ReRoutes": [ { - # The url we are forwarding the request to, ocelot will not add a trailing slash - "DownstreamTemplate": "http://somehost.com/identityserverexample", - # The path we are listening on for this re route, Ocelot will add a trailing slash to - # this property. Then when a request is made Ocelot makes sure a trailing slash is added - # to that so everything matches + # The downstream path we are forwarding the request to, ocelot will not add a trailing slash. + # Ocelot replaces any placeholders {etc} with matched values from the incoming request. + "DownstreamPathTemplate": "/identityserverexample/{someid}/something", + # The scheme you want Ocelot to use when making the downstream request + "DownstreamScheme": "https", + # The port you want Ocelot to use when making the downstream request, will default to + # scheme if nothing set + "DownstreamPort": 80, + # The host address of the downstream service, should not have a trailing slash or scheme + # if there is a trailing slash Ocelot will remove it. + "DownstreamHost" "localhost" + # The path template we are listening on for this re route, Ocelot will add a trailing + # slash to this property. Then when a request is made Ocelot makes sure a trailing + # slash is added, so everything matches "UpstreamTemplate": "/identityserverexample", # The method we are listening for on this re route "UpstreamHttpMethod": "Get", diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 3f5ef40b5..1e06a4407 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using Ocelot.Values; namespace Ocelot.Configuration.Builder { public class ReRouteBuilder { - private string _downstreamTemplate; + private string _downstreamPathTemplate; private string _upstreamTemplate; private string _upstreamTemplatePattern; private string _upstreamHttpMethod; @@ -30,6 +31,7 @@ public class ReRouteBuilder private string _serviceDiscoveryAddress; private string _downstreamScheme; private string _downstreamHost; + private int _dsPort; public ReRouteBuilder() { @@ -72,9 +74,9 @@ public ReRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) return this; } - public ReRouteBuilder WithDownstreamTemplate(string input) + public ReRouteBuilder WithDownstreamPathTemplate(string input) { - _downstreamTemplate = input; + _downstreamPathTemplate = input; return this; } @@ -184,11 +186,17 @@ public ReRouteBuilder WithCacheOptions(CacheOptions input) return this; } + public ReRouteBuilder WithDownstreamPort(int port) + { + _dsPort = port; + return this; + } + public ReRoute Build() { - Func downstreamHostFunc = () => { return _downstreamHost; }; + Func downstreamHostFunc = () => new HostAndPort(_downstreamHost, _dsPort); - return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, + return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName, diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index c71aba23e..8884f0d9f 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -8,6 +8,7 @@ using Ocelot.Configuration.Validator; using Ocelot.Responses; using Ocelot.Utilities; +using Ocelot.Values; namespace Ocelot.Configuration.Creator { @@ -96,7 +97,7 @@ private ReRoute SetUpReRoute(FileReRoute reRoute, FileGlobalConfiguration global && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); - Func downstreamHostFunc = () => { return reRoute.DownstreamHost; }; + Func downstreamHostAndPortFunc = () => new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort); if (isAuthenticated) { @@ -109,22 +110,22 @@ private ReRoute SetUpReRoute(FileReRoute reRoute, FileGlobalConfiguration global var claimsToClaims = GetAddThingsToRequest(reRoute.AddClaimsToRequest); var claimsToQueries = GetAddThingsToRequest(reRoute.AddQueriesToRequest); - return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, + return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, authOptionsForRoute, claimsToHeaders, claimsToClaims, reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostFunc, reRoute.DownstreamScheme); + globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme); } - return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, + return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, null, new List(), new List(), reRoute.RouteClaimsRequirement, isAuthorised, new List(), requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostFunc, reRoute.DownstreamScheme); + globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme); } private string BuildUpstreamTemplate(FileReRoute reRoute) diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 3afa03ce3..a653224a5 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -14,7 +14,7 @@ public FileReRoute() FileCacheOptions = new FileCacheOptions(); } - public string DownstreamTemplate { get; set; } + public string DownstreamPathTemplate { get; set; } public string UpstreamTemplate { get; set; } public string UpstreamHttpMethod { get; set; } public FileAuthenticationOptions AuthenticationOptions { get; set; } @@ -28,5 +28,6 @@ public FileReRoute() public string ServiceName { get; set; } public string DownstreamScheme {get;set;} public string DownstreamHost {get;set;} + public int DownstreamPort { get; set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 8778e5f72..960374cc2 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,17 +1,18 @@ using System; using System.Collections.Generic; +using Ocelot.Values; namespace Ocelot.Configuration { public class ReRoute { - public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, + public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery, - string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func downstreamHost, string downstreamScheme) + string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func downstreamHostAndPort, string downstreamScheme) { - DownstreamTemplate = downstreamTemplate; + DownstreamPathTemplate = downstreamPathTemplate; UpstreamTemplate = upstreamTemplate; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; @@ -32,11 +33,11 @@ public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstre UseServiceDiscovery = useServiceDiscovery; ServiceDiscoveryProvider = serviceDiscoveryProvider; ServiceDiscoveryAddress = serviceDiscoveryAddress; - DownstreamHost = downstreamHost; + DownstreamHostAndPort = downstreamHostAndPort; DownstreamScheme = downstreamScheme; } - public string DownstreamTemplate { get; private set; } + public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } public string UpstreamTemplate { get; private set; } public string UpstreamTemplatePattern { get; private set; } public string UpstreamHttpMethod { get; private set; } @@ -54,7 +55,7 @@ public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstre public bool UseServiceDiscovery { get; private set;} public string ServiceDiscoveryProvider { get; private set;} public string ServiceDiscoveryAddress { get; private set;} - public Func DownstreamHost {get;private set;} + public Func DownstreamHostAndPort {get;private set;} public string DownstreamScheme {get;private set;} } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs new file mode 100644 index 000000000..e350753c8 --- /dev/null +++ b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs @@ -0,0 +1,11 @@ +using Ocelot.Errors; + +namespace Ocelot.Configuration.Validator +{ + public class DownstreamPathTemplateAlreadyUsedError : Error + { + public DownstreamPathTemplateAlreadyUsedError(string message) : base(message, OcelotErrorCode.DownstreampathTemplateAlreadyUsedError) + { + } + } +} diff --git a/src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs new file mode 100644 index 000000000..a3dfa3094 --- /dev/null +++ b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.Configuration.Validator +{ + public class DownstreamPathTemplateContainsSchemeError : Error + { + public DownstreamPathTemplateContainsSchemeError(string message) + : base(message, OcelotErrorCode.DownstreamPathTemplateContainsSchemeError) + { + } + } +} diff --git a/src/Ocelot/Configuration/Validator/DownstreamTemplateAlreadyUsedError.cs b/src/Ocelot/Configuration/Validator/DownstreamTemplateAlreadyUsedError.cs deleted file mode 100644 index b836b1ebc..000000000 --- a/src/Ocelot/Configuration/Validator/DownstreamTemplateAlreadyUsedError.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Validator -{ - public class DownstreamTemplateAlreadyUsedError : Error - { - public DownstreamTemplateAlreadyUsedError(string message) : base(message, OcelotErrorCode.DownstreamTemplateAlreadyUsedError) - { - } - } -} diff --git a/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs b/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs deleted file mode 100644 index 8d9dba92a..000000000 --- a/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Validator -{ - public class DownstreamTemplateContainsHostError : Error - { - public DownstreamTemplateContainsHostError(string message) - : base(message, OcelotErrorCode.DownstreamTemplateContainsHostError) - { - } - } -} diff --git a/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs b/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs deleted file mode 100644 index 1901fc88d..000000000 --- a/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Validator -{ - public class DownstreamTemplateContainsSchemeError : Error - { - public DownstreamTemplateContainsSchemeError(string message) - : base(message, OcelotErrorCode.DownstreamTemplateContainsSchemeError) - { - } - } -} diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs index 3a675d419..412613ebf 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs @@ -26,7 +26,7 @@ public Response IsValid(FileConfiguration configu return new OkResponse(result); } - result = CheckForReRoutesContainingDownstreamScheme(configuration); + result = CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(configuration); if (result.IsError) { @@ -70,25 +70,25 @@ private bool IsSupportedAuthenticationProvider(string provider) return Enum.TryParse(provider, true, out supportedProvider); } - private ConfigurationValidationResult CheckForReRoutesContainingDownstreamScheme(FileConfiguration configuration) + private ConfigurationValidationResult CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(FileConfiguration configuration) { var errors = new List(); foreach(var reRoute in configuration.ReRoutes) { - if(reRoute.DownstreamTemplate.Contains("https://") - || reRoute.DownstreamTemplate.Contains("http://")) + if(reRoute.DownstreamPathTemplate.Contains("https://") + || reRoute.DownstreamPathTemplate.Contains("http://")) { - errors.Add(new DownstreamTemplateContainsSchemeError($"{reRoute.DownstreamTemplate} contains scheme")); + errors.Add(new DownstreamPathTemplateContainsSchemeError($"{reRoute.DownstreamPathTemplate} contains scheme")); } } if(errors.Any()) { - return new ConfigurationValidationResult(false, errors); + return new ConfigurationValidationResult(true, errors); } - return new ConfigurationValidationResult(true, errors); + return new ConfigurationValidationResult(false, errors); } private ConfigurationValidationResult CheckForDupliateReRoutes(FileConfiguration configuration) @@ -105,7 +105,7 @@ private ConfigurationValidationResult CheckForDupliateReRoutes(FileConfiguration .Where(x => x.Skip(1).Any()); var errors = dupes - .Select(d => new DownstreamTemplateAlreadyUsedError(string.Format("Duplicate DownstreamTemplate: {0}", d.Key.UpstreamTemplate))) + .Select(d => new DownstreamPathTemplateAlreadyUsedError(string.Format("Duplicate DownstreamPath: {0}", d.Key.UpstreamTemplate))) .Cast() .ToList(); diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 04839abba..9f40b0098 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -18,6 +18,7 @@ using Ocelot.Configuration.Validator; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.DownstreamUrlCreator; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Headers; using Ocelot.Infrastructure.Claims.Parser; @@ -59,6 +60,7 @@ public static IServiceCollection AddOcelot(this IServiceCollection services) services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -69,7 +71,7 @@ public static IServiceCollection AddOcelot(this IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index a74b6316b..f445b46bf 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -44,7 +44,7 @@ public async Task Invoke(HttpContext context) return; } - _logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamTemplate}", downstreamRoute.Data.ReRoute.DownstreamTemplate); + _logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamPath}", downstreamRoute.Data.ReRoute.DownstreamPathTemplate); SetDownstreamRouteForThisRequest(downstreamRoute.Data); diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs new file mode 100644 index 000000000..8978f6650 --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.DownstreamUrlCreator +{ + public class DownstreamHostNullOrEmptyError : Error + { + public DownstreamHostNullOrEmptyError() + : base("downstream host was null or empty", OcelotErrorCode.DownstreamHostNullOrEmptyError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs new file mode 100644 index 000000000..fbc1a5f53 --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.DownstreamUrlCreator +{ + public class DownstreamPathNullOrEmptyError : Error + { + public DownstreamPathNullOrEmptyError() + : base("downstream path was null or empty", OcelotErrorCode.DownstreamPathNullOrEmptyError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs new file mode 100644 index 000000000..e52d3488b --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.DownstreamUrlCreator +{ + public class DownstreamSchemeNullOrEmptyError : Error + { + public DownstreamSchemeNullOrEmptyError() + : base("downstream scheme was null or empty", OcelotErrorCode.DownstreamSchemeNullOrEmptyError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs new file mode 100644 index 000000000..18683e625 --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs @@ -0,0 +1,12 @@ +using Ocelot.Configuration; +using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.DownstreamUrlCreator +{ + public interface IUrlBuilder + { + Response Build(string downstreamPath, string downstreamScheme, HostAndPort downstreamHostAndPort); + } +} diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 8d0af0bda..8144b42b9 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Ocelot.Configuration; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; @@ -11,17 +12,20 @@ namespace Ocelot.DownstreamUrlCreator.Middleware public class DownstreamUrlCreatorMiddleware : OcelotMiddleware { private readonly RequestDelegate _next; - private readonly IDownstreamUrlPathPlaceholderReplacer _urlReplacer; + private readonly IDownstreamPathPlaceholderReplacer _replacer; private readonly IOcelotLogger _logger; + private readonly IUrlBuilder _urlBuilder; public DownstreamUrlCreatorMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, - IDownstreamUrlPathPlaceholderReplacer urlReplacer, - IRequestScopedDataRepository requestScopedDataRepository) + IDownstreamPathPlaceholderReplacer replacer, + IRequestScopedDataRepository requestScopedDataRepository, + IUrlBuilder urlBuilder) :base(requestScopedDataRepository) { _next = next; - _urlReplacer = urlReplacer; + _replacer = replacer; + _urlBuilder = urlBuilder; _logger = loggerFactory.CreateLogger(); } @@ -29,19 +33,34 @@ public async Task Invoke(HttpContext context) { _logger.LogDebug("started calling downstream url creator middleware"); - var downstreamUrl = _urlReplacer.Replace(DownstreamRoute.ReRoute.DownstreamTemplate, DownstreamRoute.TemplatePlaceholderNameAndValues); + var dsPath = _replacer + .Replace(DownstreamRoute.ReRoute.DownstreamPathTemplate, DownstreamRoute.TemplatePlaceholderNameAndValues); - if (downstreamUrl.IsError) + if (dsPath.IsError) { - _logger.LogDebug("IDownstreamUrlPathPlaceholderReplacer returned an error, setting pipeline error"); + _logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error"); - SetPipelineError(downstreamUrl.Errors); + SetPipelineError(dsPath.Errors); return; } - _logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", downstreamUrl.Data.Value); + var dsScheme = DownstreamRoute.ReRoute.DownstreamScheme; - SetDownstreamUrlForThisRequest(downstreamUrl.Data.Value); + var dsHostAndPort = DownstreamRoute.ReRoute.DownstreamHostAndPort(); + + var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort); + + if (dsUrl.IsError) + { + _logger.LogDebug("IUrlBuilder returned an error, setting pipeline error"); + + SetPipelineError(dsUrl.Errors); + return; + } + + _logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", dsUrl.Data.Value); + + SetDownstreamUrlForThisRequest(dsUrl.Data.Value); _logger.LogDebug("calling next middleware"); diff --git a/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs new file mode 100644 index 000000000..2124ce3ba --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; +using Ocelot.Errors; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.DownstreamUrlCreator +{ + public class UrlBuilder : IUrlBuilder + { + public Response Build(string downstreamPath, string downstreamScheme, HostAndPort downstreamHostAndPort) + { + if (string.IsNullOrEmpty(downstreamPath)) + { + return new ErrorResponse(new List {new DownstreamPathNullOrEmptyError()}); + } + + if (string.IsNullOrEmpty(downstreamScheme)) + { + return new ErrorResponse(new List { new DownstreamSchemeNullOrEmptyError() }); + } + + if (string.IsNullOrEmpty(downstreamHostAndPort.DownstreamHost)) + { + return new ErrorResponse(new List { new DownstreamHostNullOrEmptyError() }); + } + + var builder = new UriBuilder + { + Host = downstreamHostAndPort.DownstreamHost, + Path = downstreamPath, + Scheme = downstreamScheme + }; + + if (downstreamHostAndPort.DownstreamPort > 0) + { + builder.Port = downstreamHostAndPort.DownstreamPort; + } + + var url = builder.Uri.ToString(); + + return new OkResponse(new DownstreamUrl(url)); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs index 9c19f2f99..9e9256315 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs @@ -1,25 +1,25 @@ using System.Collections.Generic; using System.Text; -using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; +using Ocelot.Values; namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer { - public class DownstreamUrlPathPlaceholderReplacer : IDownstreamUrlPathPlaceholderReplacer + public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer { - public Response Replace(string downstreamTemplate, List urlPathPlaceholderNameAndValues) + public Response Replace(DownstreamPathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues) { - var upstreamUrl = new StringBuilder(); + var downstreamPath = new StringBuilder(); - upstreamUrl.Append(downstreamTemplate); + downstreamPath.Append(downstreamPathTemplate.Value); foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues) { - upstreamUrl.Replace(placeholderVariableAndValue.TemplateVariableName, placeholderVariableAndValue.TemplateVariableValue); + downstreamPath.Replace(placeholderVariableAndValue.TemplateVariableName, placeholderVariableAndValue.TemplateVariableValue); } - return new OkResponse(new DownstreamUrl(upstreamUrl.ToString())); + return new OkResponse(new DownstreamPath(downstreamPath.ToString())); } } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs index 164c42eff..72d5d4b65 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; +using Ocelot.Values; namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer { - public interface IDownstreamUrlPathPlaceholderReplacer + public interface IDownstreamPathPlaceholderReplacer { - Response Replace(string downstreamTemplate, List urlPathPlaceholderNameAndValues); + Response Replace(DownstreamPathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues); } } \ No newline at end of file diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index f0a336f03..5de770cdd 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -4,7 +4,7 @@ public enum OcelotErrorCode { UnauthenticatedError, UnknownError, - DownstreamTemplateAlreadyUsedError, + DownstreampathTemplateAlreadyUsedError, UnableToFindDownstreamRouteError, CannotAddDataError, CannotFindDataError, @@ -18,7 +18,9 @@ public enum OcelotErrorCode UnauthorizedError, ClaimValueNotAuthorisedError, UserDoesNotHaveClaimError, - DownstreamTemplateContainsSchemeError, - DownstreamTemplateContainsHostError + DownstreamPathTemplateContainsSchemeError, + DownstreamPathNullOrEmptyError, + DownstreamSchemeNullOrEmptyError, + DownstreamHostNullOrEmptyError } } diff --git a/src/Ocelot/Values/DownstreamPath.cs b/src/Ocelot/Values/DownstreamPath.cs new file mode 100644 index 000000000..90f2e83eb --- /dev/null +++ b/src/Ocelot/Values/DownstreamPath.cs @@ -0,0 +1,12 @@ +namespace Ocelot.Values +{ + public class DownstreamPath + { + public DownstreamPath(string value) + { + Value = value; + } + + public string Value { get; private set; } + } +} diff --git a/src/Ocelot/Values/DownstreamPathTemplate.cs b/src/Ocelot/Values/DownstreamPathTemplate.cs new file mode 100644 index 000000000..a4c720eb3 --- /dev/null +++ b/src/Ocelot/Values/DownstreamPathTemplate.cs @@ -0,0 +1,12 @@ +namespace Ocelot.Values +{ + public class DownstreamPathTemplate + { + public DownstreamPathTemplate(string value) + { + Value = value; + } + + public string Value { get; private set; } + } +} diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrl.cs b/src/Ocelot/Values/DownstreamUrl.cs similarity index 74% rename from src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrl.cs rename to src/Ocelot/Values/DownstreamUrl.cs index ea90179eb..f809c84b9 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrl.cs +++ b/src/Ocelot/Values/DownstreamUrl.cs @@ -1,4 +1,4 @@ -namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer +namespace Ocelot.Values { public class DownstreamUrl { @@ -9,4 +9,4 @@ public DownstreamUrl(string value) public string Value { get; private set; } } -} +} \ No newline at end of file diff --git a/src/Ocelot/Values/HostAndPort.cs b/src/Ocelot/Values/HostAndPort.cs new file mode 100644 index 000000000..cd336deca --- /dev/null +++ b/src/Ocelot/Values/HostAndPort.cs @@ -0,0 +1,14 @@ +namespace Ocelot.Values +{ + public class HostAndPort + { + public HostAndPort(string downstreamHost, int downstreamPort) + { + DownstreamHost = downstreamHost; + DownstreamPort = downstreamPort; + } + + public string DownstreamHost { get; private set; } + public int DownstreamPort { get; private set; } + } +} \ No newline at end of file diff --git a/test/.DS_Store b/test/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e73160dc2e49db945fa202355c459521f733d7f8 GIT binary patch literal 6148 zcmeHKJxc>o5S-N%0h=o=-!BmS2Su<>k}2FfE1W2aGu-F`~MC7hxva>(oPCUfq$idE!La$imz0?b@p=JYa9K6?lqru rH?D)i5bc;4?U);H$Cpu*b x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 201, string.Empty)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenThePostHasContent("postContent")) @@ -74,7 +81,10 @@ public void should_return_401_using_identity_server_reference_token() { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions @@ -91,7 +101,7 @@ public void should_return_401_using_identity_server_reference_token() }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Reference)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 201, string.Empty)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenThePostHasContent("postContent")) @@ -109,7 +119,10 @@ public void should_return_response_200_using_identity_server() { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions @@ -126,7 +139,7 @@ public void should_return_response_200_using_identity_server() }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 200, "Hello from Laura")) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) @@ -146,7 +159,10 @@ public void should_return_201_using_identity_server_access_token() { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions @@ -163,7 +179,7 @@ public void should_return_201_using_identity_server_access_token() }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 201, string.Empty)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) @@ -183,7 +199,10 @@ public void should_return_201_using_identity_server_reference_token() { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions @@ -200,7 +219,7 @@ public void should_return_201_using_identity_server_reference_token() }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Reference)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 201, string.Empty)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs index 4ceb03f6f..1f86c6ff8 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -37,7 +37,10 @@ public void should_return_response_200_authorising_route() { new FileReRoute { - DownstreamTemplate = "http://localhost:51876/", + DownstreamPathTemplate = "/", + DownstreamPort = 51876, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions @@ -91,7 +94,10 @@ public void should_return_response_403_authorising_route() { new FileReRoute { - DownstreamTemplate = "http://localhost:51876/", + DownstreamPathTemplate = "/", + DownstreamPort = 51876, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions diff --git a/test/Ocelot.AcceptanceTests/CachingTests.cs b/test/Ocelot.AcceptanceTests/CachingTests.cs index 34f10b7aa..e4e628afa 100644 --- a/test/Ocelot.AcceptanceTests/CachingTests.cs +++ b/test/Ocelot.AcceptanceTests/CachingTests.cs @@ -31,7 +31,10 @@ public void should_return_cached_response() { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", FileCacheOptions = new FileCacheOptions @@ -64,7 +67,10 @@ public void should_not_return_cached_response_as_ttl_expires() { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", FileCacheOptions = new FileCacheOptions diff --git a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs index 81a12381b..81824602b 100644 --- a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs @@ -30,7 +30,10 @@ public void should_return_response_200_when_global_ignore_case_sensitivity_set() { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get" } @@ -54,7 +57,10 @@ public void should_return_response_200_when_reroute_ignore_case_sensitivity_set( { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false @@ -79,7 +85,10 @@ public void should_return_response_404_when_reroute_respect_case_sensitivity_set { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -104,7 +113,10 @@ public void should_return_response_200_when_reroute_respect_case_sensitivity_set { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/PRODUCTS/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -129,7 +141,10 @@ public void should_return_response_404_when_global_respect_case_sensitivity_set( { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -154,7 +169,10 @@ public void should_return_response_200_when_global_respect_case_sensitivity_set( { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/PRODUCTS/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true diff --git a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs index 160686d56..08bbd968c 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs @@ -51,7 +51,10 @@ public void should_return_response_200_and_foward_claim_as_header() { new FileReRoute { - DownstreamTemplate = "http://localhost:52876/", + DownstreamPathTemplate = "/", + DownstreamPort = 52876, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions diff --git a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs index 36583018f..04dc25db4 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs @@ -51,7 +51,10 @@ public void should_return_response_200_and_foward_claim_as_query_string() { new FileReRoute { - DownstreamTemplate = "http://localhost:57876/", + DownstreamPathTemplate = "/", + DownstreamPort = 57876, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index 1a267a98a..f6f6de204 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -45,7 +45,10 @@ public void should_call_pre_query_string_builder_middleware() { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -79,7 +82,10 @@ public void should_call_authorisation_middleware() { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -113,7 +119,10 @@ public void should_call_authentication_middleware() { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "41879/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -147,7 +156,10 @@ public void should_call_pre_error_middleware() { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -181,7 +193,10 @@ public void should_call_pre_authorisation_middleware() { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -215,7 +230,10 @@ public void should_call_pre_http_authentication_middleware() { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } diff --git a/test/Ocelot.AcceptanceTests/RequestIdTests.cs b/test/Ocelot.AcceptanceTests/RequestIdTests.cs index 512736ae0..9334786ba 100644 --- a/test/Ocelot.AcceptanceTests/RequestIdTests.cs +++ b/test/Ocelot.AcceptanceTests/RequestIdTests.cs @@ -33,7 +33,10 @@ public void should_use_default_request_id_and_forward() { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", RequestIdKey = _steps.RequestIdKey @@ -58,7 +61,10 @@ public void should_use_request_id_and_forward() { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", RequestIdKey = _steps.RequestIdKey @@ -85,7 +91,10 @@ public void should_use_global_request_id_and_forward() { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index 902de6623..813077813 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -29,7 +29,7 @@ public void should_return_response_200_and_foward_claim_as_header() { new FileReRoute { - DownstreamTemplate = "http://localhost:53876/", + DownstreamPathTemplate = "http://localhost:53876/", UpstreamTemplate = "/", UpstreamHttpMethod = "Get" } diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 84e00d5b8..4f97114f9 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -40,7 +40,10 @@ public void should_return_response_200_with_simple_url() { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -56,6 +59,62 @@ public void should_return_response_200_with_simple_url() .BDDfy(); } + [Fact] + public void should_return_response_200_when_path_missing_forward_slash_as_first_char() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "api/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_when_host_has_trailing_slash() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products", + DownstreamScheme = "http", + DownstreamHost = "localhost/", + DownstreamPort = 51879, + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + [Fact] public void should_not_care_about_no_trailing() { @@ -65,7 +124,10 @@ public void should_not_care_about_no_trailing() { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/products", + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products/", UpstreamHttpMethod = "Get", } @@ -90,7 +152,10 @@ public void should_not_care_about_trailing() { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/products", + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products", UpstreamHttpMethod = "Get", } @@ -115,7 +180,10 @@ public void should_return_not_found() { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/products", + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } @@ -139,7 +207,10 @@ public void should_return_response_200_with_complex_url() { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get" } @@ -164,7 +235,10 @@ public void should_return_response_201_with_simple_url() { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamHost = "localhost", + DownstreamPort = 51879, + DownstreamScheme = "http", UpstreamTemplate = "/", UpstreamHttpMethod = "Post" } @@ -189,8 +263,11 @@ public void should_return_response_201_with_complex_query_string() { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/newThing", + DownstreamPathTemplate = "/newThing", UpstreamTemplate = "/newThing", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamHttpMethod = "Get", } } diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index 7b6b8535c..a049d3eab 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -10,7 +10,6 @@ public static void Main(string[] args) var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() .UseStartup() .Build(); diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 15276d6e3..f7e2bb75f 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -1,7 +1,10 @@ { "ReRoutes": [ { - "DownstreamTemplate": "http://localhost:52876/", + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHost": "localhost", + "DownstreamPort": 52876, "UpstreamTemplate": "/identityserverexample", "UpstreamHttpMethod": "Get", "AuthenticationOptions": { @@ -38,108 +41,165 @@ "RequestIdKey": "OcRequestId" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts", + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Get" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}/comments", + "DownstreamPathTemplate": "/posts/{postId}/comments", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}/comments", "UpstreamHttpMethod": "Get" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/comments", + "DownstreamPathTemplate": "/comments", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/comments", "UpstreamHttpMethod": "Get" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts", + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts", "UpstreamHttpMethod": "Post" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Put" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Patch" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Delete" }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products", + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/products", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products/{productId}", + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/products/{productId}", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products", + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/products", "UpstreamHttpMethod": "Post", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products/{productId}", + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/products/{productId}", "UpstreamHttpMethod": "Put", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products/{productId}", + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/products/{productId}", "UpstreamHttpMethod": "Delete", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers", + "DownstreamPathTemplate": "/api/customers", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers/{customerId}", + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers", + "DownstreamPathTemplate": "/api/customers", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers", "UpstreamHttpMethod": "Post", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers/{customerId}", + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Put", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers/{customerId}", + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Delete", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts", + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } diff --git a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs index e3cccdc0b..8822e6b2f 100644 --- a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs @@ -67,7 +67,7 @@ public void should_call_claims_to_request_correctly() { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithClaimsToClaims(new List { new ClaimToThing("sub", "UserType", "|", 0) diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs index 174ea8bc1..8a3e24f98 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs @@ -10,8 +10,8 @@ namespace Ocelot.UnitTests.Configuration { public class ConfigurationValidationTests { - private FileConfiguration _fileConfiguration; private readonly IConfigurationValidator _configurationValidator; + private FileConfiguration _fileConfiguration; private Response _result; public ConfigurationValidationTests() @@ -22,32 +22,13 @@ public ConfigurationValidationTests() [Fact] public void configuration_is_invalid_if_scheme_in_downstream_template() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamTemplate = "http://www.bbc.co.uk/api/products/{productId}", - UpstreamTemplate = "http://asdf.com" - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_if_host_in_downstream_template() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "www.bbc.co.uk/api/products/{productId}", + DownstreamPathTemplate = "http://www.bbc.co.uk/api/products/{productId}", UpstreamTemplate = "http://asdf.com" } } @@ -60,13 +41,13 @@ public void configuration_is_invalid_if_host_in_downstream_template() [Fact] public void configuration_is_valid_with_one_reroute() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com" } } @@ -79,13 +60,13 @@ public void configuration_is_valid_with_one_reroute() [Fact] public void configuration_is_valid_with_valid_authentication_provider() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com", AuthenticationOptions = new FileAuthenticationOptions { @@ -102,13 +83,13 @@ public void configuration_is_valid_with_valid_authentication_provider() [Fact] public void configuration_is_invalid_with_invalid_authentication_provider() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com", AuthenticationOptions = new FileAuthenticationOptions { @@ -126,25 +107,25 @@ public void configuration_is_invalid_with_invalid_authentication_provider() [Fact] public void configuration_is_not_valid_with_duplicate_reroutes() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com" }, new FileReRoute { - DownstreamTemplate = "http://www.bbc.co.uk", + DownstreamPathTemplate = "http://www.bbc.co.uk", UpstreamTemplate = "http://asdf.com" } } })) .When(x => x.WhenIValidateTheConfiguration()) .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorIs()) + .And(x => x.ThenTheErrorIs()) .BDDfy(); } @@ -173,4 +154,4 @@ private void ThenTheErrorIs() _result.Data.Errors[0].ShouldBeOfType(); } } -} +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 4207a333f..fc80a4787 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -46,7 +46,7 @@ public void should_use_downstream_host() { DownstreamHost = "127.0.0.1", UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } }, @@ -57,7 +57,7 @@ public void should_use_downstream_host() { new ReRouteBuilder() .WithDownstreamHost("127.0.0.1") - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -76,7 +76,7 @@ public void should_use_downstream_scheme() { DownstreamScheme = "https", UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } }, @@ -87,7 +87,7 @@ public void should_use_downstream_scheme() { new ReRouteBuilder() .WithDownstreamScheme("https") - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -106,7 +106,7 @@ public void should_use_service_discovery_for_downstream_service_host() new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, ServiceName = "ProductService" @@ -126,7 +126,7 @@ public void should_use_service_discovery_for_downstream_service_host() .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -149,7 +149,7 @@ public void should_not_use_service_discovery_for_downstream_host_url_when_no_ser new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, } @@ -160,7 +160,7 @@ public void should_not_use_service_discovery_for_downstream_host_url_when_no_ser .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -182,7 +182,7 @@ public void should_use_reroute_case_sensitivity_value() new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false } @@ -193,7 +193,7 @@ public void should_use_reroute_case_sensitivity_value() .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -212,7 +212,7 @@ public void should_set_upstream_template_pattern_to_ignore_case_sensitivity() new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get" } } @@ -222,7 +222,7 @@ public void should_set_upstream_template_pattern_to_ignore_case_sensitivity() .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -241,7 +241,7 @@ public void should_set_upstream_template_pattern_to_respect_case_sensitivity() new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -252,7 +252,7 @@ public void should_set_upstream_template_pattern_to_respect_case_sensitivity() .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -271,7 +271,7 @@ public void should_set_global_request_id_key() new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -286,7 +286,7 @@ public void should_set_global_request_id_key() .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -306,7 +306,7 @@ public void should_create_template_pattern_that_matches_anything_to_end_of_strin new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -317,7 +317,7 @@ public void should_create_template_pattern_that_matches_anything_to_end_of_strin .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -332,7 +332,7 @@ public void should_create_with_headers_to_extract() var expected = new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -355,7 +355,7 @@ public void should_create_with_headers_to_extract() new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, AuthenticationOptions = new FileAuthenticationOptions @@ -395,7 +395,7 @@ public void should_create_with_authentication_properties() var expected = new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -414,7 +414,7 @@ public void should_create_with_authentication_properties() new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, AuthenticationOptions = new FileAuthenticationOptions @@ -446,7 +446,7 @@ public void should_create_template_pattern_that_matches_more_than_one_placeholde new FileReRoute { UpstreamTemplate = "/api/products/{productId}/variants/{variantId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -457,7 +457,7 @@ public void should_create_template_pattern_that_matches_more_than_one_placeholde .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}/variants/{variantId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") @@ -476,7 +476,7 @@ public void should_create_template_pattern_that_matches_more_than_one_placeholde new FileReRoute { UpstreamTemplate = "/api/products/{productId}/variants/{variantId}/", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -487,7 +487,7 @@ public void should_create_template_pattern_that_matches_more_than_one_placeholde .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}/variants/{variantId}/") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") @@ -506,7 +506,7 @@ public void should_create_template_pattern_that_matches_to_end_of_string() new FileReRoute { UpstreamTemplate = "/", - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -517,7 +517,7 @@ public void should_create_template_pattern_that_matches_to_end_of_string() .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/api/products/") + .WithDownstreamPathTemplate("/api/products/") .WithUpstreamTemplate("/") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/$") @@ -553,7 +553,7 @@ private void ThenTheReRoutesAre(List expectedReRoutes) var result = _config.Data.ReRoutes[i]; var expected = expectedReRoutes[i]; - result.DownstreamTemplate.ShouldBe(expected.DownstreamTemplate); + result.DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate.Value); result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); result.UpstreamTemplate.ShouldBe(expected.UpstreamTemplate); result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern); diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index 8f7af24e3..ec46f914f 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -44,7 +44,7 @@ public void can_get_config() private void ThenTheConfigurationIsReturned() { - _getResult.Data.ReRoutes[0].DownstreamTemplate.ShouldBe("initial"); + _getResult.Data.ReRoutes[0].DownstreamPathTemplate.Value.ShouldBe("initial"); } private void WhenIGetTheConfiguration() @@ -75,16 +75,16 @@ private void ThenNoErrorsAreReturned() class FakeConfig : IOcelotConfiguration { - private readonly string _downstreamTemplate; + private readonly string _downstreamTemplatePath; - public FakeConfig(string downstreamTemplate) + public FakeConfig(string downstreamTemplatePath) { - _downstreamTemplate = downstreamTemplate; + _downstreamTemplatePath = downstreamTemplatePath; } public List ReRoutes => new List { - new ReRouteBuilder().WithDownstreamTemplate(_downstreamTemplate).Build() + new ReRouteBuilder().WithDownstreamPathTemplate(_downstreamTemplatePath).Build() }; } } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 22dfea80b..0d5a6d48a 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -61,7 +61,7 @@ public DownstreamRouteFinderMiddlewareTests() [Fact] public void should_call_scoped_data_repository_correctly() { - this.Given(x => x.GivenTheDownStreamRouteFinderReturns(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("any old string").Build()))) + this.Given(x => x.GivenTheDownStreamRouteFinderReturns(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index bb390d327..c0afca426 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -44,7 +44,7 @@ public void should_return_route() .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPath") + .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("someUpstreamPath") @@ -57,7 +57,7 @@ public void should_return_route() .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPath") + .WithDownstreamPathTemplate("someDownstreamPath") .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -75,13 +75,13 @@ public void should_return_correct_route_for_http_verb() .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPath") + .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("") .Build(), new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPathForAPost") + .WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithUpstreamTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Post") .WithUpstreamTemplatePattern("") @@ -94,7 +94,7 @@ public void should_return_correct_route_for_http_verb() .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPathForAPost") + .WithDownstreamPathTemplate("someDownstreamPathForAPost") .Build() ))) .BDDfy(); @@ -107,7 +107,7 @@ public void should_not_return_route() .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamTemplate("somPath") + .WithDownstreamPathTemplate("somPath") .WithUpstreamTemplate("somePath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("somePath") @@ -174,7 +174,7 @@ private void WhenICallTheFinder() private void ThenTheFollowingIsReturned(DownstreamRoute expected) { - _result.Data.ReRoute.DownstreamTemplate.ShouldBe(expected.ReRoute.DownstreamTemplate); + _result.Data.ReRoute.DownstreamPathTemplate.Value.ShouldBe(expected.ReRoute.DownstreamPathTemplate.Value); for (int i = 0; i < _result.Data.TemplatePlaceholderNameAndValues.Count; i++) { diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 98bc5f0b6..5581a32ee 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -7,15 +7,18 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; +using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.DownstreamUrlCreator; using Ocelot.DownstreamUrlCreator.Middleware; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Responses; +using Ocelot.Values; using TestStack.BDDfy; using Xunit; @@ -23,21 +26,23 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator { public class DownstreamUrlCreatorMiddlewareTests : IDisposable { - private readonly Mock _downstreamUrlTemplateVariableReplacer; + private readonly Mock _downstreamUrlTemplateVariableReplacer; private readonly Mock _scopedRepository; + private readonly Mock _urlBuilder; private readonly string _url; private readonly TestServer _server; private readonly HttpClient _client; private Response _downstreamRoute; private HttpResponseMessage _result; + private OkResponse _downstreamPath; private OkResponse _downstreamUrl; public DownstreamUrlCreatorMiddlewareTests() { _url = "http://localhost:51879"; - _downstreamUrlTemplateVariableReplacer = new Mock(); + _downstreamUrlTemplateVariableReplacer = new Mock(); _scopedRepository = new Mock(); - + _urlBuilder = new Mock(); var builder = new WebHostBuilder() .ConfigureServices(x => { @@ -45,6 +50,7 @@ public DownstreamUrlCreatorMiddlewareTests() x.AddLogging(); x.AddSingleton(_downstreamUrlTemplateVariableReplacer.Object); x.AddSingleton(_scopedRepository.Object); + x.AddSingleton(_urlBuilder.Object); }) .UseUrls(_url) .UseKestrel() @@ -61,21 +67,30 @@ public DownstreamUrlCreatorMiddlewareTests() } [Fact] - public void should_call_scoped_data_repository_correctly() + public void should_call_dependencies_correctly() { - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("any old string").Build()))) - .And(x => x.TheUrlReplacerReturns("any old string")) + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build()))) + .And(x => x.TheUrlReplacerReturns("/api/products/1")) + .And(x => x.TheUrlBuilderReturns("http://www.bbc.co.uk/api/products/1")) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); } + private void TheUrlBuilderReturns(string dsUrl) + { + _downstreamUrl = new OkResponse(new DownstreamUrl(dsUrl)); + _urlBuilder + .Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(_downstreamUrl); + } + private void TheUrlReplacerReturns(string downstreamUrl) { - _downstreamUrl = new OkResponse(new DownstreamUrl(downstreamUrl)); + _downstreamPath = new OkResponse(new DownstreamPath(downstreamUrl)); _downstreamUrlTemplateVariableReplacer - .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) - .Returns(_downstreamUrl); + .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) + .Returns(_downstreamPath); } private void ThenTheScopedDataRepositoryIsCalledCorrectly() diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs new file mode 100644 index 000000000..7e512798b --- /dev/null +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs @@ -0,0 +1,124 @@ +using System; +using Ocelot.Configuration; +using Ocelot.DownstreamUrlCreator; +using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DownstreamUrlCreator +{ + public class UrlBuilderTests + { + private readonly IUrlBuilder _urlBuilder; + private string _dsPath; + private string _dsScheme; + private string _dsHost; + private int _dsPort; + + private Response _result; + + public UrlBuilderTests() + { + _urlBuilder = new UrlBuilder(); + } + + [Fact] + public void should_return_error_when_downstream_path_is_null() + { + this.Given(x => x.GivenADownstreamPath(null)) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenThereIsAnErrorOfType()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_downstream_scheme_is_null() + { + this.Given(x => x.GivenADownstreamScheme(null)) + .And(x => x.GivenADownstreamPath("test")) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenThereIsAnErrorOfType()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_downstream_host_is_null() + { + this.Given(x => x.GivenADownstreamScheme(null)) + .And(x => x.GivenADownstreamPath("test")) + .And(x => x.GivenADownstreamScheme("test")) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenThereIsAnErrorOfType()) + .BDDfy(); + } + + [Fact] + public void should_not_use_port_if_zero() + { + this.Given(x => x.GivenADownstreamPath("/api/products/1")) + .And(x => x.GivenADownstreamScheme("http")) + .And(x => x.GivenADownstreamHost("127.0.0.1")) + .And(x => x.GivenADownstreamPort(0)) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenTheUrlIsReturned("http://127.0.0.1/api/products/1")) + .And(x => x.ThenTheUrlIsWellFormed()) + .BDDfy(); + } + + [Fact] + public void should_build_well_formed_uri() + { + this.Given(x => x.GivenADownstreamPath("/api/products/1")) + .And(x => x.GivenADownstreamScheme("http")) + .And(x => x.GivenADownstreamHost("127.0.0.1")) + .And(x => x.GivenADownstreamPort(5000)) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenTheUrlIsReturned("http://127.0.0.1:5000/api/products/1")) + .And(x => x.ThenTheUrlIsWellFormed()) + .BDDfy(); + } + + private void ThenThereIsAnErrorOfType() + { + _result.Errors[0].ShouldBeOfType(); + } + + private void GivenADownstreamPath(string dsPath) + { + _dsPath = dsPath; + } + + private void GivenADownstreamScheme(string dsScheme) + { + _dsScheme = dsScheme; + } + + private void GivenADownstreamHost(string dsHost) + { + _dsHost = dsHost; + } + + private void GivenADownstreamPort(int dsPort) + { + _dsPort = dsPort; + } + + private void WhenIBuildTheUrl() + { + _result = _urlBuilder.Build(_dsPath, _dsScheme, new HostAndPort(_dsHost, _dsPort)); + } + + private void ThenTheUrlIsReturned(string expected) + { + _result.Data.Value.ShouldBe(expected); + } + + private void ThenTheUrlIsWellFormed() + { + Uri.IsWellFormedUriString(_result.Data.Value, UriKind.Absolute).ShouldBeTrue(); + } + } +} diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs index b1ad369bf..a7a5a89b0 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs @@ -4,6 +4,7 @@ using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Responses; +using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -13,12 +14,12 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer public class UpstreamUrlPathTemplateVariableReplacerTests { private DownstreamRoute _downstreamRoute; - private Response _result; - private readonly IDownstreamUrlPathPlaceholderReplacer _downstreamUrlPathReplacer; + private Response _result; + private readonly IDownstreamPathPlaceholderReplacer _downstreamPathReplacer; public UpstreamUrlPathTemplateVariableReplacerTests() { - _downstreamUrlPathReplacer = new DownstreamUrlPathPlaceholderReplacer(); + _downstreamPathReplacer = new DownstreamTemplatePathPlaceholderReplacer(); } [Fact] @@ -33,7 +34,7 @@ public void can_replace_no_template_variables() [Fact] public void can_replace_no_template_variables_with_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("/")) .BDDfy(); @@ -42,7 +43,7 @@ public void can_replace_no_template_variables_with_slash() [Fact] public void can_replace_url_no_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("api").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api")) .BDDfy(); @@ -51,7 +52,7 @@ public void can_replace_url_no_slash() [Fact] public void can_replace_url_one_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("api/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/")) .BDDfy(); @@ -60,7 +61,7 @@ public void can_replace_url_one_slash() [Fact] public void can_replace_url_multiple_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("api/product/products/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api/product/products/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/product/products/")) .BDDfy(); @@ -74,7 +75,7 @@ public void can_replace_url_one_template_variable() new UrlPathPlaceholderNameAndValue("{productId}", "1") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/products/{productId}/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/")) .BDDfy(); @@ -88,7 +89,7 @@ public void can_replace_url_one_template_variable_with_path_after() new UrlPathPlaceholderNameAndValue("{productId}", "1") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/products/{productId}/variants").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/variants").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants")) .BDDfy(); @@ -103,7 +104,7 @@ public void can_replace_url_two_template_variable() new UrlPathPlaceholderNameAndValue("{variantId}", "12") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/products/{productId}/variants/{variantId}").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/variants/{variantId}").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants/12")) .BDDfy(); @@ -119,7 +120,7 @@ public void can_replace_url_three_template_variable() new UrlPathPlaceholderNameAndValue("{categoryId}", "34") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/category/34/products/1/variants/12")) .BDDfy(); @@ -132,7 +133,7 @@ private void GivenThereIsAUrlMatch(DownstreamRoute downstreamRoute) private void WhenIReplaceTheTemplateVariables() { - _result = _downstreamUrlPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); + _result = _downstreamPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamPathTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); } private void ThenTheDownstreamUrlPathIsReturned(string expected) diff --git a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs index b85802afa..3516d26b4 100644 --- a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs @@ -67,7 +67,7 @@ public void should_call_add_headers_to_request_correctly() { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithClaimsToHeaders(new List { new ClaimToThing("UserId", "Subject", "", 0) diff --git a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs index e4c7375ec..39b329378 100644 --- a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs @@ -65,7 +65,7 @@ public void should_call_add_queries_correctly() { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithClaimsToQueries(new List { new ClaimToThing("UserId", "Subject", "", 0) diff --git a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs index 8c0237839..543613a8f 100644 --- a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs @@ -71,7 +71,7 @@ public void should_add_request_id_to_repository() { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithRequestIdKey("LSRequestId").Build()); var requestId = Guid.NewGuid().ToString(); @@ -88,7 +88,7 @@ public void should_add_trace_indentifier_to_repository() { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithRequestIdKey("LSRequestId").Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) From 4a43accc4688012bbb6d07ee6d24a1dcf8789214 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sun, 29 Jan 2017 09:41:05 +0000 Subject: [PATCH 04/29] implementing load balancers --- .../DownstreamUrlCreatorMiddleware.cs | 8 + src/Ocelot/Errors/OcelotErrorCode.cs | 4 +- src/Ocelot/Values/HostAndPort.cs | 5 + test/Ocelot.UnitTests/LeastConnectionTests.cs | 362 ++++++++++++++++++ test/Ocelot.UnitTests/RoundRobinTests.cs | 50 ++- test/Ocelot.UnitTests/ServiceRegistryTests.cs | 25 +- 6 files changed, 427 insertions(+), 27 deletions(-) create mode 100644 test/Ocelot.UnitTests/LeastConnectionTests.cs diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 8144b42b9..b4d73c7ce 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -46,12 +46,18 @@ public async Task Invoke(HttpContext context) var dsScheme = DownstreamRoute.ReRoute.DownstreamScheme; + //here we could have a lb factory that takes stuff or we could just get the load balancer from the reRoute? + //returns the lb for this request + + //lease the next address from the lb + var dsHostAndPort = DownstreamRoute.ReRoute.DownstreamHostAndPort(); var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort); if (dsUrl.IsError) { + //todo - release the lb connection? _logger.LogDebug("IUrlBuilder returned an error, setting pipeline error"); SetPipelineError(dsUrl.Errors); @@ -66,6 +72,8 @@ public async Task Invoke(HttpContext context) await _next.Invoke(context); + //todo - release the lb connection? + _logger.LogDebug("succesfully called next middleware"); } } diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index 5de770cdd..85c3e0970 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -21,6 +21,8 @@ public enum OcelotErrorCode DownstreamPathTemplateContainsSchemeError, DownstreamPathNullOrEmptyError, DownstreamSchemeNullOrEmptyError, - DownstreamHostNullOrEmptyError + DownstreamHostNullOrEmptyError, + ServicesAreNullError, + ServicesAreEmptyError } } diff --git a/src/Ocelot/Values/HostAndPort.cs b/src/Ocelot/Values/HostAndPort.cs index cd336deca..f8769743d 100644 --- a/src/Ocelot/Values/HostAndPort.cs +++ b/src/Ocelot/Values/HostAndPort.cs @@ -10,5 +10,10 @@ public HostAndPort(string downstreamHost, int downstreamPort) public string DownstreamHost { get; private set; } public int DownstreamPort { get; private set; } + + public override string ToString() + { + return $"{DownstreamHost}:{DownstreamPort}"; + } } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LeastConnectionTests.cs new file mode 100644 index 000000000..758d1917e --- /dev/null +++ b/test/Ocelot.UnitTests/LeastConnectionTests.cs @@ -0,0 +1,362 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Ocelot.Errors; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests +{ + public class LeastConnectionTests + { + private HostAndPort _hostAndPort; + private Response _result; + private LeastConnection _leastConnection; + private List _services; + + public LeastConnectionTests() + { + } + + [Fact] + public void should_get_next_url() + { + var serviceName = "products"; + + var hostAndPort = new HostAndPort("localhost", 80); + + var availableServices = new List + { + new Service(serviceName, hostAndPort) + }; + + 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 + { + new Service(serviceName, new HostAndPort("127.0.0.1", 80)), + new Service(serviceName, new HostAndPort("127.0.0.2", 80)), + new Service(serviceName, new HostAndPort("127.0.0.3", 80)) + }; + + _services = availableServices; + _leastConnection = new LeastConnection(() => _services, serviceName); + + var response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[2].HostAndPort.DownstreamHost); + } + + [Fact] + public void should_build_connections_per_service() + { + var serviceName = "products"; + + var availableServices = new List + { + new Service(serviceName, new HostAndPort("127.0.0.1", 80)), + new Service(serviceName, new HostAndPort("127.0.0.2", 80)), + }; + + _services = availableServices; + _leastConnection = new LeastConnection(() => _services, serviceName); + + var response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + } + + [Fact] + public void should_release_connection() + { + var serviceName = "products"; + + var availableServices = new List + { + new Service(serviceName, new HostAndPort("127.0.0.1", 80)), + new Service(serviceName, new HostAndPort("127.0.0.2", 80)), + }; + + _services = availableServices; + _leastConnection = new LeastConnection(() => _services, serviceName); + + var response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + 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(); + + 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(), serviceName)) + .When(x => x.WhenIGetTheNextHostAndPort()) + .Then(x => x.ThenServiceAreEmptyErrorIsReturned()) + .BDDfy(); + } + + private void ThenServiceAreNullErrorIsReturned() + { + _result.IsError.ShouldBeTrue(); + _result.Errors[0].ShouldBeOfType(); + } + + private void ThenServiceAreEmptyErrorIsReturned() + { + _result.IsError.ShouldBeTrue(); + _result.Errors[0].ShouldBeOfType(); + } + + private void GivenTheLoadBalancerStarts(List services, string serviceName) + { + _services = services; + _leastConnection = new LeastConnection(() => _services, serviceName); + } + + private void WhenTheLoadBalancerStarts(List services, string serviceName) + { + GivenTheLoadBalancerStarts(services, serviceName); + } + + private void GivenAHostAndPort(HostAndPort hostAndPort) + { + _hostAndPort = hostAndPort; + } + + private void WhenIGetTheNextHostAndPort() + { + _result = _leastConnection.Lease(); + } + + private void ThenTheNextHostAndPortIsReturned() + { + _result.Data.DownstreamHost.ShouldBe(_hostAndPort.DownstreamHost); + _result.Data.DownstreamPort.ShouldBe(_hostAndPort.DownstreamPort); + } + } + + public class LeastConnection : ILoadBalancer + { + private Func> _services; + private List _leases; + private string _serviceName; + + public LeastConnection(Func> services, string serviceName) + { + _services = services; + _serviceName = serviceName; + _leases = new List(); + } + + public Response Lease() + { + var services = _services(); + + if(services == null) + { + return new ErrorResponse(new List(){ new ServicesAreNullError($"services were null for {_serviceName}")}); + } + + if(!services.Any()) + { + return new ErrorResponse(new List(){ new ServicesAreEmptyError($"services were empty for {_serviceName}")}); + } + + //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(new HostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort)); + } + + public Response Release(HostAndPort hostAndPort) + { + 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); + } + + return new OkResponse(); + } + + 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 services) + { + if(_leases.Count > 0) + { + var leasesToRemove = new List(); + + 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(); + } + } + + 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;} + } + + public class ServicesAreNullError : Error + { + public ServicesAreNullError(string message) + : base(message, OcelotErrorCode.ServicesAreNullError) + { + } + } + + public class ServicesAreEmptyError : Error + { + public ServicesAreEmptyError(string message) + : base(message, OcelotErrorCode.ServicesAreEmptyError) + { + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/RoundRobinTests.cs b/test/Ocelot.UnitTests/RoundRobinTests.cs index 82689b626..5b7da0700 100644 --- a/test/Ocelot.UnitTests/RoundRobinTests.cs +++ b/test/Ocelot.UnitTests/RoundRobinTests.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.Diagnostics; +using Ocelot.Responses; using Ocelot.Values; using Shouldly; +using TestStack.BDDfy; using Xunit; namespace Ocelot.UnitTests @@ -10,6 +12,7 @@ public class RoundRobinTests { private readonly RoundRobin _roundRobin; private readonly List _hostAndPorts; + private Response _hostAndPort; public RoundRobinTests() { @@ -26,12 +29,13 @@ public RoundRobinTests() [Fact] public void should_get_next_address() { - var address = _roundRobin.Next(); - address.ShouldBe(_hostAndPorts[0]); - address = _roundRobin.Next(); - address.ShouldBe(_hostAndPorts[1]); - address = _roundRobin.Next(); - address.ShouldBe(_hostAndPorts[2]); + 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] @@ -41,19 +45,30 @@ public void should_go_back_to_first_address_after_finished_last() while (stopWatch.ElapsedMilliseconds < 1000) { - var address = _roundRobin.Next(); - address.ShouldBe(_hostAndPorts[0]); - address = _roundRobin.Next(); - address.ShouldBe(_hostAndPorts[1]); - address = _roundRobin.Next(); - address.ShouldBe(_hostAndPorts[2]); + var address = _roundRobin.Lease(); + address.Data.ShouldBe(_hostAndPorts[0]); + address = _roundRobin.Lease(); + address.Data.ShouldBe(_hostAndPorts[1]); + address = _roundRobin.Lease(); + address.Data.ShouldBe(_hostAndPorts[2]); } } + + private void GivenIGetTheNextAddress() + { + _hostAndPort = _roundRobin.Lease(); + } + + private void ThenTheNextAddressIndexIs(int index) + { + _hostAndPort.Data.ShouldBe(_hostAndPorts[index]); + } } public interface ILoadBalancer { - HostAndPort Next(); + Response Lease(); + Response Release(HostAndPort hostAndPort); } public class RoundRobin : ILoadBalancer @@ -66,7 +81,7 @@ public RoundRobin(List hostAndPorts) _hostAndPorts = hostAndPorts; } - public HostAndPort Next() + public Response Lease() { if (_last >= _hostAndPorts.Count) { @@ -75,7 +90,12 @@ public HostAndPort Next() var next = _hostAndPorts[_last]; _last++; - return next; + return new OkResponse(next); + } + + public Response Release(HostAndPort hostAndPort) + { + return new OkResponse(); } } } diff --git a/test/Ocelot.UnitTests/ServiceRegistryTests.cs b/test/Ocelot.UnitTests/ServiceRegistryTests.cs index 8866f1ae1..27987d421 100644 --- a/test/Ocelot.UnitTests/ServiceRegistryTests.cs +++ b/test/Ocelot.UnitTests/ServiceRegistryTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -21,7 +22,7 @@ public ServiceRegistryTests() [Fact] public void should_register_service() { - this.Given(x => x.GivenAServiceToRegister("product", "localhost:5000")) + this.Given(x => x.GivenAServiceToRegister("product", "localhost:5000", 80)) .When(x => x.WhenIRegisterTheService()) .Then(x => x.ThenTheServiceIsRegistered()) .BDDfy(); @@ -29,7 +30,7 @@ public void should_register_service() public void should_lookup_service() { - this.Given(x => x.GivenAServiceIsRegistered("product", "localhost:600")) + this.Given(x => x.GivenAServiceIsRegistered("product", "localhost:600", 80)) .When(x => x.WhenILookupTheService("product")) .Then(x => x.ThenTheServiceDetailsAreReturned()) .BDDfy(); @@ -37,7 +38,8 @@ public void should_lookup_service() private void ThenTheServiceDetailsAreReturned() { - _services[0].Address.ShouldBe(_service.Address); + _services[0].HostAndPort.DownstreamHost.ShouldBe(_service.HostAndPort.DownstreamHost); + _services[0].HostAndPort.DownstreamPort.ShouldBe(_service.HostAndPort.DownstreamPort); _services[0].Name.ShouldBe(_service.Name); } @@ -46,15 +48,15 @@ private void WhenILookupTheService(string name) _services = _serviceRegistry.Lookup(name); } - private void GivenAServiceIsRegistered(string name, string address) + private void GivenAServiceIsRegistered(string name, string address, int port) { - _service = new Service(name, address); + _service = new Service(name, new HostAndPort(address, port)); _serviceRepository.Set(_service); } - private void GivenAServiceToRegister(string name, string address) + private void GivenAServiceToRegister(string name, string address, int port) { - _service = new Service(name, address); + _service = new Service(name, new HostAndPort(address, port)); } private void WhenIRegisterTheService() @@ -65,7 +67,8 @@ private void WhenIRegisterTheService() private void ThenTheServiceIsRegistered() { var serviceNameAndAddress = _serviceRepository.Get(_service.Name); - serviceNameAndAddress[0].Address.ShouldBe(_service.Address); + serviceNameAndAddress[0].HostAndPort.DownstreamHost.ShouldBe(_service.HostAndPort.DownstreamHost); + serviceNameAndAddress[0].HostAndPort.DownstreamPort.ShouldBe(_service.HostAndPort.DownstreamPort); serviceNameAndAddress[0].Name.ShouldBe(_service.Name); } } @@ -96,13 +99,13 @@ public List Lookup(string name) public class Service { - public Service(string name, string address) + public Service(string name, HostAndPort hostAndPort) { Name = name; - Address = address; + HostAndPort = hostAndPort; } public string Name {get; private set;} - public string Address {get; private set;} + public HostAndPort HostAndPort {get; private set;} } public interface IServiceRepository From 0e92976df8204f76c25712409966025bc352efb5 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 1 Feb 2017 06:40:29 +0000 Subject: [PATCH 05/29] playing around with lb factory --- .../Configuration/Builder/ReRouteBuilder.cs | 9 +- .../Creator/FileOcelotConfigurationCreator.cs | 13 ++- src/Ocelot/Configuration/File/FileReRoute.cs | 1 + src/Ocelot/Configuration/ReRoute.cs | 6 +- .../DownstreamUrlCreatorMiddleware.cs | 2 + .../LoadBalancerFactoryTests.cs | 95 +++++++++++++++++++ test/Ocelot.UnitTests/RoundRobinTests.cs | 8 +- 7 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 1e06a4407..ecc0b67f7 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -32,12 +32,19 @@ public class ReRouteBuilder private string _downstreamScheme; private string _downstreamHost; private int _dsPort; + private string _loadBalancer; public ReRouteBuilder() { _additionalScopes = new List(); } + public ReRouteBuilder WithLoadBalancer(string loadBalancer) + { + _loadBalancer = loadBalancer; + return this; + } + public ReRouteBuilder WithDownstreamScheme(string downstreamScheme) { _downstreamScheme = downstreamScheme; @@ -200,7 +207,7 @@ public ReRoute Build() _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName, - _useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, downstreamHostFunc, _downstreamScheme); + _useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, downstreamHostFunc, _downstreamScheme, _loadBalancer); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 8884f0d9f..d09d738c2 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -97,6 +97,13 @@ private ReRoute SetUpReRoute(FileReRoute reRoute, FileGlobalConfiguration global && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); + //can we do the logic in this func to get the host and port from the load balancer? + //lBfactory.GetLbForThisDownstreamTemplate + //use it in the func to get the next host and port? + //how do we release it? cant callback, could access the lb and release later? + + //ideal world we would get the host and port, then make the request using it, then release the connection to the lb + Func downstreamHostAndPortFunc = () => new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort); if (isAuthenticated) @@ -116,7 +123,8 @@ private ReRoute SetUpReRoute(FileReRoute reRoute, FileGlobalConfiguration global reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme); + globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme, + reRoute.LoadBalancer); } return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate, @@ -125,7 +133,8 @@ private ReRoute SetUpReRoute(FileReRoute reRoute, FileGlobalConfiguration global reRoute.RouteClaimsRequirement, isAuthorised, new List(), requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme); + globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme, + reRoute.LoadBalancer); } private string BuildUpstreamTemplate(FileReRoute reRoute) diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index a653224a5..0d0fc7bda 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -29,5 +29,6 @@ public FileReRoute() public string DownstreamScheme {get;set;} public string DownstreamHost {get;set;} public int DownstreamPort { get; set; } + public string LoadBalancer {get;set;} } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 960374cc2..9faab4ab0 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -10,8 +10,10 @@ public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTem bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery, - string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func downstreamHostAndPort, string downstreamScheme) + string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func downstreamHostAndPort, + string downstreamScheme, string loadBalancer) { + LoadBalancer = loadBalancer; DownstreamPathTemplate = downstreamPathTemplate; UpstreamTemplate = upstreamTemplate; UpstreamHttpMethod = upstreamHttpMethod; @@ -36,7 +38,6 @@ public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTem DownstreamHostAndPort = downstreamHostAndPort; DownstreamScheme = downstreamScheme; } - public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } public string UpstreamTemplate { get; private set; } public string UpstreamTemplatePattern { get; private set; } @@ -57,5 +58,6 @@ public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTem public string ServiceDiscoveryAddress { get; private set;} public Func DownstreamHostAndPort {get;private set;} public string DownstreamScheme {get;private set;} + public string LoadBalancer {get;private set;} } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index b4d73c7ce..957acb8e1 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -51,6 +51,8 @@ public async Task Invoke(HttpContext context) //lease the next address from the lb + //this could return the load balancer which you call next on, that gives you the host and port then you can call release in a try catch + //and if the call works? var dsHostAndPort = DownstreamRoute.ReRoute.DownstreamHostAndPort(); var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort); diff --git a/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs new file mode 100644 index 000000000..6d2a3a59a --- /dev/null +++ b/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs @@ -0,0 +1,95 @@ +using System; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests +{ + public class LoadBalancerFactoryTests + { + private ReRoute _reRoute; + private LoadBalancerFactory _factory; + private ILoadBalancer _result; + + public LoadBalancerFactoryTests() + { + _factory = new LoadBalancerFactory(); + } + + [Fact] + public void should_return_no_load_balancer() + { + var reRoute = new ReRouteBuilder() + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_round_robin_load_balancer() + { + var reRoute = new ReRouteBuilder() + .WithLoadBalancer("RoundRobin") + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + private void GivenAReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGetTheLoadBalancer() + { + _result = _factory.Get(_reRoute); + } + + private void ThenTheLoadBalancerIsReturned() + { + _result.ShouldBeOfType(); + } + } + + public class NoLoadBalancer : ILoadBalancer + { + Response ILoadBalancer.Lease() + { + throw new NotImplementedException(); + } + + Response ILoadBalancer.Release(HostAndPort hostAndPort) + { + throw new NotImplementedException(); + } + } + + public interface ILoadBalancerFactory + { + ILoadBalancer Get(ReRoute reRoute); + } + + public class LoadBalancerFactory : ILoadBalancerFactory + { + public ILoadBalancer Get(ReRoute reRoute) + { + switch (reRoute.LoadBalancer) + { + case "RoundRobin": + return new RoundRobinLoadBalancer(null); + default: + return new NoLoadBalancer(); + } + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/RoundRobinTests.cs b/test/Ocelot.UnitTests/RoundRobinTests.cs index 5b7da0700..f12714603 100644 --- a/test/Ocelot.UnitTests/RoundRobinTests.cs +++ b/test/Ocelot.UnitTests/RoundRobinTests.cs @@ -10,7 +10,7 @@ namespace Ocelot.UnitTests { public class RoundRobinTests { - private readonly RoundRobin _roundRobin; + private readonly RoundRobinLoadBalancer _roundRobin; private readonly List _hostAndPorts; private Response _hostAndPort; @@ -23,7 +23,7 @@ public RoundRobinTests() new HostAndPort("127.0.0.1", 5001) }; - _roundRobin = new RoundRobin(_hostAndPorts); + _roundRobin = new RoundRobinLoadBalancer(_hostAndPorts); } [Fact] @@ -71,12 +71,12 @@ public interface ILoadBalancer Response Release(HostAndPort hostAndPort); } - public class RoundRobin : ILoadBalancer + public class RoundRobinLoadBalancer : ILoadBalancer { private readonly List _hostAndPorts; private int _last; - public RoundRobin(List hostAndPorts) + public RoundRobinLoadBalancer(List hostAndPorts) { _hostAndPorts = hostAndPorts; } From 24dbb958e34da4f040b150b0b95097b93da54d13 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 1 Feb 2017 19:34:55 +0000 Subject: [PATCH 06/29] plying around with service providers --- .../Creator/FileOcelotConfigurationCreator.cs | 18 ++- .../ServiceDiscovery/IServiceProvider.cs | 10 ++ .../IServiceProviderFactory.cs | 9 ++ .../ServiceDiscovery/NoServiceProvider.cs | 20 +++ .../ServiceProviderFactory.cs | 13 ++ src/Ocelot/Values/Service.cs | 13 ++ test/Ocelot.UnitTests/LeastConnectionTests.cs | 14 +-- .../LoadBalancerFactoryTests.cs | 114 ++++++++++++++++-- .../NoServiceProviderTests.cs | 55 +++++++++ test/Ocelot.UnitTests/RoundRobinTests.cs | 32 ++--- .../ServiceProviderFactoryTests.cs | 50 ++++++++ test/Ocelot.UnitTests/ServiceRegistryTests.cs | 12 +- 12 files changed, 314 insertions(+), 46 deletions(-) create mode 100644 src/Ocelot/ServiceDiscovery/IServiceProvider.cs create mode 100644 src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs create mode 100644 src/Ocelot/ServiceDiscovery/NoServiceProvider.cs create mode 100644 src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs create mode 100644 src/Ocelot/Values/Service.cs create mode 100644 test/Ocelot.UnitTests/NoServiceProviderTests.cs create mode 100644 test/Ocelot.UnitTests/ServiceProviderFactoryTests.cs diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index d09d738c2..24c4db720 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -7,6 +7,7 @@ using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; using Ocelot.Responses; +using Ocelot.ServiceDiscovery; using Ocelot.Utilities; using Ocelot.Values; @@ -104,7 +105,22 @@ private ReRoute SetUpReRoute(FileReRoute reRoute, FileGlobalConfiguration global //ideal world we would get the host and port, then make the request using it, then release the connection to the lb - Func downstreamHostAndPortFunc = () => new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort); + Func downstreamHostAndPortFunc = () => { + + //service provider factory takes the reRoute + //can return no service provider (just use ocelot config) + //can return consol service provider + //returns a service provider + //we call get on the service provider + //could reutrn services from consol or just configuration.json + //this returns a list of services and we take the first one + var hostAndPort = new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort); + var services = new List(); + var serviceProvider = new NoServiceProvider(services); + var service = serviceProvider.Get(); + var firstHostAndPort = service[0].HostAndPort; + return firstHostAndPort; + }; if (isAuthenticated) { diff --git a/src/Ocelot/ServiceDiscovery/IServiceProvider.cs b/src/Ocelot/ServiceDiscovery/IServiceProvider.cs new file mode 100644 index 000000000..60e428c85 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/IServiceProvider.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery +{ + public interface IServiceProvider + { + List Get(); + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs new file mode 100644 index 000000000..0043edaa7 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration; + +namespace Ocelot.ServiceDiscovery +{ + public interface IServiceProviderFactory + { + Ocelot.ServiceDiscovery.IServiceProvider Get(ReRoute reRoute); + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/NoServiceProvider.cs b/src/Ocelot/ServiceDiscovery/NoServiceProvider.cs new file mode 100644 index 000000000..9f8b0239c --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/NoServiceProvider.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery +{ + public class NoServiceProvider : IServiceProvider + { + private List _services; + + public NoServiceProvider(List services) + { + _services = services; + } + + public List Get() + { + return _services; + } + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs new file mode 100644 index 000000000..583774e73 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs @@ -0,0 +1,13 @@ +using System; +using Ocelot.Configuration; + +namespace Ocelot.ServiceDiscovery +{ + public class ServiceProviderFactory : IServiceProviderFactory + { + public Ocelot.ServiceDiscovery.IServiceProvider Get(ReRoute reRoute) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Values/Service.cs b/src/Ocelot/Values/Service.cs new file mode 100644 index 000000000..104fbc098 --- /dev/null +++ b/src/Ocelot/Values/Service.cs @@ -0,0 +1,13 @@ +namespace Ocelot.Values +{ + public class Service + { + public Service(string name, HostAndPort hostAndPort) + { + Name = name; + HostAndPort = hostAndPort; + } + public string Name {get; private set;} + public HostAndPort HostAndPort {get; private set;} + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LeastConnectionTests.cs index 758d1917e..ede2105f4 100644 --- a/test/Ocelot.UnitTests/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LeastConnectionTests.cs @@ -14,7 +14,7 @@ public class LeastConnectionTests { private HostAndPort _hostAndPort; private Response _result; - private LeastConnection _leastConnection; + private LeastConnectionLoadBalancer _leastConnection; private List _services; public LeastConnectionTests() @@ -53,7 +53,7 @@ public void should_serve_from_service_with_least_connections() }; _services = availableServices; - _leastConnection = new LeastConnection(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); var response = _leastConnection.Lease(); @@ -80,7 +80,7 @@ public void should_build_connections_per_service() }; _services = availableServices; - _leastConnection = new LeastConnection(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); var response = _leastConnection.Lease(); @@ -111,7 +111,7 @@ public void should_release_connection() }; _services = availableServices; - _leastConnection = new LeastConnection(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); var response = _leastConnection.Lease(); @@ -178,7 +178,7 @@ private void ThenServiceAreEmptyErrorIsReturned() private void GivenTheLoadBalancerStarts(List services, string serviceName) { _services = services; - _leastConnection = new LeastConnection(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); } private void WhenTheLoadBalancerStarts(List services, string serviceName) @@ -203,13 +203,13 @@ private void ThenTheNextHostAndPortIsReturned() } } - public class LeastConnection : ILoadBalancer + public class LeastConnectionLoadBalancer : ILoadBalancer { private Func> _services; private List _leases; private string _serviceName; - public LeastConnection(Func> services, string serviceName) + public LeastConnectionLoadBalancer(Func> services, string serviceName) { _services = services; _serviceName = serviceName; diff --git a/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs index 6d2a3a59a..12f59cebb 100644 --- a/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Linq; +using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Responses; @@ -14,10 +17,12 @@ public class LoadBalancerFactoryTests private ReRoute _reRoute; private LoadBalancerFactory _factory; private ILoadBalancer _result; - + private Mock _serviceProvider; + public LoadBalancerFactoryTests() { - _factory = new LoadBalancerFactory(); + _serviceProvider = new Mock(); + _factory = new LoadBalancerFactory(_serviceProvider.Object); } [Fact] @@ -36,8 +41,8 @@ public void should_return_no_load_balancer() public void should_return_round_robin_load_balancer() { var reRoute = new ReRouteBuilder() - .WithLoadBalancer("RoundRobin") - .Build(); + .WithLoadBalancer("RoundRobin") + .Build(); this.Given(x => x.GivenAReRoute(reRoute)) .When(x => x.WhenIGetTheLoadBalancer()) @@ -45,6 +50,38 @@ public void should_return_round_robin_load_balancer() .BDDfy(); } + [Fact] + public void should_return_round_least_connection_balancer() + { + var reRoute = new ReRouteBuilder() + .WithLoadBalancer("LeastConnection") + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_call_service_provider() + { + var reRoute = new ReRouteBuilder() + .WithLoadBalancer("RoundRobin") + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheServiceProviderIsCalledCorrectly(reRoute)) + .BDDfy(); + } + + private void ThenTheServiceProviderIsCalledCorrectly(ReRoute reRoute) + { + _serviceProvider + .Verify(x => x.Get(), Times.Once); + } + private void GivenAReRoute(ReRoute reRoute) { _reRoute = reRoute; @@ -61,16 +98,62 @@ private void ThenTheLoadBalancerIsReturned() } } - public class NoLoadBalancer : ILoadBalancer + public class NoLoadBalancerTests { - Response ILoadBalancer.Lease() + private List _services; + private NoLoadBalancer _loadBalancer; + private Response _result; + + [Fact] + public void should_return_host_and_port() + { + var hostAndPort = new HostAndPort("127.0.0.1", 80); + + var services = new List + { + new Service("product", hostAndPort) + }; + this.Given(x => x.GivenServices(services)) + .When(x => x.WhenIGetTheNextHostAndPort()) + .Then(x => x.ThenTheHostAndPortIs(hostAndPort)) + .BDDfy(); + } + + private void GivenServices(List services) + { + _services = services; + } + + private void WhenIGetTheNextHostAndPort() + { + _loadBalancer = new NoLoadBalancer(_services); + _result = _loadBalancer.Lease(); + } + + private void ThenTheHostAndPortIs(HostAndPort expected) { - throw new NotImplementedException(); + _result.Data.ShouldBe(expected); } + } + + public class NoLoadBalancer : ILoadBalancer + { + private List _services; - Response ILoadBalancer.Release(HostAndPort hostAndPort) + public NoLoadBalancer(List services) + { + _services = services; + } + + public Response Lease() { - throw new NotImplementedException(); + var service = _services.FirstOrDefault(); + return new OkResponse(service.HostAndPort); + } + + public Response Release(HostAndPort hostAndPort) + { + return new OkResponse(); } } @@ -81,14 +164,23 @@ public interface ILoadBalancerFactory public class LoadBalancerFactory : ILoadBalancerFactory { + private Ocelot.ServiceDiscovery.IServiceProvider _serviceProvider; + + public LoadBalancerFactory(Ocelot.ServiceDiscovery.IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + public ILoadBalancer Get(ReRoute reRoute) { switch (reRoute.LoadBalancer) { case "RoundRobin": - return new RoundRobinLoadBalancer(null); + return new RoundRobinLoadBalancer(_serviceProvider.Get()); + case "LeastConnection": + return new LeastConnectionLoadBalancer(() => _serviceProvider.Get(), reRoute.ServiceName); default: - return new NoLoadBalancer(); + return new NoLoadBalancer(_serviceProvider.Get()); } } } diff --git a/test/Ocelot.UnitTests/NoServiceProviderTests.cs b/test/Ocelot.UnitTests/NoServiceProviderTests.cs new file mode 100644 index 000000000..f85ef13c7 --- /dev/null +++ b/test/Ocelot.UnitTests/NoServiceProviderTests.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.ServiceDiscovery; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests +{ + public class NoServiceProviderTests + { + private NoServiceProvider _serviceProvider; + private HostAndPort _hostAndPort; + private List _result; + private List _expected; + + [Fact] + public void should_return_services() + { + var hostAndPort = new HostAndPort("127.0.0.1", 80); + + var services = new List + { + new Service("product", hostAndPort) + }; + + this.Given(x => x.GivenAHostAndPort(services)) + .When(x => x.WhenIGetTheService()) + .Then(x => x.ThenTheFollowingIsReturned(services)) + .BDDfy(); + } + + private void GivenAHostAndPort(List services) + { + _expected = services; + } + + private void WhenIGetTheService() + { + _serviceProvider = new NoServiceProvider(_expected); + _result = _serviceProvider.Get(); + } + + private void ThenTheFollowingIsReturned(List 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); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/RoundRobinTests.cs b/test/Ocelot.UnitTests/RoundRobinTests.cs index f12714603..39d334413 100644 --- a/test/Ocelot.UnitTests/RoundRobinTests.cs +++ b/test/Ocelot.UnitTests/RoundRobinTests.cs @@ -11,19 +11,19 @@ namespace Ocelot.UnitTests public class RoundRobinTests { private readonly RoundRobinLoadBalancer _roundRobin; - private readonly List _hostAndPorts; + private readonly List _services; private Response _hostAndPort; public RoundRobinTests() { - _hostAndPorts = new List + _services = new List { - new HostAndPort("127.0.0.1", 5000), - new HostAndPort("127.0.0.1", 5001), - new HostAndPort("127.0.0.1", 5001) + new Service("product", new HostAndPort("127.0.0.1", 5000)), + new Service("product", new HostAndPort("127.0.0.1", 5001)), + new Service("product", new HostAndPort("127.0.0.1", 5001)) }; - _roundRobin = new RoundRobinLoadBalancer(_hostAndPorts); + _roundRobin = new RoundRobinLoadBalancer(_services); } [Fact] @@ -46,11 +46,11 @@ public void should_go_back_to_first_address_after_finished_last() while (stopWatch.ElapsedMilliseconds < 1000) { var address = _roundRobin.Lease(); - address.Data.ShouldBe(_hostAndPorts[0]); + address.Data.ShouldBe(_services[0].HostAndPort); address = _roundRobin.Lease(); - address.Data.ShouldBe(_hostAndPorts[1]); + address.Data.ShouldBe(_services[1].HostAndPort); address = _roundRobin.Lease(); - address.Data.ShouldBe(_hostAndPorts[2]); + address.Data.ShouldBe(_services[2].HostAndPort); } } @@ -61,7 +61,7 @@ private void GivenIGetTheNextAddress() private void ThenTheNextAddressIndexIs(int index) { - _hostAndPort.Data.ShouldBe(_hostAndPorts[index]); + _hostAndPort.Data.ShouldBe(_services[index].HostAndPort); } } @@ -73,24 +73,24 @@ public interface ILoadBalancer public class RoundRobinLoadBalancer : ILoadBalancer { - private readonly List _hostAndPorts; + private readonly List _services; private int _last; - public RoundRobinLoadBalancer(List hostAndPorts) + public RoundRobinLoadBalancer(List services) { - _hostAndPorts = hostAndPorts; + _services = services; } public Response Lease() { - if (_last >= _hostAndPorts.Count) + if (_last >= _services.Count) { _last = 0; } - var next = _hostAndPorts[_last]; + var next = _services[_last]; _last++; - return new OkResponse(next); + return new OkResponse(next.HostAndPort); } public Response Release(HostAndPort hostAndPort) diff --git a/test/Ocelot.UnitTests/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceProviderFactoryTests.cs new file mode 100644 index 000000000..056098167 --- /dev/null +++ b/test/Ocelot.UnitTests/ServiceProviderFactoryTests.cs @@ -0,0 +1,50 @@ +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.ServiceDiscovery; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests +{ + public class ServiceProviderFactoryTests + { + private ReRoute _reRote; + private IServiceProvider _result; + private ServiceProviderFactory _factory; + + public ServiceProviderFactoryTests() + { + _factory = new ServiceProviderFactory(); + } + + [Fact] + public void should_return_no_service_provider() + { + var reRoute = new ReRouteBuilder() + .WithDownstreamHost("127.0.0.1") + .WithDownstreamPort(80) + .Build(); + + this.Given(x => x.GivenTheReRoute(reRoute)) + .When(x => x.WhenIGetTheServiceProvider()) + .Then(x => x.ThenTheServiceProviderIs()) + .BDDfy(); + } + + private void GivenTheReRoute(ReRoute reRoute) + { + _reRote = reRoute; + } + + private void WhenIGetTheServiceProvider() + { + _result = _factory.Get(_reRote); + } + + private void ThenTheServiceProviderIs() + { + _result.ShouldBeOfType(); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/ServiceRegistryTests.cs b/test/Ocelot.UnitTests/ServiceRegistryTests.cs index 27987d421..090122136 100644 --- a/test/Ocelot.UnitTests/ServiceRegistryTests.cs +++ b/test/Ocelot.UnitTests/ServiceRegistryTests.cs @@ -86,6 +86,7 @@ public ServiceRegistry(IServiceRepository repository) { _repository = repository; } + public void Register(Service serviceNameAndAddress) { _repository.Set(serviceNameAndAddress); @@ -97,17 +98,6 @@ public List Lookup(string name) } } - public class Service - { - public Service(string name, HostAndPort hostAndPort) - { - Name = name; - HostAndPort = hostAndPort; - } - public string Name {get; private set;} - public HostAndPort HostAndPort {get; private set;} - } - public interface IServiceRepository { List Get(string serviceName); From 074ae4d609ff785aa79e02d45d0572848bc1c82f Mon Sep 17 00:00:00 2001 From: TomPallister Date: Wed, 1 Feb 2017 22:30:28 +0000 Subject: [PATCH 07/29] started adding a load balancer house (terrible name?) --- .../LoadBalancers/ILoadBalancer.cs | 1 + .../LoadBalancers/ILoadBalancerHouse.cs | 10 ++ .../LoadBalancers/LoadBalancerHouse.cs | 29 +++++ .../LoadBalancer/LoadBalancerHouseTests.cs | 121 ++++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs create mode 100644 src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs create mode 100644 test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs index fe4e5baf4..100ee6f0c 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs @@ -1,3 +1,4 @@ +using System; using Ocelot.Responses; using Ocelot.Values; diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs new file mode 100644 index 000000000..065ae2ac5 --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs @@ -0,0 +1,10 @@ +using Ocelot.Responses; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public interface ILoadBalancerHouse + { + Response Get(string key); + Response Add(string key, ILoadBalancer loadBalancer); + } +} \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs new file mode 100644 index 000000000..2bb8b9665 --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Responses; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class LoadBalancerHouse + { + private readonly Dictionary _loadBalancers; + + public LoadBalancerHouse() + { + _loadBalancers = new Dictionary(); + } + + public Response Get(string key) + { + return new OkResponse(_loadBalancers[key]); + } + + public Response Add(string key, ILoadBalancer loadBalancer) + { + _loadBalancers[key] = loadBalancer; + return new OkResponse(); + } + } +} diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs new file mode 100644 index 000000000..2fd15ee72 --- /dev/null +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -0,0 +1,121 @@ +using System; +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 _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.WhenWeGetThatLoadBalancer(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.WhenWeGetThatLoadBalancer(key)) + .Then(x => x.ThenTheLoadBalancerIs()) + .When(x => x.WhenWeGetThatLoadBalancer(keyTwo)) + .Then(x => x.ThenTheLoadBalancerIs()) + .BDDfy(); + } + + private void ThenTheLoadBalancerIs() + { + _getResult.Data.ShouldBeOfType(); + } + + private void ThenItIsAdded() + { + _addResult.IsError.ShouldBe(false); + _addResult.ShouldBeOfType(); + } + + private void WhenIAddTheLoadBalancer() + { + _addResult = _loadBalancerHouse.Add(_key, _loadBalancer); + } + + + private void GivenThereIsALoadBalancer(string key, ILoadBalancer loadBalancer) + { + _key = key; + _loadBalancer = loadBalancer; + WhenIAddTheLoadBalancer(); + } + + private void WhenWeGetThatLoadBalancer(string key) + { + _getResult = _loadBalancerHouse.Get(key); + } + + private void ThenItIsReturned() + { + _getResult.Data.ShouldBe(_loadBalancer); + } + + class FakeLoadBalancer : ILoadBalancer + { + public Response Lease() + { + throw new NotImplementedException(); + } + + public Response Release(HostAndPort hostAndPort) + { + throw new NotImplementedException(); + } + } + + class FakeRoundRobinLoadBalancer : ILoadBalancer + { + public Response Lease() + { + throw new NotImplementedException(); + } + + public Response Release(HostAndPort hostAndPort) + { + throw new NotImplementedException(); + } + } + } +} From 37aaeeed82fe64812357b05d4b2d5bb3f342be41 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Thu, 2 Feb 2017 08:00:37 +0000 Subject: [PATCH 08/29] added houses --- src/Ocelot/Errors/OcelotErrorCode.cs | 4 +- .../LoadBalancers/LoadBalancerHouse.cs | 14 +- .../UnableToFindLoadBalancerError.cs | 12 ++ .../ServiceDiscovery/IServiceProviderHouse.cs | 12 ++ .../ServiceDiscovery/ServiceProviderHouse.cs | 34 +++++ .../UnableToFindServiceProviderError.cs | 12 ++ .../LoadBalancer/LoadBalancerHouseTests.cs | 22 ++- .../ServiceProviderHouseTests.cs | 126 ++++++++++++++++++ 8 files changed, 229 insertions(+), 7 deletions(-) create mode 100644 src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs create mode 100644 src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs create mode 100644 src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs create mode 100644 src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs create mode 100644 test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index 85c3e0970..f2c479df4 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -23,6 +23,8 @@ public enum OcelotErrorCode DownstreamSchemeNullOrEmptyError, DownstreamHostNullOrEmptyError, ServicesAreNullError, - ServicesAreEmptyError + ServicesAreEmptyError, + UnableToFindServiceProviderError, + UnableToFindLoadBalancerError } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs index 2bb8b9665..12c040c01 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -6,7 +6,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers { - public class LoadBalancerHouse + public class LoadBalancerHouse : ILoadBalancerHouse { private readonly Dictionary _loadBalancers; @@ -17,7 +17,17 @@ public LoadBalancerHouse() public Response Get(string key) { - return new OkResponse(_loadBalancers[key]); + ILoadBalancer loadBalancer; + + if(_loadBalancers.TryGetValue(key, out loadBalancer)) + { + return new OkResponse(_loadBalancers[key]); + } + + return new ErrorResponse(new List() + { + new UnableToFindLoadBalancerError($"unabe to find load balancer for {key}") + }); } public Response Add(string key, ILoadBalancer loadBalancer) diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs b/src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs new file mode 100644 index 000000000..3dd3ede4a --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class UnableToFindLoadBalancerError : Errors.Error + { + public UnableToFindLoadBalancerError(string message) + : base(message, OcelotErrorCode.UnableToFindLoadBalancerError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs b/src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs new file mode 100644 index 000000000..0f85f5280 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using Ocelot.Responses; + +namespace Ocelot.ServiceDiscovery +{ + public interface IServiceProviderHouse + { + Response Get(string key); + Response Add(string key, IServiceProvider serviceProvider); + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs b/src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs new file mode 100644 index 000000000..63d14dcec --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Ocelot.Responses; + +namespace Ocelot.ServiceDiscovery +{ + public class ServiceProviderHouse : IServiceProviderHouse + { + private Dictionary _serviceProviders; + + public ServiceProviderHouse() + { + _serviceProviders = new Dictionary(); + } + + public Response Get(string key) + { + IServiceProvider serviceProvider; + if(_serviceProviders.TryGetValue(key, out serviceProvider)) + { + return new OkResponse(serviceProvider); + } + + return new ErrorResponse(new List() + { + new UnableToFindServiceProviderError($"unabe to find service provider for {key}") + }); + } + public Response Add(string key, IServiceProvider serviceProvider) + { + _serviceProviders[key] = serviceProvider; + return new OkResponse(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs b/src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs new file mode 100644 index 000000000..b8ed1a47f --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.ServiceDiscovery +{ + public class UnableToFindServiceProviderError : Error + { + public UnableToFindServiceProviderError(string message) + : base(message, OcelotErrorCode.UnableToFindServiceProviderError) + { + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index 2fd15ee72..31a7bd37a 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -38,7 +38,7 @@ public void should_get_load_balancer() var key = "test"; this.Given(x => x.GivenThereIsALoadBalancer(key, new FakeLoadBalancer())) - .When(x => x.WhenWeGetThatLoadBalancer(key)) + .When(x => x.WhenWeGetTheLoadBalancer(key)) .Then(x => x.ThenItIsReturned()) .BDDfy(); } @@ -51,13 +51,27 @@ public void should_store_load_balancers_by_key() this.Given(x => x.GivenThereIsALoadBalancer(key, new FakeLoadBalancer())) .And(x => x.GivenThereIsALoadBalancer(keyTwo, new FakeRoundRobinLoadBalancer())) - .When(x => x.WhenWeGetThatLoadBalancer(key)) + .When(x => x.WhenWeGetTheLoadBalancer(key)) .Then(x => x.ThenTheLoadBalancerIs()) - .When(x => x.WhenWeGetThatLoadBalancer(keyTwo)) + .When(x => x.WhenWeGetTheLoadBalancer(keyTwo)) .Then(x => x.ThenTheLoadBalancerIs()) .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(); + } + private void ThenTheLoadBalancerIs() { _getResult.Data.ShouldBeOfType(); @@ -82,7 +96,7 @@ private void GivenThereIsALoadBalancer(string key, ILoadBalancer loadBalancer) WhenIAddTheLoadBalancer(); } - private void WhenWeGetThatLoadBalancer(string key) + private void WhenWeGetTheLoadBalancer(string key) { _getResult = _loadBalancerHouse.Get(key); } diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs new file mode 100644 index 000000000..9443c113d --- /dev/null +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Responses; +using Ocelot.ServiceDiscovery; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class ServiceProviderHouseTests + { + private Ocelot.ServiceDiscovery.IServiceProvider _serviceProvider; + private readonly ServiceProviderHouse _serviceProviderHouse; + private Response _addResult; + private Response _getResult; + private string _key; + + public ServiceProviderHouseTests() + { + _serviceProviderHouse = new ServiceProviderHouse(); + } + + [Fact] + public void should_store_service_provider() + { + var key = "test"; + + this.Given(x => x.GivenThereIsAServiceProvider(key, new FakeServiceProvider())) + .When(x => x.WhenIAddTheServiceProvider()) + .Then(x => x.ThenItIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_get_service_provider() + { + var key = "test"; + + this.Given(x => x.GivenThereIsAServiceProvider(key, new FakeServiceProvider())) + .When(x => x.WhenWeGetTheServiceProvider(key)) + .Then(x => x.ThenItIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_store_service_providers_by_key() + { + var key = "test"; + var keyTwo = "testTwo"; + + this.Given(x => x.GivenThereIsAServiceProvider(key, new FakeServiceProvider())) + .And(x => x.GivenThereIsAServiceProvider(keyTwo, new FakeConsulServiceProvider())) + .When(x => x.WhenWeGetTheServiceProvider(key)) + .Then(x => x.ThenTheServiceProviderIs()) + .When(x => x.WhenWeGetTheServiceProvider(keyTwo)) + .Then(x => x.ThenTheServiceProviderIs()) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_no_service_provider_house_with_key() + { + this.When(x => x.WhenWeGetTheServiceProvider("test")) + .Then(x => x.ThenAnErrorIsReturned()) + .BDDfy(); + } + + private void ThenAnErrorIsReturned() + { + _getResult.IsError.ShouldBeTrue(); + _getResult.Errors[0].ShouldBeOfType(); + } + + private void ThenTheServiceProviderIs() + { + _getResult.Data.ShouldBeOfType(); + } + + private void ThenItIsAdded() + { + _addResult.IsError.ShouldBe(false); + _addResult.ShouldBeOfType(); + } + + private void WhenIAddTheServiceProvider() + { + _addResult = _serviceProviderHouse.Add(_key, _serviceProvider); + } + + private void GivenThereIsAServiceProvider(string key, Ocelot.ServiceDiscovery.IServiceProvider serviceProvider) + { + _key = key; + _serviceProvider = serviceProvider; + WhenIAddTheServiceProvider(); + } + + private void WhenWeGetTheServiceProvider(string key) + { + _getResult = _serviceProviderHouse.Get(key); + } + + private void ThenItIsReturned() + { + _getResult.Data.ShouldBe(_serviceProvider); + } + + class FakeServiceProvider : Ocelot.ServiceDiscovery.IServiceProvider + { + public List Get() + { + throw new NotImplementedException(); + } + } + + class FakeConsulServiceProvider : Ocelot.ServiceDiscovery.IServiceProvider + { + public List Get() + { + throw new NotImplementedException(); + } + } + } +} From 07ca7989b012366436a4e90ccdbe32bad9a36472 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Thu, 2 Feb 2017 21:34:15 +0000 Subject: [PATCH 09/29] more work towards getting service discovery working with load balancing --- .../Creator/FileOcelotConfigurationCreator.cs | 71 ++++++---- .../ServiceCollectionExtensions.cs | 6 + .../LoadBalancers/ILoadBalancerFactory.cs | 6 +- .../LoadBalancers/LoadBalancerFactory.cs | 28 ++-- .../Middleware/LoadBalancingMiddleware.cs | 35 ++--- .../LoadBalancingMiddlewareExtensions.cs | 12 ++ src/Ocelot/Middleware/OcelotMiddleware.cs | 15 +++ .../IServiceProviderFactory.cs | 2 + .../ServiceDiscovery/IServiceProviderHouse.cs | 12 -- .../ServiceDiscovery/ServiceProviderHouse.cs | 34 ----- .../FileConfigurationCreatorTests.cs | 6 +- .../LoadBalancer/LoadBalancerFactoryTests.cs | 12 +- .../LoadBalancerMiddlewareTests.cs | 124 +++++++++++++++++ .../ServiceProviderHouseTests.cs | 126 ------------------ 14 files changed, 248 insertions(+), 241 deletions(-) create mode 100644 src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs delete mode 100644 src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs delete mode 100644 src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs create mode 100644 test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs delete mode 100644 test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 827dcbad8..61c61fae7 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -27,13 +27,19 @@ public class FileOcelotConfigurationCreator : IOcelotConfigurationCreator private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser; private readonly ILogger _logger; + private readonly ILoadBalancerFactory _loadBalanceFactory; + private readonly ILoadBalancerHouse _loadBalancerHouse; public FileOcelotConfigurationCreator( IOptions options, IConfigurationValidator configurationValidator, IClaimToThingConfigurationParser claimToThingConfigurationParser, - ILogger logger) + ILogger logger, + ILoadBalancerFactory loadBalancerFactory, + ILoadBalancerHouse loadBalancerHouse) { + _loadBalanceFactory = loadBalancerFactory; + _loadBalancerHouse = loadBalancerHouse; _options = options; _configurationValidator = configurationValidator; _claimToThingConfigurationParser = claimToThingConfigurationParser; @@ -78,55 +84,62 @@ private IOcelotConfiguration SetUpConfiguration() return new OcelotConfiguration(reRoutes); } - private ReRoute SetUpReRoute(FileReRoute reRoute, FileGlobalConfiguration globalConfiguration) + private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) { var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); - var upstreamTemplate = BuildUpstreamTemplate(reRoute); + var upstreamTemplate = BuildUpstreamTemplate(fileReRoute); - var isAuthenticated = !string.IsNullOrEmpty(reRoute.AuthenticationOptions?.Provider); + var isAuthenticated = !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); - var isAuthorised = reRoute.RouteClaimsRequirement?.Count > 0; + var isAuthorised = fileReRoute.RouteClaimsRequirement?.Count > 0; - var isCached = reRoute.FileCacheOptions.TtlSeconds > 0; + var isCached = fileReRoute.FileCacheOptions.TtlSeconds > 0; var requestIdKey = globalRequestIdConfiguration ? globalConfiguration.RequestIdKey - : reRoute.RequestIdKey; + : fileReRoute.RequestIdKey; - var useServiceDiscovery = !string.IsNullOrEmpty(reRoute.ServiceName) + var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName) && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Address) && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); + ReRoute reRoute; + if (isAuthenticated) { - var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider, - reRoute.AuthenticationOptions.ProviderRootUrl, reRoute.AuthenticationOptions.ScopeName, - reRoute.AuthenticationOptions.RequireHttps, reRoute.AuthenticationOptions.AdditionalScopes, - reRoute.AuthenticationOptions.ScopeSecret); + var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider, + fileReRoute.AuthenticationOptions.ProviderRootUrl, fileReRoute.AuthenticationOptions.ScopeName, + fileReRoute.AuthenticationOptions.RequireHttps, fileReRoute.AuthenticationOptions.AdditionalScopes, + fileReRoute.AuthenticationOptions.ScopeSecret); - var claimsToHeaders = GetAddThingsToRequest(reRoute.AddHeadersToRequest); - var claimsToClaims = GetAddThingsToRequest(reRoute.AddClaimsToRequest); - var claimsToQueries = GetAddThingsToRequest(reRoute.AddQueriesToRequest); + var claimsToHeaders = GetAddThingsToRequest(fileReRoute.AddHeadersToRequest); + var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest); + var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest); - return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate, - reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, + reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), fileReRoute.UpstreamTemplate, + fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, authOptionsForRoute, claimsToHeaders, claimsToClaims, - reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, - requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), - reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, reRoute.DownstreamScheme, - reRoute.LoadBalancer, reRoute.DownstreamHost, reRoute.DownstreamPort); + fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, + requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), + fileReRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, + globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort); } - return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate, - reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, + reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), fileReRoute.UpstreamTemplate, + fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, null, new List(), new List(), - reRoute.RouteClaimsRequirement, isAuthorised, new List(), - requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), - reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, reRoute.DownstreamScheme, - reRoute.LoadBalancer, reRoute.DownstreamHost, reRoute.DownstreamPort); + fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), + requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), + fileReRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, + globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort); + + var loadBalancer = _loadBalanceFactory.Get(reRoute); + //todo - not sure if this is the correct key, but this is probably the only unique key i can think of + _loadBalancerHouse.Add($"{fileReRoute.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}", loadBalancer); + return reRoute; } private string BuildUpstreamTemplate(FileReRoute reRoute) diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 9f40b0098..86564f924 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -23,11 +23,13 @@ using Ocelot.Headers; using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Infrastructure.RequestData; +using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Logging; using Ocelot.QueryStrings; using Ocelot.Request.Builder; using Ocelot.Requester; using Ocelot.Responder; +using Ocelot.ServiceDiscovery; namespace Ocelot.DependencyInjection { @@ -59,6 +61,10 @@ public static IServiceCollection AddOcelot(this IServiceCollection services) { services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs index 930a5211c..55089cdec 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs @@ -1,7 +1,9 @@ -namespace Ocelot.LoadBalancer.LoadBalancers +using Ocelot.Configuration; + +namespace Ocelot.LoadBalancer.LoadBalancers { public interface ILoadBalancerFactory { - ILoadBalancer Get(string serviceName, string loadBalancer); + ILoadBalancer Get(ReRoute reRoute); } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs index d5203062d..7e11df394 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs @@ -1,26 +1,34 @@ -using Ocelot.ServiceDiscovery; +using Ocelot.Configuration; +using Ocelot.ServiceDiscovery; namespace Ocelot.LoadBalancer.LoadBalancers { public class LoadBalancerFactory : ILoadBalancerFactory { - private readonly IServiceProvider _serviceProvider; - - public LoadBalancerFactory(IServiceProvider serviceProvider) + private readonly IServiceProviderFactory _serviceProviderFactory; + public LoadBalancerFactory(IServiceProviderFactory serviceProviderFactory) { - _serviceProvider = serviceProvider; + _serviceProviderFactory = serviceProviderFactory; } - public ILoadBalancer Get(string serviceName, string loadBalancer) + public ILoadBalancer Get(ReRoute reRoute) { - switch (loadBalancer) + var serviceConfig = new ServiceConfiguraion( + reRoute.ServiceName, + reRoute.DownstreamHost, + reRoute.DownstreamPort, + reRoute.UseServiceDiscovery); + + var serviceProvider = _serviceProviderFactory.Get(serviceConfig); + + switch (reRoute.LoadBalancer) { case "RoundRobin": - return new RoundRobinLoadBalancer(_serviceProvider.Get()); + return new RoundRobinLoadBalancer(serviceProvider.Get()); case "LeastConnection": - return new LeastConnectionLoadBalancer(() => _serviceProvider.Get(), serviceName); + return new LeastConnectionLoadBalancer(() => serviceProvider.Get(), reRoute.ServiceName); default: - return new NoLoadBalancer(_serviceProvider.Get()); + return new NoLoadBalancer(serviceProvider.Get()); } } } diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index 5c66e98a6..e762186d9 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -14,37 +14,30 @@ public class LoadBalancingMiddleware : OcelotMiddleware { private readonly RequestDelegate _next; private readonly IOcelotLogger _logger; + private readonly ILoadBalancerHouse _loadBalancerHouse; public LoadBalancingMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository) + IRequestScopedDataRepository requestScopedDataRepository, + ILoadBalancerHouse loadBalancerHouse) : base(requestScopedDataRepository) { _next = next; _logger = loggerFactory.CreateLogger(); + _loadBalancerHouse = loadBalancerHouse; } public async Task Invoke(HttpContext context) { _logger.LogDebug("started calling query string builder middleware"); - //todo - get out of di? or do this when we bootstrap? - var serviceProviderFactory = new ServiceProviderFactory(); - var serviceConfig = new ServiceConfiguraion( - DownstreamRoute.ReRoute.ServiceName, - DownstreamRoute.ReRoute.DownstreamHost, - DownstreamRoute.ReRoute.DownstreamPort, - DownstreamRoute.ReRoute.UseServiceDiscovery); - //todo - get this out of some kind of service provider house? - var serviceProvider = serviceProviderFactory.Get(serviceConfig); - - //todo - get out of di? or do this when we bootstrap? - var loadBalancerFactory = new LoadBalancerFactory(serviceProvider); - //todo - currently instanciates a load balancer per request which is wrong, - //need some kind of load balance house! :) - var loadBalancer = loadBalancerFactory.Get(DownstreamRoute.ReRoute.ServiceName, DownstreamRoute.ReRoute.LoadBalancer); - var response = loadBalancer.Lease(); - + var loadBalancer = _loadBalancerHouse.Get($"{DownstreamRoute.ReRoute.UpstreamTemplate}{DownstreamRoute.ReRoute.UpstreamHttpMethod}"); + //todo check reponse and return error + + var response = loadBalancer.Data.Lease(); + //todo check reponse and return error + + SetHostAndPortForThisRequest(response.Data); _logger.LogDebug("calling next middleware"); //todo - try next middleware if we get an exception make sure we release @@ -53,11 +46,11 @@ public async Task Invoke(HttpContext context) { await _next.Invoke(context); - loadBalancer.Release(response.Data); + loadBalancer.Data.Release(response.Data); } - catch (Exception exception) + catch (Exception) { - loadBalancer.Release(response.Data); + loadBalancer.Data.Release(response.Data); throw; } diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs new file mode 100644 index 000000000..52d47bdd0 --- /dev/null +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.LoadBalancer.Middleware +{ + public static class LoadBalancingMiddlewareExtensions + { + public static IApplicationBuilder UseLoadBalancingMiddlewareExtensions(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Middleware/OcelotMiddleware.cs b/src/Ocelot/Middleware/OcelotMiddleware.cs index 0bb510406..b89371089 100644 --- a/src/Ocelot/Middleware/OcelotMiddleware.cs +++ b/src/Ocelot/Middleware/OcelotMiddleware.cs @@ -3,6 +3,7 @@ using Ocelot.DownstreamRouteFinder; using Ocelot.Errors; using Ocelot.Infrastructure.RequestData; +using Ocelot.Values; namespace Ocelot.Middleware { @@ -69,6 +70,20 @@ public HttpResponseMessage HttpResponseMessage } } + public HostAndPort HostAndPort + { + get + { + var hostAndPort = _requestScopedDataRepository.Get("HostAndPort"); + return hostAndPort.Data; + } + } + + public void SetHostAndPortForThisRequest(HostAndPort hostAndPort) + { + _requestScopedDataRepository.Add("HostAndPort", hostAndPort); + } + public void SetDownstreamRouteForThisRequest(DownstreamRoute downstreamRoute) { _requestScopedDataRepository.Add("DownstreamRoute", downstreamRoute); diff --git a/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs index 67b84c69f..62c55f539 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs @@ -1,3 +1,5 @@ +using System; + namespace Ocelot.ServiceDiscovery { public interface IServiceProviderFactory diff --git a/src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs b/src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs deleted file mode 100644 index 0f85f5280..000000000 --- a/src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using Ocelot.Responses; - -namespace Ocelot.ServiceDiscovery -{ - public interface IServiceProviderHouse - { - Response Get(string key); - Response Add(string key, IServiceProvider serviceProvider); - } -} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs b/src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs deleted file mode 100644 index 63d14dcec..000000000 --- a/src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using Ocelot.Responses; - -namespace Ocelot.ServiceDiscovery -{ - public class ServiceProviderHouse : IServiceProviderHouse - { - private Dictionary _serviceProviders; - - public ServiceProviderHouse() - { - _serviceProviders = new Dictionary(); - } - - public Response Get(string key) - { - IServiceProvider serviceProvider; - if(_serviceProviders.TryGetValue(key, out serviceProvider)) - { - return new OkResponse(serviceProvider); - } - - return new ErrorResponse(new List() - { - new UnableToFindServiceProviderError($"unabe to find service provider for {key}") - }); - } - public Response Add(string key, IServiceProvider serviceProvider) - { - _serviceProviders[key] = serviceProvider; - return new OkResponse(); - } - } -} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index fc80a4787..b6e28033e 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -8,6 +8,7 @@ using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; +using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; @@ -24,6 +25,8 @@ public class FileConfigurationCreatorTests private readonly Mock _configParser; private readonly Mock> _logger; private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator; + private readonly Mock _loadBalancerFactory; + private readonly Mock _loadBalancerHouse; public FileConfigurationCreatorTests() { @@ -32,7 +35,8 @@ public FileConfigurationCreatorTests() _validator = new Mock(); _fileConfig = new Mock>(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( - _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object); + _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object, + _loadBalancerFactory.Object, _loadBalancerHouse.Object); } [Fact] diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index c4eb736b7..7361c7265 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -14,12 +14,12 @@ public class LoadBalancerFactoryTests private ReRoute _reRoute; private LoadBalancerFactory _factory; private ILoadBalancer _result; - private Mock _serviceProvider; + private Mock _serviceProviderFactory; public LoadBalancerFactoryTests() { - _serviceProvider = new Mock(); - _factory = new LoadBalancerFactory(_serviceProvider.Object); + _serviceProviderFactory = new Mock(); + _factory = new LoadBalancerFactory(_serviceProviderFactory.Object); } [Fact] @@ -75,8 +75,8 @@ public void should_call_service_provider() private void ThenTheServiceProviderIsCalledCorrectly() { - _serviceProvider - .Verify(x => x.Get(), Times.Once); + _serviceProviderFactory + .Verify(x => x.Get(It.IsAny()), Times.Once); } private void GivenAReRoute(ReRoute reRoute) @@ -86,7 +86,7 @@ private void GivenAReRoute(ReRoute reRoute) private void WhenIGetTheLoadBalancer() { - _result = _factory.Get(_reRoute.ServiceName, _reRoute.LoadBalancer); + _result = _factory.Get(_reRoute); } private void ThenTheLoadBalancerIsReturned() diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs new file mode 100644 index 000000000..ed6d30c08 --- /dev/null +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -0,0 +1,124 @@ +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.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 _loadBalancerHouse; + private readonly Mock _scopedRepository; + private readonly Mock _loadBalancer; + private readonly string _url; + private readonly TestServer _server; + private readonly HttpClient _client; + private HttpResponseMessage _result; + private OkResponse _request; + private OkResponse _downstreamUrl; + private OkResponse _downstreamRoute; + + public LoadBalancerMiddlewareTests() + { + _url = "http://localhost:51879"; + _loadBalancerHouse = new Mock(); + _scopedRepository = new Mock(); + var builder = new WebHostBuilder() + .ConfigureServices(x => + { + x.AddSingleton(); + x.AddLogging(); + x.AddSingleton(_loadBalancerHouse.Object); + x.AddSingleton(_scopedRepository.Object); + }) + .UseUrls(_url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(_url) + .Configure(app => + { + app.UseLoadBalancingMiddlewareExtensions(); + }); + + _server = new TestServer(builder); + _client = _server.CreateClient(); + } + + [Fact] + public void should_call_scoped_data_repository_correctly() + { + var downstreamRoute = new DownstreamRoute(new List(), + 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(); + } + + private void GivenTheLoadBalancerReturns() + { + _loadBalancer + .Setup(x => x.Lease()) + .Returns(new OkResponse(new HostAndPort("127.0.0.1", 80))); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + _scopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + + private void GivenTheLoadBalancerHouseReturns() + { + _loadBalancerHouse + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse(_loadBalancer.Object)); + } + + private void ThenTheScopedDataRepositoryIsCalledCorrectly() + { + _scopedRepository + .Verify(x => x.Add("Request", _request.Data), Times.Once()); + } + + private void WhenICallTheMiddleware() + { + _result = _client.GetAsync(_url).Result; + } + + private void GivenTheDownStreamUrlIs(string downstreamUrl) + { + _downstreamUrl = new OkResponse(downstreamUrl); + _scopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamUrl); + } + + public void Dispose() + { + _client.Dispose(); + _server.Dispose(); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs deleted file mode 100644 index 9443c113d..000000000 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Collections.Generic; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Responses; -using Ocelot.ServiceDiscovery; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.LoadBalancer -{ - public class ServiceProviderHouseTests - { - private Ocelot.ServiceDiscovery.IServiceProvider _serviceProvider; - private readonly ServiceProviderHouse _serviceProviderHouse; - private Response _addResult; - private Response _getResult; - private string _key; - - public ServiceProviderHouseTests() - { - _serviceProviderHouse = new ServiceProviderHouse(); - } - - [Fact] - public void should_store_service_provider() - { - var key = "test"; - - this.Given(x => x.GivenThereIsAServiceProvider(key, new FakeServiceProvider())) - .When(x => x.WhenIAddTheServiceProvider()) - .Then(x => x.ThenItIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_get_service_provider() - { - var key = "test"; - - this.Given(x => x.GivenThereIsAServiceProvider(key, new FakeServiceProvider())) - .When(x => x.WhenWeGetTheServiceProvider(key)) - .Then(x => x.ThenItIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_store_service_providers_by_key() - { - var key = "test"; - var keyTwo = "testTwo"; - - this.Given(x => x.GivenThereIsAServiceProvider(key, new FakeServiceProvider())) - .And(x => x.GivenThereIsAServiceProvider(keyTwo, new FakeConsulServiceProvider())) - .When(x => x.WhenWeGetTheServiceProvider(key)) - .Then(x => x.ThenTheServiceProviderIs()) - .When(x => x.WhenWeGetTheServiceProvider(keyTwo)) - .Then(x => x.ThenTheServiceProviderIs()) - .BDDfy(); - } - - [Fact] - public void should_return_error_if_no_service_provider_house_with_key() - { - this.When(x => x.WhenWeGetTheServiceProvider("test")) - .Then(x => x.ThenAnErrorIsReturned()) - .BDDfy(); - } - - private void ThenAnErrorIsReturned() - { - _getResult.IsError.ShouldBeTrue(); - _getResult.Errors[0].ShouldBeOfType(); - } - - private void ThenTheServiceProviderIs() - { - _getResult.Data.ShouldBeOfType(); - } - - private void ThenItIsAdded() - { - _addResult.IsError.ShouldBe(false); - _addResult.ShouldBeOfType(); - } - - private void WhenIAddTheServiceProvider() - { - _addResult = _serviceProviderHouse.Add(_key, _serviceProvider); - } - - private void GivenThereIsAServiceProvider(string key, Ocelot.ServiceDiscovery.IServiceProvider serviceProvider) - { - _key = key; - _serviceProvider = serviceProvider; - WhenIAddTheServiceProvider(); - } - - private void WhenWeGetTheServiceProvider(string key) - { - _getResult = _serviceProviderHouse.Get(key); - } - - private void ThenItIsReturned() - { - _getResult.Data.ShouldBe(_serviceProvider); - } - - class FakeServiceProvider : Ocelot.ServiceDiscovery.IServiceProvider - { - public List Get() - { - throw new NotImplementedException(); - } - } - - class FakeConsulServiceProvider : Ocelot.ServiceDiscovery.IServiceProvider - { - public List Get() - { - throw new NotImplementedException(); - } - } - } -} From 666a2e71132df72efe1498ecdb70cbd7f73278a8 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Thu, 2 Feb 2017 21:38:08 +0000 Subject: [PATCH 10/29] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 249a99a31..d96b98686 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ocelot -[![Build status](https://ci.appveyor.com/api/projects/status/roahbe4nl526ysya?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot) +[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot) [![Join the chat at https://gitter.im/Ocelotey/Lobby](https://badges.gitter.im/Ocelotey/Lobby.svg)](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) From f2c6d1c799d909e96d598880af1b5313d45f6f7e Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Thu, 2 Feb 2017 22:34:46 +0000 Subject: [PATCH 11/29] load balancer middle ware test and cake mac osx build script --- build.sh | 101 ++++++++++++++++++ .../ServiceCollectionExtensions.cs | 1 - .../Middleware/LoadBalancingMiddleware.cs | 2 +- .../LoadBalancerMiddlewareTests.cs | 8 +- 4 files changed, 108 insertions(+), 4 deletions(-) create mode 100755 build.sh diff --git a/build.sh b/build.sh new file mode 100755 index 000000000..04731adf1 --- /dev/null +++ b/build.sh @@ -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 \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 86564f924..1615a69b2 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -64,7 +64,6 @@ public static IServiceCollection AddOcelot(this IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index e762186d9..0ef74324b 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -29,7 +29,7 @@ public LoadBalancingMiddleware(RequestDelegate next, public async Task Invoke(HttpContext context) { - _logger.LogDebug("started calling query string builder middleware"); + _logger.LogDebug("started calling load balancing middleware"); var loadBalancer = _loadBalancerHouse.Get($"{DownstreamRoute.ReRoute.UpstreamTemplate}{DownstreamRoute.ReRoute.UpstreamHttpMethod}"); //todo check reponse and return error diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index ed6d30c08..7f34c9035 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -27,6 +27,7 @@ public class LoadBalancerMiddlewareTests private readonly TestServer _server; private readonly HttpClient _client; private HttpResponseMessage _result; + private HostAndPort _hostAndPort; private OkResponse _request; private OkResponse _downstreamUrl; private OkResponse _downstreamRoute; @@ -36,6 +37,8 @@ public LoadBalancerMiddlewareTests() _url = "http://localhost:51879"; _loadBalancerHouse = new Mock(); _scopedRepository = new Mock(); + _loadBalancer = new Mock(); + _loadBalancerHouse = new Mock(); var builder = new WebHostBuilder() .ConfigureServices(x => { @@ -76,9 +79,10 @@ public void should_call_scoped_data_repository_correctly() private void GivenTheLoadBalancerReturns() { + _hostAndPort = new HostAndPort("127.0.0.1", 80); _loadBalancer .Setup(x => x.Lease()) - .Returns(new OkResponse(new HostAndPort("127.0.0.1", 80))); + .Returns(new OkResponse(_hostAndPort)); } private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) @@ -99,7 +103,7 @@ private void GivenTheLoadBalancerHouseReturns() private void ThenTheScopedDataRepositoryIsCalledCorrectly() { _scopedRepository - .Verify(x => x.Add("Request", _request.Data), Times.Once()); + .Verify(x => x.Add("HostAndPort", _hostAndPort), Times.Once()); } private void WhenICallTheMiddleware() From aef6507da347301962f2d7b364b1c24ea084e665 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 3 Feb 2017 07:43:26 +0000 Subject: [PATCH 12/29] fixed failing tests after service discovery changes --- .../FileConfigurationCreatorTests.cs | 49 +++++++++++++++++++ .../LoadBalancer/LoadBalancerFactoryTests.cs | 13 +++++ 2 files changed, 62 insertions(+) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index b6e28033e..b1893b5d7 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -27,6 +27,7 @@ public class FileConfigurationCreatorTests private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator; private readonly Mock _loadBalancerFactory; private readonly Mock _loadBalancerHouse; + private readonly Mock _loadBalancer; public FileConfigurationCreatorTests() { @@ -34,11 +35,39 @@ public FileConfigurationCreatorTests() _configParser = new Mock(); _validator = new Mock(); _fileConfig = new Mock>(); + _loadBalancerFactory = new Mock(); + _loadBalancerHouse = new Mock(); + _loadBalancer = new Mock(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _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 + { + 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] public void should_use_downstream_host() { @@ -70,6 +99,7 @@ public void should_use_downstream_host() .BDDfy(); } + [Fact] public void should_use_downstream_scheme() { this.Given(x => x.GivenTheConfigIs(new FileConfiguration @@ -580,5 +610,24 @@ private void ThenTheAuthenticationOptionsAre(List expectedReRoutes) } } + + private void GivenTheLoadBalancerFactoryReturns() + { + _loadBalancerFactory + .Setup(x => x.Get(It.IsAny())) + .Returns(_loadBalancer.Object); + } + + private void TheLoadBalancerFactoryIsCalledCorrectly() + { + _loadBalancerFactory + .Verify(x => x.Get(It.IsAny()), Times.Once); + } + + private void ThenTheLoadBalancerHouseIsCalledCorrectly() + { + _loadBalancerHouse + .Verify(x => x.Add(It.IsAny(), _loadBalancer.Object), Times.Once); + } } } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index 7361c7265..d645b1b67 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -15,13 +15,22 @@ public class LoadBalancerFactoryTests private LoadBalancerFactory _factory; private ILoadBalancer _result; private Mock _serviceProviderFactory; + private Mock _serviceProvider; public LoadBalancerFactoryTests() { _serviceProviderFactory = new Mock(); + _serviceProvider = new Mock(); _factory = new LoadBalancerFactory(_serviceProviderFactory.Object); } + private void GivenTheServiceProviderFactoryReturns() + { + _serviceProviderFactory + .Setup(x => x.Get(It.IsAny())) + .Returns(_serviceProvider.Object); + } + [Fact] public void should_return_no_load_balancer() { @@ -29,6 +38,7 @@ public void should_return_no_load_balancer() .Build(); this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => x.GivenTheServiceProviderFactoryReturns()) .When(x => x.WhenIGetTheLoadBalancer()) .Then(x => x.ThenTheLoadBalancerIsReturned()) .BDDfy(); @@ -42,6 +52,7 @@ public void should_return_round_robin_load_balancer() .Build(); this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => x.GivenTheServiceProviderFactoryReturns()) .When(x => x.WhenIGetTheLoadBalancer()) .Then(x => x.ThenTheLoadBalancerIsReturned()) .BDDfy(); @@ -55,6 +66,7 @@ public void should_return_round_least_connection_balancer() .Build(); this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => x.GivenTheServiceProviderFactoryReturns()) .When(x => x.WhenIGetTheLoadBalancer()) .Then(x => x.ThenTheLoadBalancerIsReturned()) .BDDfy(); @@ -68,6 +80,7 @@ public void should_call_service_provider() .Build(); this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => x.GivenTheServiceProviderFactoryReturns()) .When(x => x.WhenIGetTheLoadBalancer()) .Then(x => x.ThenTheServiceProviderIsCalledCorrectly()) .BDDfy(); From f285b0e0add544a14e443144477cf3d389a3e229 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 3 Feb 2017 08:00:07 +0000 Subject: [PATCH 13/29] plugged load balancer middleware into Ocelot pipeline, load balanced downstream host and port now used by url creator middleware --- .../Configuration/Builder/ReRouteBuilder.cs | 9 +++++- .../Creator/FileOcelotConfigurationCreator.cs | 10 ++++--- src/Ocelot/Configuration/ReRoute.cs | 4 ++- .../DownstreamUrlCreatorMiddleware.cs | 7 +---- .../Middleware/LoadBalancingMiddleware.cs | 28 +++++++++++-------- .../LoadBalancingMiddlewareExtensions.cs | 2 +- .../Middleware/OcelotMiddlewareExtensions.cs | 4 +++ .../DownstreamUrlCreatorMiddlewareTests.cs | 14 +++++++++- .../LoadBalancerMiddlewareTests.cs | 2 +- 9 files changed, 54 insertions(+), 26 deletions(-) diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 9579330c5..e12b1e4bb 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -6,6 +6,7 @@ namespace Ocelot.Configuration.Builder { public class ReRouteBuilder { + private string _loadBalancerKey; private string _downstreamPathTemplate; private string _upstreamTemplate; private string _upstreamTemplatePattern; @@ -199,6 +200,12 @@ public ReRouteBuilder WithDownstreamPort(int port) return this; } + public ReRouteBuilder WithLoadBalancerKey(string loadBalancerKey) + { + _loadBalancerKey = loadBalancerKey; + return this; + } + public ReRoute Build() { return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, @@ -206,7 +213,7 @@ public ReRoute Build() _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName, _useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, _downstreamScheme, _loadBalancer, - _downstreamHost, _dsPort); + _downstreamHost, _dsPort, _loadBalancerKey); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 61c61fae7..e9ceb41aa 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -104,6 +104,9 @@ private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration gl && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Address) && !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}"; + ReRoute reRoute; if (isAuthenticated) @@ -124,7 +127,7 @@ private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration gl requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), fileReRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort); + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort,loadBalancerKey); } reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), fileReRoute.UpstreamTemplate, @@ -134,11 +137,10 @@ private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration gl requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), fileReRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort); + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort,loadBalancerKey); var loadBalancer = _loadBalanceFactory.Get(reRoute); - //todo - not sure if this is the correct key, but this is probably the only unique key i can think of - _loadBalancerHouse.Add($"{fileReRoute.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}", loadBalancer); + _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); return reRoute; } diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 5cbaaebb1..987f5be7c 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -11,8 +11,9 @@ public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTem List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery, string serviceDiscoveryProvider, string serviceDiscoveryAddress, - string downstreamScheme, string loadBalancer, string downstreamHost, int downstreamPort) + string downstreamScheme, string loadBalancer, string downstreamHost, int downstreamPort, string loadBalancerKey) { + LoadBalancerKey = loadBalancerKey; LoadBalancer = loadBalancer; DownstreamHost = downstreamHost; DownstreamPort = downstreamPort; @@ -39,6 +40,7 @@ public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTem ServiceDiscoveryAddress = serviceDiscoveryAddress; DownstreamScheme = downstreamScheme; } + public string LoadBalancerKey {get;private set;} public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } public string UpstreamTemplate { get; private set; } public string UpstreamTemplatePattern { get; private set; } diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index bca204669..80365074d 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -47,15 +47,12 @@ public async Task Invoke(HttpContext context) var dsScheme = DownstreamRoute.ReRoute.DownstreamScheme; - //todo - get this out of scoped data repo? - var dsHostAndPort = new HostAndPort(DownstreamRoute.ReRoute.DownstreamHost, - DownstreamRoute.ReRoute.DownstreamPort); + var dsHostAndPort = HostAndPort; var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort); if (dsUrl.IsError) { - //todo - release the lb connection? _logger.LogDebug("IUrlBuilder returned an error, setting pipeline error"); SetPipelineError(dsUrl.Errors); @@ -70,8 +67,6 @@ public async Task Invoke(HttpContext context) await _next.Invoke(context); - //todo - release the lb connection? - _logger.LogDebug("succesfully called next middleware"); } } diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index 0ef74324b..99bf5167e 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -31,26 +31,32 @@ public async Task Invoke(HttpContext context) { _logger.LogDebug("started calling load balancing middleware"); - var loadBalancer = _loadBalancerHouse.Get($"{DownstreamRoute.ReRoute.UpstreamTemplate}{DownstreamRoute.ReRoute.UpstreamHttpMethod}"); - //todo check reponse and return error - - var response = loadBalancer.Data.Lease(); - //todo check reponse and return error - - SetHostAndPortForThisRequest(response.Data); + var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey); + if(loadBalancer.IsError) + { + //set errors and return + } + + var hostAndPort = loadBalancer.Data.Lease(); + if(hostAndPort.IsError) + { + //set errors and return + } + + SetHostAndPortForThisRequest(hostAndPort.Data); + _logger.LogDebug("calling next middleware"); - //todo - try next middleware if we get an exception make sure we release - //the host and port? Not sure if this is the way to go but we shall see! try { await _next.Invoke(context); - loadBalancer.Data.Release(response.Data); + loadBalancer.Data.Release(hostAndPort.Data); } catch (Exception) { - loadBalancer.Data.Release(response.Data); + loadBalancer.Data.Release(hostAndPort.Data); + _logger.LogDebug("error calling next middleware, exception will be thrown to global handler"); throw; } diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs index 52d47bdd0..0d0224b8d 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs @@ -4,7 +4,7 @@ namespace Ocelot.LoadBalancer.Middleware { public static class LoadBalancingMiddlewareExtensions { - public static IApplicationBuilder UseLoadBalancingMiddlewareExtensions(this IApplicationBuilder builder) + public static IApplicationBuilder UseLoadBalancingMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware(); } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index dfa3b3f42..b0cd3f7ee 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -18,6 +18,7 @@ namespace Ocelot.Middleware using System.Threading.Tasks; using Authorisation.Middleware; using Microsoft.AspNetCore.Http; + using Ocelot.LoadBalancer.Middleware; public static class OcelotMiddlewareExtensions { @@ -98,6 +99,9 @@ public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder, Oc // Now we can run any query string transformation logic builder.UseQueryStringBuilderMiddleware(); + // Get the load balancer for this request + builder.UseLoadBalancingMiddleware(); + // This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used builder.UseDownstreamUrlCreatorMiddleware(); diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 5581a32ee..a01677b26 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -36,6 +36,7 @@ public class DownstreamUrlCreatorMiddlewareTests : IDisposable private HttpResponseMessage _result; private OkResponse _downstreamPath; private OkResponse _downstreamUrl; + private HostAndPort _hostAndPort; public DownstreamUrlCreatorMiddlewareTests() { @@ -69,14 +70,25 @@ public DownstreamUrlCreatorMiddlewareTests() [Fact] public void should_call_dependencies_correctly() { + var hostAndPort = new HostAndPort("127.0.0.1", 80); + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build()))) + .And(x => x.GivenTheHostAndPortIs(hostAndPort)) .And(x => x.TheUrlReplacerReturns("/api/products/1")) - .And(x => x.TheUrlBuilderReturns("http://www.bbc.co.uk/api/products/1")) + .And(x => x.TheUrlBuilderReturns("http://127.0.0.1:80/api/products/1")) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); } + private void GivenTheHostAndPortIs(HostAndPort hostAndPort) + { + _hostAndPort = hostAndPort; + _scopedRepository + .Setup(x => x.Get("HostAndPort")) + .Returns(new OkResponse(_hostAndPort)); + } + private void TheUrlBuilderReturns(string dsUrl) { _downstreamUrl = new OkResponse(new DownstreamUrl(dsUrl)); diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index 7f34c9035..93c638849 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -54,7 +54,7 @@ public LoadBalancerMiddlewareTests() .UseUrls(_url) .Configure(app => { - app.UseLoadBalancingMiddlewareExtensions(); + app.UseLoadBalancingMiddleware(); }); _server = new TestServer(builder); From c88eeb66bb42a807ef189d9570e388c0a5c7a245 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 3 Feb 2017 08:58:56 +0000 Subject: [PATCH 14/29] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d96b98686..2e57bd6c1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ocelot -[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot) +[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-build) [![Join the chat at https://gitter.im/Ocelotey/Lobby](https://badges.gitter.im/Ocelotey/Lobby.svg)](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) From 4577ec60d5dc1262ad38da0ef216279499372e77 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 3 Feb 2017 08:59:45 +0000 Subject: [PATCH 15/29] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e57bd6c1..ae1ef0338 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ocelot -[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-build) +[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb) [![Join the chat at https://gitter.im/Ocelotey/Lobby](https://badges.gitter.im/Ocelotey/Lobby.svg)](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) From b0ff2fd317dff2d78844f547a9ab0f94ade9484c Mon Sep 17 00:00:00 2001 From: "tom.pallister" Date: Fri, 3 Feb 2017 13:02:51 +0000 Subject: [PATCH 16/29] fixed failing tests --- .../Creator/FileOcelotConfigurationCreator.cs | 30 +++++++++++-------- .../ServiceCollectionExtensions.cs | 4 +-- .../FileConfigurationCreatorTests.cs | 2 ++ 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index e9ceb41aa..2bbc67058 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -108,7 +108,7 @@ private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration gl var loadBalancerKey = $"{fileReRoute.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}"; ReRoute reRoute; - + if (isAuthenticated) { var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider, @@ -120,24 +120,30 @@ private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration gl var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest); var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest); - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), fileReRoute.UpstreamTemplate, + reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), + fileReRoute.UpstreamTemplate, fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, authOptionsForRoute, claimsToHeaders, claimsToClaims, fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), - fileReRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort,loadBalancerKey); + fileReRoute.ServiceName, useServiceDiscovery, + globalConfiguration?.ServiceDiscoveryProvider?.Provider, + globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey); } - - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), fileReRoute.UpstreamTemplate, - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, - null, new List(), new List(), - fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), + else + { + reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), + fileReRoute.UpstreamTemplate, + fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, + null, new List(), new List(), + fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), - fileReRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, + fileReRoute.ServiceName, useServiceDiscovery, + globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort,loadBalancerKey); + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey); + } var loadBalancer = _loadBalanceFactory.Get(reRoute); _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 1615a69b2..839a825b5 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -61,8 +61,8 @@ public static IServiceCollection AddOcelot(this IServiceCollection services) { services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index b1893b5d7..65a612400 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -410,6 +410,7 @@ public void should_create_with_headers_to_extract() })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheConfigHeaderExtractorReturns(new ClaimToThing("CustomerId", "CustomerId", "", 0))) + .And(x => x.GivenTheLoadBalancerFactoryReturns()) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(expected)) .And(x => x.ThenTheAuthenticationOptionsAre(expected)) @@ -464,6 +465,7 @@ public void should_create_with_authentication_properties() } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheLoadBalancerFactoryReturns()) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(expected)) .And(x => x.ThenTheAuthenticationOptionsAre(expected)) From 9828c3b427afe744f7f60dcf6ddf2c49bf559feb Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 3 Feb 2017 22:50:57 +0000 Subject: [PATCH 17/29] started adding consul acceptance test --- build.cake | 4 +- src/Ocelot/Configuration/ReRoute.cs | 3 +- .../ServiceCollectionExtensions.cs | 2 +- src/Ocelot/Errors/OcelotErrorCode.cs | 2 +- .../LoadBalancers/LoadBalancerFactory.cs | 9 +- .../ConfigurationServiceProvider.cs | 4 +- ...ovider.cs => IServiceDiscoveryProvider.cs} | 2 +- .../IServiceDiscoveryProviderFactory.cs | 9 ++ .../IServiceProviderFactory.cs | 9 -- .../ServiceDiscoveryProviderFactory.cs | 18 +++ .../ServiceProviderConfiguraion.cs | 21 +++ .../ServiceProviderFactory.cs | 34 ----- ...ableToFindServiceDiscoveryProviderError.cs | 12 ++ .../UnableToFindServiceProviderError.cs | 12 -- .../ServiceDiscoveryTests.cs | 135 ++++++++++++++++++ test/Ocelot.AcceptanceTests/Steps.cs | 13 ++ .../LoadBalancer/LoadBalancerFactoryTests.cs | 12 +- .../ServiceProviderFactoryTests.cs | 12 +- 18 files changed, 234 insertions(+), 79 deletions(-) rename src/Ocelot/ServiceDiscovery/{IServiceProvider.cs => IServiceDiscoveryProvider.cs} (74%) create mode 100644 src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs delete mode 100644 src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs create mode 100644 src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs create mode 100644 src/Ocelot/ServiceDiscovery/ServiceProviderConfiguraion.cs delete mode 100644 src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs create mode 100644 src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs delete mode 100644 src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs create mode 100644 test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs diff --git a/build.cake b/build.cake index 1d798d743..26b45b25b 100644 --- a/build.cake +++ b/build.cake @@ -42,8 +42,8 @@ var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; // internal build variables - don't change these. var releaseTag = ""; -var buildVersion = committedVersion; var committedVersion = "0.0.0-dev"; +var buildVersion = committedVersion; var target = Argument("target", "Default"); @@ -264,7 +264,7 @@ private string GetNuGetVersionForCommit() /// Updates project version in all of our projects private void PersistVersion(string version) { - Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version)); + //Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version)); var projectJsonFiles = GetFiles("./**/project.json"); diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 987f5be7c..d9fe60c90 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -11,7 +11,8 @@ public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTem List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery, string serviceDiscoveryProvider, string serviceDiscoveryAddress, - string downstreamScheme, string loadBalancer, string downstreamHost, int downstreamPort, string loadBalancerKey) + string downstreamScheme, string loadBalancer, string downstreamHost, int downstreamPort, + string loadBalancerKey) { LoadBalancerKey = loadBalancerKey; LoadBalancer = loadBalancer; diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 839a825b5..0a6bd42c3 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -61,7 +61,7 @@ public static IServiceCollection AddOcelot(this IServiceCollection services) { services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index f2c479df4..d24988b95 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -24,7 +24,7 @@ public enum OcelotErrorCode DownstreamHostNullOrEmptyError, ServicesAreNullError, ServicesAreEmptyError, - UnableToFindServiceProviderError, + UnableToFindServiceDiscoveryProviderError, UnableToFindLoadBalancerError } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs index 7e11df394..082f3b616 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs @@ -5,19 +5,20 @@ namespace Ocelot.LoadBalancer.LoadBalancers { public class LoadBalancerFactory : ILoadBalancerFactory { - private readonly IServiceProviderFactory _serviceProviderFactory; - public LoadBalancerFactory(IServiceProviderFactory serviceProviderFactory) + private readonly IServiceDiscoveryProviderFactory _serviceProviderFactory; + public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFactory) { _serviceProviderFactory = serviceProviderFactory; } public ILoadBalancer Get(ReRoute reRoute) { - var serviceConfig = new ServiceConfiguraion( + var serviceConfig = new ServiceProviderConfiguraion( reRoute.ServiceName, reRoute.DownstreamHost, reRoute.DownstreamPort, - reRoute.UseServiceDiscovery); + reRoute.UseServiceDiscovery, + reRoute.ServiceDiscoveryProvider); var serviceProvider = _serviceProviderFactory.Get(serviceConfig); diff --git a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs index b207f7721..f1045be3b 100644 --- a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs +++ b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using Ocelot.Values; - + namespace Ocelot.ServiceDiscovery { - public class ConfigurationServiceProvider : IServiceProvider + public class ConfigurationServiceProvider : IServiceDiscoveryProvider { private List _services; diff --git a/src/Ocelot/ServiceDiscovery/IServiceProvider.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs similarity index 74% rename from src/Ocelot/ServiceDiscovery/IServiceProvider.cs rename to src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs index 60e428c85..2732b5e3e 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceProvider.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs @@ -3,7 +3,7 @@ namespace Ocelot.ServiceDiscovery { - public interface IServiceProvider + public interface IServiceDiscoveryProvider { List Get(); } diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs new file mode 100644 index 000000000..fe2acaa8c --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ocelot.ServiceDiscovery +{ + public interface IServiceDiscoveryProviderFactory + { + IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig); + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs deleted file mode 100644 index 62c55f539..000000000 --- a/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Ocelot.ServiceDiscovery -{ - public interface IServiceProviderFactory - { - IServiceProvider Get(ServiceConfiguraion serviceConfig); - } -} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs new file mode 100644 index 000000000..cb06e99cc --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery +{ + public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory + { + public IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig) + { + var services = new List() + { + new Service(serviceConfig.ServiceName, new HostAndPort(serviceConfig.DownstreamHost, serviceConfig.DownstreamPort)) + }; + + return new ConfigurationServiceProvider(services); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceProviderConfiguraion.cs b/src/Ocelot/ServiceDiscovery/ServiceProviderConfiguraion.cs new file mode 100644 index 000000000..70638aaaf --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ServiceProviderConfiguraion.cs @@ -0,0 +1,21 @@ +namespace Ocelot.ServiceDiscovery +{ + public class ServiceProviderConfiguraion + { + public ServiceProviderConfiguraion(string serviceName, string downstreamHost, + int downstreamPort, bool useServiceDiscovery, string serviceDiscoveryProvider) + { + ServiceName = serviceName; + DownstreamHost = downstreamHost; + DownstreamPort = downstreamPort; + UseServiceDiscovery = useServiceDiscovery; + ServiceDiscoveryProvider = serviceDiscoveryProvider; + } + + public string ServiceName { get; } + public string DownstreamHost { get; } + public int DownstreamPort { get; } + public bool UseServiceDiscovery { get; } + public string ServiceDiscoveryProvider {get;} + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs deleted file mode 100644 index be3b8b8c9..000000000 --- a/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using Ocelot.Values; - -namespace Ocelot.ServiceDiscovery -{ - public class ServiceProviderFactory : IServiceProviderFactory - { - public IServiceProvider Get(ServiceConfiguraion serviceConfig) - { - var services = new List() - { - new Service(serviceConfig.ServiceName, new HostAndPort(serviceConfig.DownstreamHost, serviceConfig.DownstreamPort)) - }; - - return new ConfigurationServiceProvider(services); - } - } - - public class ServiceConfiguraion - { - public ServiceConfiguraion(string serviceName, string downstreamHost, int downstreamPort, bool useServiceDiscovery) - { - ServiceName = serviceName; - DownstreamHost = downstreamHost; - DownstreamPort = downstreamPort; - UseServiceDiscovery = useServiceDiscovery; - } - - public string ServiceName { get; } - public string DownstreamHost { get; } - public int DownstreamPort { get; } - public bool UseServiceDiscovery { get; } - } -} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs b/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs new file mode 100644 index 000000000..163e63ef9 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.ServiceDiscovery +{ + public class UnableToFindServiceDiscoveryProviderError : Error + { + public UnableToFindServiceDiscoveryProviderError(string message) + : base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs b/src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs deleted file mode 100644 index b8ed1a47f..000000000 --- a/src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.ServiceDiscovery -{ - public class UnableToFindServiceProviderError : Error - { - public UnableToFindServiceProviderError(string message) - : base(message, OcelotErrorCode.UnableToFindServiceProviderError) - { - } - } -} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs new file mode 100644 index 000000000..5994b86bb --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +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 _builder; + private IWebHost _fakeConsulBuilder; + private readonly Steps _steps; + + public ServiceDiscoveryTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_use_service_discovery_and_load_balance_request() + { + var serviceName = "product"; + var downstreamServiceOneUrl = "http://localhost:51879"; + var downstreamServiceTwoUrl = "http://localhost:51880"; + var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; + var downstreamServiceOneCounter = 0; + var downstreamServiceTwoCounter = 0; + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + ServiceName = serviceName, + LoadBalancer = "LeastConnection", + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Provider = "Consul" + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, 200, downstreamServiceOneCounter)) + .And(x => x.GivenThereIsAServiceRunningOn(downstreamServiceTwoUrl, 200, downstreamServiceTwoCounter)) + .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceName, downstreamServiceOneUrl, downstreamServiceTwoUrl)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50)) + .Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50, downstreamServiceOneCounter, downstreamServiceTwoCounter)) + .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(downstreamServiceOneCounter,downstreamServiceTwoCounter)) + .BDDfy(); + } + + private void ThenBothServicesCalledRealisticAmountOfTimes(int counterOne, int counterTwo) + { + counterOne.ShouldBeGreaterThan(10); + counterTwo.ShouldBeGreaterThan(10); + } + + private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected, int counterOne, int counterTwo) + { + var total = counterOne + counterTwo; + total.ShouldBe(expected); + } + + private void GivenTheServicesAreRegisteredWithConsul(string serviceName, params string[] urls) + { + //register these services with fake consul + } + + private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) + { + _fakeConsulBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + //do consul shit + }); + }) + .Build(); + + _fakeConsulBuilder.Start(); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, int counter) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + counter++; + context.Response.StatusCode = statusCode; + }); + }) + .Build(); + + _builder.Start(); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index c2bd7ee7a..666c32562 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Threading.Tasks; using CacheManager.Core; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; @@ -153,6 +154,18 @@ public void WhenIGetUrlOnTheApiGateway(string url) _response = _ocelotClient.GetAsync(url).Result; } + public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) + { + var tasks = new Task[times]; + + for (int i = 0; i < times; i++) + { + tasks[i] = _ocelotClient.GetAsync(url); + } + + Task.WaitAll(tasks); + } + public void WhenIGetUrlOnTheApiGateway(string url, string requestId) { _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId); diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index d645b1b67..e8e0210b0 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -14,20 +14,20 @@ public class LoadBalancerFactoryTests private ReRoute _reRoute; private LoadBalancerFactory _factory; private ILoadBalancer _result; - private Mock _serviceProviderFactory; - private Mock _serviceProvider; + private Mock _serviceProviderFactory; + private Mock _serviceProvider; public LoadBalancerFactoryTests() { - _serviceProviderFactory = new Mock(); - _serviceProvider = new Mock(); + _serviceProviderFactory = new Mock(); + _serviceProvider = new Mock(); _factory = new LoadBalancerFactory(_serviceProviderFactory.Object); } private void GivenTheServiceProviderFactoryReturns() { _serviceProviderFactory - .Setup(x => x.Get(It.IsAny())) + .Setup(x => x.Get(It.IsAny())) .Returns(_serviceProvider.Object); } @@ -89,7 +89,7 @@ public void should_call_service_provider() private void ThenTheServiceProviderIsCalledCorrectly() { _serviceProviderFactory - .Verify(x => x.Get(It.IsAny()), Times.Once); + .Verify(x => x.Get(It.IsAny()), Times.Once); } private void GivenAReRoute(ReRoute reRoute) diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 9bcc2672c..82e5cb73f 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -7,19 +7,19 @@ namespace Ocelot.UnitTests.ServiceDiscovery { public class ServiceProviderFactoryTests { - private ServiceConfiguraion _serviceConfig; - private IServiceProvider _result; - private readonly ServiceProviderFactory _factory; + private ServiceProviderConfiguraion _serviceConfig; + private IServiceDiscoveryProvider _result; + private readonly ServiceDiscoveryProviderFactory _factory; public ServiceProviderFactoryTests() { - _factory = new ServiceProviderFactory(); + _factory = new ServiceDiscoveryProviderFactory(); } [Fact] public void should_return_no_service_provider() { - var serviceConfig = new ServiceConfiguraion("product", "127.0.0.1", 80, false); + var serviceConfig = new ServiceProviderConfiguraion("product", "127.0.0.1", 80, false, "Does not matter"); this.Given(x => x.GivenTheReRoute(serviceConfig)) .When(x => x.WhenIGetTheServiceProvider()) @@ -27,7 +27,7 @@ public void should_return_no_service_provider() .BDDfy(); } - private void GivenTheReRoute(ServiceConfiguraion serviceConfig) + private void GivenTheReRoute(ServiceProviderConfiguraion serviceConfig) { _serviceConfig = serviceConfig; } From 2a03d33d7e8e5252a00be671afc5d455409b4a86 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 3 Feb 2017 22:53:53 +0000 Subject: [PATCH 18/29] added cake log back in --- build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cake b/build.cake index 26b45b25b..1a1f005d8 100644 --- a/build.cake +++ b/build.cake @@ -264,7 +264,7 @@ private string GetNuGetVersionForCommit() /// Updates project version in all of our projects private void PersistVersion(string version) { - //Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version)); + Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version)); var projectJsonFiles = GetFiles("./**/project.json"); From 9c9315a94f45bead2d5f80e7de444510408356bd Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 3 Feb 2017 22:59:00 +0000 Subject: [PATCH 19/29] updated tests url --- test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index 5994b86bb..b2134a702 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -27,8 +27,8 @@ public ServiceDiscoveryTests() public void should_use_service_discovery_and_load_balance_request() { var serviceName = "product"; - var downstreamServiceOneUrl = "http://localhost:51879"; - var downstreamServiceTwoUrl = "http://localhost:51880"; + var downstreamServiceOneUrl = "http://localhost:50879"; + var downstreamServiceTwoUrl = "http://localhost:50880"; var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; var downstreamServiceOneCounter = 0; var downstreamServiceTwoCounter = 0; From 84f01433b5ed2bc5f71753f5a90236d43cdeca40 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 4 Feb 2017 11:47:07 +0000 Subject: [PATCH 20/29] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae1ef0338..cb5e8bd53 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Install Ocelot and it's dependecies using nuget. At the moment all we have is the pre version. Once we have something working in a half decent way we will drop a version. -`Install-Package Ocelot -Pre` +`Install-Package Ocelot` All versions can be found [here](https://www.nuget.org/packages/Ocelot/) From 7900aa3f49553ce7057650e14173660a103eefd1 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 4 Feb 2017 12:06:33 +0000 Subject: [PATCH 21/29] got sidetracked and downgraded to .net core app 1.1 that actually exists no idea why i had 1.4 :( --- .../ConsulServiceDiscoveryProvider.cs | 16 +++++ .../ServiceDiscoveryProviderFactory.cs | 10 ++++ src/Ocelot/project.json | 59 ++++++++++--------- .../TestConfiguration.cs | 4 +- test/Ocelot.AcceptanceTests/project.json | 2 +- test/Ocelot.Benchmarks/project.json | 4 +- test/Ocelot.ManualTest/project.json | 2 +- .../ServiceProviderFactoryTests.cs | 11 ++++ test/Ocelot.UnitTests/project.json | 2 +- 9 files changed, 73 insertions(+), 37 deletions(-) create mode 100644 src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs diff --git a/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs new file mode 100644 index 000000000..af5c4ddc4 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery +{ + public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider + { + public List Get() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index cb06e99cc..e87014186 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -7,6 +7,11 @@ public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory { public IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig) { + if (serviceConfig.UseServiceDiscovery) + { + return GetServiceDiscoveryProvider(serviceConfig.ServiceName, serviceConfig.ServiceDiscoveryProvider); + } + var services = new List() { new Service(serviceConfig.ServiceName, new HostAndPort(serviceConfig.DownstreamHost, serviceConfig.DownstreamPort)) @@ -14,5 +19,10 @@ public IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig) return new ConfigurationServiceProvider(services); } + + private IServiceDiscoveryProvider GetServiceDiscoveryProvider(string serviceName, string serviceProviderName) + { + return new ConsulServiceDiscoveryProvider(); + } } } \ No newline at end of file diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 8d259469c..6ce4ffbba 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -1,41 +1,42 @@ { "version": "0.0.0-dev", - "dependencies": { - "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", - "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", - "Microsoft.Extensions.Configuration.Json": "1.1.0", - "Microsoft.Extensions.Logging": "1.1.0", - "Microsoft.Extensions.Logging.Console": "1.1.0", - "Microsoft.Extensions.Logging.Debug": "1.1.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", - "Microsoft.AspNetCore.Http": "1.1.0", - "System.Text.RegularExpressions": "4.3.0", - "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", - "Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0", - "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0", - "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0", - "Microsoft.AspNetCore.Authentication.Google": "1.1.0", - "Microsoft.AspNetCore.Authentication.Facebook": "1.1.0", - "Microsoft.AspNetCore.Authentication.Twitter": "1.1.0", - "Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0", - "Microsoft.AspNetCore.Authentication": "1.1.0", - "IdentityServer4.AccessTokenValidation": "1.0.2", - "Microsoft.AspNetCore.Mvc": "1.1.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", - "Microsoft.NETCore.App": "1.1.0", - "CacheManager.Core": "0.9.2", - "CacheManager.Microsoft.Extensions.Configuration": "0.9.2", - "CacheManager.Microsoft.Extensions.Logging": "0.9.2" - }, + "dependencies": { + "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", + "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", + "Microsoft.Extensions.Configuration.Json": "1.1.0", + "Microsoft.Extensions.Logging": "1.1.0", + "Microsoft.Extensions.Logging.Console": "1.1.0", + "Microsoft.Extensions.Logging.Debug": "1.1.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", + "Microsoft.AspNetCore.Http": "1.1.0", + "System.Text.RegularExpressions": "4.3.0", + "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", + "Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0", + "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0", + "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0", + "Microsoft.AspNetCore.Authentication.Google": "1.1.0", + "Microsoft.AspNetCore.Authentication.Facebook": "1.1.0", + "Microsoft.AspNetCore.Authentication.Twitter": "1.1.0", + "Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0", + "Microsoft.AspNetCore.Authentication": "1.1.0", + "IdentityServer4.AccessTokenValidation": "1.0.2", + "Microsoft.AspNetCore.Mvc": "1.1.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", + "Microsoft.NETCore.App": "1.1.0", + "CacheManager.Core": "0.9.2", + "CacheManager.Microsoft.Extensions.Configuration": "0.9.2", + "CacheManager.Microsoft.Extensions.Logging": "0.9.2", + "Consul": "0.7.2.1" + }, "runtimes": { "win10-x64": {}, "osx.10.11-x64":{}, "win7-x64": {} }, "frameworks": { - "netcoreapp1.4": { + "netcoreapp1.1": { "imports": [ ] } diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index 0aa730be0..ce802efb6 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -4,14 +4,12 @@ public static class TestConfiguration { - public static double Version => 1.4; + public static double Version => 1.1; public static string ConfigurationPath => GetConfigurationPath(); public static string GetConfigurationPath() { var osArchitecture = RuntimeInformation.OSArchitecture.ToString(); - - var oSDescription = string.Empty; if(RuntimeInformation.OSDescription.ToLower().Contains("darwin")) { diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 17f35a3c7..2e5f9ee89 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -40,7 +40,7 @@ "win7-x64": {} }, "frameworks": { - "netcoreapp1.4": { + "netcoreapp1.1": { "imports": [ ] } diff --git a/test/Ocelot.Benchmarks/project.json b/test/Ocelot.Benchmarks/project.json index 5f7a49871..061a2223c 100644 --- a/test/Ocelot.Benchmarks/project.json +++ b/test/Ocelot.Benchmarks/project.json @@ -6,7 +6,7 @@ "dependencies": { "Ocelot": "0.0.0-dev", - "BenchmarkDotNet": "0.10.1" + "BenchmarkDotNet": "0.10.2" }, "runtimes": { "win10-x64": {}, @@ -14,7 +14,7 @@ "win7-x64": {} }, "frameworks": { - "netcoreapp1.4": { + "netcoreapp1.1": { "imports": [ ] } diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index 3ae09ccbd..cf67f9bd1 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -24,7 +24,7 @@ "win7-x64": {} }, "frameworks": { - "netcoreapp1.4": { + "netcoreapp1.1": { "imports": [ ] } diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 82e5cb73f..97fb265ab 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -27,6 +27,17 @@ public void should_return_no_service_provider() .BDDfy(); } + [Fact] + public void should_return_consul_service_provider() + { + var serviceConfig = new ServiceProviderConfiguraion("product", string.Empty, 0, true, "Consul"); + + this.Given(x => x.GivenTheReRoute(serviceConfig)) + .When(x => x.WhenIGetTheServiceProvider()) + .Then(x => x.ThenTheServiceProviderIs()) + .BDDfy(); + } + private void GivenTheReRoute(ServiceProviderConfiguraion serviceConfig) { _serviceConfig = serviceConfig; diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index ab3e6cb17..3151ac57c 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -32,7 +32,7 @@ "win7-x64": {} }, "frameworks": { - "netcoreapp1.4": { + "netcoreapp1.1": { "imports": [ ] } From c46dcc05b82207b81a92e5f069ac26d11e20c493 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 4 Feb 2017 13:16:31 +0000 Subject: [PATCH 22/29] started implementing the consul service provider --- .../Configuration/Builder/ReRouteBuilder.cs | 19 +++++-- .../Creator/FileOcelotConfigurationCreator.cs | 38 +++++++------- .../Creator/IOcelotConfigurationCreator.cs | 3 +- .../File/FileServiceDiscoveryProvider.cs | 3 +- .../Provider/IOcelotConfigurationProvider.cs | 5 +- .../Provider/OcelotConfigurationProvider.cs | 7 +-- src/Ocelot/Configuration/ReRoute.cs | 29 +++++------ .../ServiceProviderConfiguraion.cs | 12 +++-- .../Finder/DownstreamRouteFinder.cs | 5 +- .../Finder/IDownstreamRouteFinder.cs | 5 +- .../DownstreamRouteFinderMiddleware.cs | 2 +- .../Extensions/StringExtensions.cs | 24 +++++++++ .../LoadBalancers/ILoadBalancer.cs | 3 +- .../LoadBalancers/ILoadBalancerFactory.cs | 5 +- .../LeastConnectionLoadBalancer.cs | 13 ++--- .../LoadBalancers/LoadBalancerFactory.cs | 23 +++++---- .../LoadBalancers/NoLoadBalancer.cs | 3 +- .../LoadBalancers/RoundRobinLoadBalancer.cs | 3 +- .../Middleware/LoadBalancingMiddleware.cs | 2 +- .../ConfigurationServiceProvider.cs | 5 +- .../ConsulRegistryConfiguration.cs | 16 ++++++ .../ConsulServiceDiscoveryProvider.cs | 43 +++++++++++++++- .../IServiceDiscoveryProvider.cs | 3 +- .../IServiceDiscoveryProviderFactory.cs | 1 + .../ServiceDiscoveryProviderFactory.cs | 14 +++-- src/Ocelot/Values/Service.cs | 22 ++++++-- .../ServiceDiscoveryTests.cs | 5 +- .../Ocelot.AcceptanceTests/configuration.json | 2 +- .../FileConfigurationCreatorTests.cs | 6 +-- .../FileConfigurationProviderTests.cs | 4 +- .../DownstreamRouteFinderMiddlewareTests.cs | 2 +- .../DownstreamRouteFinderTests.cs | 4 +- .../LoadBalancer/LeastConnectionTests.cs | 51 ++++++++++--------- .../LoadBalancer/LoadBalancerFactoryTests.cs | 2 +- .../LoadBalancer/LoadBalancerHouseTests.cs | 5 +- .../LoadBalancerMiddlewareTests.cs | 2 +- .../LoadBalancer/NoLoadBalancerTests.cs | 4 +- .../LoadBalancer/RoundRobinTests.cs | 14 ++--- .../ConfigurationServiceProviderTests.cs | 4 +- .../ServiceProviderFactoryTests.cs | 5 +- .../ServiceDiscovery/ServiceRegistryTests.cs | 4 +- 41 files changed, 282 insertions(+), 140 deletions(-) rename src/Ocelot/{ServiceDiscovery => Configuration}/ServiceProviderConfiguraion.cs (53%) create mode 100644 src/Ocelot/Infrastructure/Extensions/StringExtensions.cs create mode 100644 src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index e12b1e4bb..caa09d3f9 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -34,6 +34,8 @@ public class ReRouteBuilder private string _downstreamHost; private int _dsPort; private string _loadBalancer; + private string _serviceProviderHost; + private int _serviceProviderPort; public ReRouteBuilder() { @@ -206,14 +208,25 @@ public ReRouteBuilder WithLoadBalancerKey(string loadBalancerKey) return this; } + public ReRouteBuilder WithServiceProviderHost(string serviceProviderHost) + { + _serviceProviderHost = serviceProviderHost; + return this; + } + + public ReRouteBuilder WithServiceProviderPort(int serviceProviderPort) + { + _serviceProviderPort = serviceProviderPort; + return this; + } + public ReRoute Build() { return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, - _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName, - _useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, _downstreamScheme, _loadBalancer, - _downstreamHost, _dsPort, _loadBalancerKey); + _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer, + _downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort)); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 2bbc67058..703239d0f 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Ocelot.Configuration.File; @@ -8,7 +9,6 @@ using Ocelot.Configuration.Validator; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; -using Ocelot.ServiceDiscovery; using Ocelot.Utilities; using Ocelot.Values; @@ -46,9 +46,9 @@ public FileOcelotConfigurationCreator( _logger = logger; } - public Response Create() + public async Task> Create() { - var config = SetUpConfiguration(); + var config = await SetUpConfiguration(); return new OkResponse(config); } @@ -57,7 +57,7 @@ public Response Create() /// 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 /// - private IOcelotConfiguration SetUpConfiguration() + private async Task SetUpConfiguration() { var response = _configurationValidator.IsValid(_options.Value); @@ -77,14 +77,14 @@ private IOcelotConfiguration SetUpConfiguration() foreach (var reRoute in _options.Value.ReRoutes) { - var ocelotReRoute = SetUpReRoute(reRoute, _options.Value.GlobalConfiguration); + var ocelotReRoute = await SetUpReRoute(reRoute, _options.Value.GlobalConfiguration); reRoutes.Add(ocelotReRoute); } return new OcelotConfiguration(reRoutes); } - private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + private async Task SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) { var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); @@ -101,7 +101,6 @@ private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration gl : fileReRoute.RequestIdKey; var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName) - && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Address) && !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 @@ -109,6 +108,13 @@ private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration gl ReRoute reRoute; + var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; + + var serviceProviderConfiguration = new ServiceProviderConfiguraion(fileReRoute.ServiceName, + fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, useServiceDiscovery, + globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Host, + serviceProviderPort); + if (isAuthenticated) { var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider, @@ -125,11 +131,10 @@ private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration gl fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, authOptionsForRoute, claimsToHeaders, claimsToClaims, fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), - fileReRoute.ServiceName, useServiceDiscovery, - globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey); + requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds) + , fileReRoute.DownstreamScheme, + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, + serviceProviderConfiguration); } else { @@ -139,13 +144,12 @@ private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration gl null, new List(), new List(), fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), - fileReRoute.ServiceName, useServiceDiscovery, - globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey); + fileReRoute.DownstreamScheme, + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, + serviceProviderConfiguration); } - var loadBalancer = _loadBalanceFactory.Get(reRoute); + var loadBalancer = await _loadBalanceFactory.Get(reRoute); _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); return reRoute; } diff --git a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs index 6cc7c2e8d..7547d91f0 100644 --- a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs @@ -1,9 +1,10 @@ +using System.Threading.Tasks; using Ocelot.Responses; namespace Ocelot.Configuration.Creator { public interface IOcelotConfigurationCreator { - Response Create(); + Task> Create(); } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs index 47efc6dfd..2f26b6eac 100644 --- a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs +++ b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs @@ -3,6 +3,7 @@ namespace Ocelot.Configuration.File public class FileServiceDiscoveryProvider { public string Provider {get;set;} - public string Address {get;set;} + public string Host {get;set;} + public int Port { get; set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs index 30ded2e9e..3256e44a4 100644 --- a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs @@ -1,9 +1,10 @@ -using Ocelot.Responses; +using System.Threading.Tasks; +using Ocelot.Responses; namespace Ocelot.Configuration.Provider { public interface IOcelotConfigurationProvider { - Response Get(); + Task> Get(); } } diff --git a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs index 4b6c5fd2d..80fd56970 100644 --- a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs @@ -1,4 +1,5 @@ -using Ocelot.Configuration.Creator; +using System.Threading.Tasks; +using Ocelot.Configuration.Creator; using Ocelot.Configuration.Repository; using Ocelot.Responses; @@ -19,7 +20,7 @@ public OcelotConfigurationProvider(IOcelotConfigurationRepository repo, _creator = creator; } - public Response Get() + public async Task> Get() { var repoConfig = _repo.Get(); @@ -30,7 +31,7 @@ public Response Get() if (repoConfig.Data == null) { - var creatorConfig = _creator.Create(); + var creatorConfig = await _creator.Create(); if (creatorConfig.IsError) { diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index d9fe60c90..278d07460 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -6,15 +6,20 @@ namespace Ocelot.Configuration { public class ReRoute { - public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, - bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, - List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, - string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery, - string serviceDiscoveryProvider, string serviceDiscoveryAddress, - string downstreamScheme, string loadBalancer, string downstreamHost, int downstreamPort, - string loadBalancerKey) + public ReRoute(DownstreamPathTemplate downstreamPathTemplate, + string upstreamTemplate, string upstreamHttpMethod, + string upstreamTemplatePattern, + bool isAuthenticated, AuthenticationOptions authenticationOptions, + List configurationHeaderExtractorProperties, + List claimsToClaims, + Dictionary routeClaimsRequirement, bool isAuthorised, + List 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; @@ -35,12 +40,9 @@ public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTem ?? new List(); ClaimsToHeaders = configurationHeaderExtractorProperties ?? new List(); - ServiceName = serviceName; - UseServiceDiscovery = useServiceDiscovery; - ServiceDiscoveryProvider = serviceDiscoveryProvider; - ServiceDiscoveryAddress = serviceDiscoveryAddress; DownstreamScheme = downstreamScheme; } + public string LoadBalancerKey {get;private set;} public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } public string UpstreamTemplate { get; private set; } @@ -56,13 +58,10 @@ public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTem public string RequestIdKey { get; private set; } public bool IsCached { get; private set; } public CacheOptions FileCacheOptions { get; private set; } - public string ServiceName { get; private set;} - public bool UseServiceDiscovery { get; private set;} - public string ServiceDiscoveryProvider { get; private set;} - public string ServiceDiscoveryAddress { get; private set;} public 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; } } } \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceProviderConfiguraion.cs b/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs similarity index 53% rename from src/Ocelot/ServiceDiscovery/ServiceProviderConfiguraion.cs rename to src/Ocelot/Configuration/ServiceProviderConfiguraion.cs index 70638aaaf..d471a9e51 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceProviderConfiguraion.cs +++ b/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs @@ -1,21 +1,25 @@ -namespace Ocelot.ServiceDiscovery +namespace Ocelot.Configuration { public class ServiceProviderConfiguraion { - public ServiceProviderConfiguraion(string serviceName, string downstreamHost, - int downstreamPort, bool useServiceDiscovery, string serviceDiscoveryProvider) + 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 ServiceDiscoveryProvider { get; } + public string ServiceProviderHost { get; private set; } + public int ServiceProviderPort { get; private set; } } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 752da281b..eacd69124 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Ocelot.Configuration.Provider; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Errors; @@ -21,9 +22,9 @@ public DownstreamRouteFinder(IOcelotConfigurationProvider configProvider, IUrlPa _urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; } - public Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod) + public async Task> 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)); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs index e351ab2f0..7ae3ff791 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs @@ -1,9 +1,10 @@ -using Ocelot.Responses; +using System.Threading.Tasks; +using Ocelot.Responses; namespace Ocelot.DownstreamRouteFinder.Finder { public interface IDownstreamRouteFinder { - Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod); + Task> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod); } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index f445b46bf..e88bfde8a 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -34,7 +34,7 @@ public async Task Invoke(HttpContext context) _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) { diff --git a/src/Ocelot/Infrastructure/Extensions/StringExtensions.cs b/src/Ocelot/Infrastructure/Extensions/StringExtensions.cs new file mode 100644 index 000000000..d74583811 --- /dev/null +++ b/src/Ocelot/Infrastructure/Extensions/StringExtensions.cs @@ -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; + } + + } +} \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs index 100ee6f0c..aa2a8f02f 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Ocelot.Responses; using Ocelot.Values; @@ -6,7 +7,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers { public interface ILoadBalancer { - Response Lease(); + Task> Lease(); Response Release(HostAndPort hostAndPort); } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs index 55089cdec..19fdf3ebe 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs @@ -1,9 +1,10 @@ -using Ocelot.Configuration; +using System.Threading.Tasks; +using Ocelot.Configuration; namespace Ocelot.LoadBalancer.LoadBalancers { public interface ILoadBalancerFactory { - ILoadBalancer Get(ReRoute reRoute); + Task Get(ReRoute reRoute); } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs index 4799ab126..38984567b 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Ocelot.Errors; using Ocelot.Responses; using Ocelot.Values; @@ -9,20 +10,20 @@ namespace Ocelot.LoadBalancer.LoadBalancers { public class LeastConnectionLoadBalancer : ILoadBalancer { - private Func> _services; - private List _leases; - private string _serviceName; + private readonly Func>> _services; + private readonly List _leases; + private readonly string _serviceName; - public LeastConnectionLoadBalancer(Func> services, string serviceName) + public LeastConnectionLoadBalancer(Func>> services, string serviceName) { _services = services; _serviceName = serviceName; _leases = new List(); } - public Response Lease() + public async Task> Lease() { - var services = _services(); + var services = await _services.Invoke(); if (services == null) { diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs index 082f3b616..08e45d2b3 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs @@ -1,4 +1,5 @@ -using Ocelot.Configuration; +using System.Threading.Tasks; +using Ocelot.Configuration; using Ocelot.ServiceDiscovery; namespace Ocelot.LoadBalancer.LoadBalancers @@ -11,25 +12,27 @@ public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFacto _serviceProviderFactory = serviceProviderFactory; } - public ILoadBalancer Get(ReRoute reRoute) + public async Task Get(ReRoute reRoute) { var serviceConfig = new ServiceProviderConfiguraion( - reRoute.ServiceName, - reRoute.DownstreamHost, - reRoute.DownstreamPort, - reRoute.UseServiceDiscovery, - reRoute.ServiceDiscoveryProvider); + 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(serviceProvider.Get()); + return new RoundRobinLoadBalancer(await serviceProvider.Get()); case "LeastConnection": - return new LeastConnectionLoadBalancer(() => serviceProvider.Get(), reRoute.ServiceName); + return new LeastConnectionLoadBalancer(async () => await serviceProvider.Get(), reRoute.ServiceProviderConfiguraion.ServiceName); default: - return new NoLoadBalancer(serviceProvider.Get()); + return new NoLoadBalancer(await serviceProvider.Get()); } } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs index 2788656af..f654dca81 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Ocelot.Responses; using Ocelot.Values; @@ -14,7 +15,7 @@ public NoLoadBalancer(List services) _services = services; } - public Response Lease() + public async Task> Lease() { var service = _services.FirstOrDefault(); return new OkResponse(service.HostAndPort); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs index 1ffb46ceb..0bb1f8298 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Ocelot.Responses; using Ocelot.Values; @@ -14,7 +15,7 @@ public RoundRobinLoadBalancer(List services) _services = services; } - public Response Lease() + public async Task> Lease() { if (_last >= _services.Count) { diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index 99bf5167e..d2e9ee8ae 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -37,7 +37,7 @@ public async Task Invoke(HttpContext context) //set errors and return } - var hostAndPort = loadBalancer.Data.Lease(); + var hostAndPort = await loadBalancer.Data.Lease(); if(hostAndPort.IsError) { //set errors and return diff --git a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs index f1045be3b..f6280d7b4 100644 --- a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs +++ b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs @@ -1,18 +1,19 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Ocelot.Values; namespace Ocelot.ServiceDiscovery { public class ConfigurationServiceProvider : IServiceDiscoveryProvider { - private List _services; + private readonly List _services; public ConfigurationServiceProvider(List services) { _services = services; } - public List Get() + public async Task> Get() { return _services; } diff --git a/src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs b/src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs new file mode 100644 index 000000000..8d496a859 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs index af5c4ddc4..c74c90f08 100644 --- a/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs +++ b/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs @@ -2,15 +2,54 @@ 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 { - public List Get() + 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> 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()); + } + + private string GetVersionFromStrings(IEnumerable strings) { - throw new NotImplementedException(); + return strings + ?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal)) + .TrimStart(VersionPrefix); } } } diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs index 2732b5e3e..2c643d4b1 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Ocelot.Values; namespace Ocelot.ServiceDiscovery { public interface IServiceDiscoveryProvider { - List Get(); + Task> Get(); } } \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs index fe2acaa8c..6c6c3d4ca 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs @@ -1,4 +1,5 @@ using System; +using Ocelot.Configuration; namespace Ocelot.ServiceDiscovery { diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index e87014186..006221908 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Ocelot.Configuration; using Ocelot.Values; namespace Ocelot.ServiceDiscovery @@ -9,20 +10,25 @@ public IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig) { if (serviceConfig.UseServiceDiscovery) { - return GetServiceDiscoveryProvider(serviceConfig.ServiceName, serviceConfig.ServiceDiscoveryProvider); + return GetServiceDiscoveryProvider(serviceConfig.ServiceName, serviceConfig.ServiceDiscoveryProvider, serviceConfig.ServiceProviderHost, serviceConfig.ServiceProviderPort); } var services = new List() { - new Service(serviceConfig.ServiceName, new HostAndPort(serviceConfig.DownstreamHost, serviceConfig.DownstreamPort)) + 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) + private IServiceDiscoveryProvider GetServiceDiscoveryProvider(string serviceName, string serviceProviderName, string providerHostName, int providerPort) { - return new ConsulServiceDiscoveryProvider(); + var consulRegistryConfiguration = new ConsulRegistryConfiguration(providerHostName, providerPort, serviceName); + return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration); } } } \ No newline at end of file diff --git a/src/Ocelot/Values/Service.cs b/src/Ocelot/Values/Service.cs index 104fbc098..0ba12b79b 100644 --- a/src/Ocelot/Values/Service.cs +++ b/src/Ocelot/Values/Service.cs @@ -1,13 +1,29 @@ +using System.Collections.Generic; + namespace Ocelot.Values { public class Service { - public Service(string name, HostAndPort hostAndPort) + public Service(string name, + HostAndPort hostAndPort, + string id, + string version, + IEnumerable tags) { Name = name; HostAndPort = hostAndPort; + Id = id; + Version = version; + Tags = tags; } - public string Name {get; private set;} - public HostAndPort HostAndPort {get; private set;} + public string Id { get; private set; } + + public string Name { get; private set; } + + public string Version { get; private set; } + + public IEnumerable Tags { get; private set; } + + public HostAndPort HostAndPort { get; private set; } } } \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index b2134a702..06bee686d 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -29,7 +29,7 @@ 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:9500"; + var fakeConsulServiceDiscoveryUrl = "http://localhost:8500"; var downstreamServiceOneCounter = 0; var downstreamServiceTwoCounter = 0; @@ -51,7 +51,8 @@ public void should_use_service_discovery_and_load_balance_request() { ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() { - Provider = "Consul" + Provider = "Consul", + Host = "localhost" } } }; diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index 713b93f1e..d7db55c75 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -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,"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Address":null}}} \ No newline at end of file +{"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}}} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 65a612400..e1da7de07 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -151,7 +151,7 @@ public void should_use_service_discovery_for_downstream_service_host() ServiceDiscoveryProvider = new FileServiceDiscoveryProvider { Provider = "consul", - Address = "127.0.0.1" + Host = "127.0.0.1" } } })) @@ -579,7 +579,7 @@ private void GivenTheConfigIs(FileConfiguration fileConfiguration) private void WhenICreateTheConfig() { - _config = _ocelotConfigurationCreator.Create(); + _config = _ocelotConfigurationCreator.Create().Result; } private void ThenTheReRoutesAre(List expectedReRoutes) @@ -617,7 +617,7 @@ private void GivenTheLoadBalancerFactoryReturns() { _loadBalancerFactory .Setup(x => x.Get(It.IsAny())) - .Returns(_loadBalancer.Object); + .ReturnsAsync(_loadBalancer.Object); } private void TheLoadBalancerFactoryIsCalledCorrectly() diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs index 56fb64879..98e012934 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs @@ -81,7 +81,7 @@ private void GivenTheCreatorReturns(Response config) { _creator .Setup(x => x.Create()) - .Returns(config); + .ReturnsAsync(config); } private void GivenTheRepoReturns(Response config) @@ -93,7 +93,7 @@ private void GivenTheRepoReturns(Response config) private void WhenIGetTheConfig() { - _result = _ocelotConfigurationProvider.Get(); + _result = _ocelotConfigurationProvider.Get().Result; } private void TheFollowingIsReturned(Response expected) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 0d5a6d48a..a80a31680 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -84,7 +84,7 @@ private void GivenTheDownStreamRouteFinderReturns(DownstreamRoute downstreamRout _downstreamRoute = new OkResponse(downstreamRoute); _downstreamRouteFinder .Setup(x => x.FindDownstreamRoute(It.IsAny(), It.IsAny())) - .Returns(_downstreamRoute); + .ReturnsAsync(_downstreamRoute); } public void Dispose() diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index c0afca426..dc9978b30 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -159,7 +159,7 @@ private void GivenTheConfigurationIs(List reRoutesConfig) _reRoutesConfig = reRoutesConfig; _mockConfig .Setup(x => x.Get()) - .Returns(new OkResponse(new OcelotConfiguration(_reRoutesConfig))); + .ReturnsAsync(new OkResponse(new OcelotConfiguration(_reRoutesConfig))); } private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) @@ -169,7 +169,7 @@ private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) private void WhenICallTheFinder() { - _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod); + _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod).Result; } private void ThenTheFollowingIsReturned(DownstreamRoute expected) diff --git a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs index a8617b228..47b3a7d07 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; using Ocelot.Values; @@ -24,7 +25,7 @@ public void should_get_next_url() var availableServices = new List { - new Service(serviceName, hostAndPort) + new Service(serviceName, hostAndPort, string.Empty, string.Empty, new string[0]) }; this.Given(x => x.GivenAHostAndPort(hostAndPort)) @@ -41,23 +42,23 @@ public void should_serve_from_service_with_least_connections() var availableServices = new List { - new Service(serviceName, new HostAndPort("127.0.0.1", 80)), - new Service(serviceName, new HostAndPort("127.0.0.2", 80)), - new Service(serviceName, new HostAndPort("127.0.0.3", 80)) + 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(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName); - var response = _leastConnection.Lease(); + var response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[2].HostAndPort.DownstreamHost); } @@ -69,26 +70,26 @@ public void should_build_connections_per_service() var availableServices = new List { - new Service(serviceName, new HostAndPort("127.0.0.1", 80)), - new Service(serviceName, new HostAndPort("127.0.0.2", 80)), + 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(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName); - var response = _leastConnection.Lease(); + var response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); } @@ -100,33 +101,33 @@ public void should_release_connection() var availableServices = new List { - new Service(serviceName, new HostAndPort("127.0.0.1", 80)), - new Service(serviceName, new HostAndPort("127.0.0.2", 80)), + 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(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName); - var response = _leastConnection.Lease(); + var response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + 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(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); } @@ -172,7 +173,7 @@ private void ThenServiceAreEmptyErrorIsReturned() private void GivenTheLoadBalancerStarts(List services, string serviceName) { _services = services; - _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName); } private void WhenTheLoadBalancerStarts(List services, string serviceName) @@ -187,7 +188,7 @@ private void GivenAHostAndPort(HostAndPort hostAndPort) private void WhenIGetTheNextHostAndPort() { - _result = _leastConnection.Lease(); + _result = _leastConnection.Lease().Result; } private void ThenTheNextHostAndPortIsReturned() diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index e8e0210b0..d030eb999 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -99,7 +99,7 @@ private void GivenAReRoute(ReRoute reRoute) private void WhenIGetTheLoadBalancer() { - _result = _factory.Get(_reRoute); + _result = _factory.Get(_reRoute).Result; } private void ThenTheLoadBalancerIsReturned() diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index 31a7bd37a..471e4b70c 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; using Ocelot.Values; @@ -108,7 +109,7 @@ private void ThenItIsReturned() class FakeLoadBalancer : ILoadBalancer { - public Response Lease() + public Task> Lease() { throw new NotImplementedException(); } @@ -121,7 +122,7 @@ public Response Release(HostAndPort hostAndPort) class FakeRoundRobinLoadBalancer : ILoadBalancer { - public Response Lease() + public Task> Lease() { throw new NotImplementedException(); } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index 93c638849..ab6a59a82 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -82,7 +82,7 @@ private void GivenTheLoadBalancerReturns() _hostAndPort = new HostAndPort("127.0.0.1", 80); _loadBalancer .Setup(x => x.Lease()) - .Returns(new OkResponse(_hostAndPort)); + .ReturnsAsync(new OkResponse(_hostAndPort)); } private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) diff --git a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs index a2fd2be84..ac89a6d02 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs @@ -21,7 +21,7 @@ public void should_return_host_and_port() var services = new List { - new Service("product", hostAndPort) + new Service("product", hostAndPort, string.Empty, string.Empty, new string[0]) }; this.Given(x => x.GivenServices(services)) .When(x => x.WhenIGetTheNextHostAndPort()) @@ -37,7 +37,7 @@ private void GivenServices(List services) private void WhenIGetTheNextHostAndPort() { _loadBalancer = new NoLoadBalancer(_services); - _result = _loadBalancer.Lease(); + _result = _loadBalancer.Lease().Result; } private void ThenTheHostAndPortIs(HostAndPort expected) diff --git a/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs index cb934af89..f2ef53674 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs @@ -19,9 +19,9 @@ public RoundRobinTests() { _services = new List { - new Service("product", new HostAndPort("127.0.0.1", 5000)), - new Service("product", new HostAndPort("127.0.0.1", 5001)), - new Service("product", new HostAndPort("127.0.0.1", 5001)) + 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); @@ -46,18 +46,18 @@ public void should_go_back_to_first_address_after_finished_last() while (stopWatch.ElapsedMilliseconds < 1000) { - var address = _roundRobin.Lease(); + var address = _roundRobin.Lease().Result; address.Data.ShouldBe(_services[0].HostAndPort); - address = _roundRobin.Lease(); + address = _roundRobin.Lease().Result; address.Data.ShouldBe(_services[1].HostAndPort); - address = _roundRobin.Lease(); + address = _roundRobin.Lease().Result; address.Data.ShouldBe(_services[2].HostAndPort); } } private void GivenIGetTheNextAddress() { - _hostAndPort = _roundRobin.Lease(); + _hostAndPort = _roundRobin.Lease().Result; } private void ThenTheNextAddressIndexIs(int index) diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs index 182dd5148..f1e732e76 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs @@ -21,7 +21,7 @@ public void should_return_services() var services = new List { - new Service("product", hostAndPort) + new Service("product", hostAndPort, string.Empty, string.Empty, new string[0]) }; this.Given(x => x.GivenServices(services)) @@ -38,7 +38,7 @@ private void GivenServices(List services) private void WhenIGetTheService() { _serviceProvider = new ConfigurationServiceProvider(_expected); - _result = _serviceProvider.Get(); + _result = _serviceProvider.Get().Result; } private void ThenTheFollowingIsReturned(List services) diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 97fb265ab..7dae5e472 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -1,3 +1,4 @@ +using Ocelot.Configuration; using Ocelot.ServiceDiscovery; using Shouldly; using TestStack.BDDfy; @@ -19,7 +20,7 @@ public ServiceProviderFactoryTests() [Fact] public void should_return_no_service_provider() { - var serviceConfig = new ServiceProviderConfiguraion("product", "127.0.0.1", 80, false, "Does not matter"); + 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()) @@ -30,7 +31,7 @@ public void should_return_no_service_provider() [Fact] public void should_return_consul_service_provider() { - var serviceConfig = new ServiceProviderConfiguraion("product", string.Empty, 0, true, "Consul"); + var serviceConfig = new ServiceProviderConfiguraion("product", string.Empty, 0, true, "Consul", string.Empty, 0); this.Given(x => x.GivenTheReRoute(serviceConfig)) .When(x => x.WhenIGetTheServiceProvider()) diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs index 61f7c975e..874253293 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs @@ -50,13 +50,13 @@ private void WhenILookupTheService(string name) private void GivenAServiceIsRegistered(string name, string address, int port) { - _service = new Service(name, new HostAndPort(address, 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)); + _service = new Service(name, new HostAndPort(address, port), string.Empty, string.Empty, new string[0]); } private void WhenIRegisterTheService() From fb0f101732277e31134ce06a2d9d4e7ca04ecc1c Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sun, 5 Feb 2017 21:08:16 +0000 Subject: [PATCH 23/29] wip fake consul provider --- .../LeastConnectionLoadBalancer.cs | 37 ++++--- .../LoadBalancers/LoadBalancerHouse.cs | 12 ++- .../Middleware/OcelotMiddlewareExtensions.cs | 16 +++ .../ServiceDiscoveryTests.cs | 98 +++++++++++++++---- .../TestConfiguration.cs | 2 +- test/Ocelot.AcceptanceTests/project.json | 3 +- .../LoadBalancer/LeastConnectionTests.cs | 45 +++++++++ 7 files changed, 174 insertions(+), 39 deletions(-) diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs index 38984567b..bfb4817bd 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs @@ -13,6 +13,7 @@ public class LeastConnectionLoadBalancer : ILoadBalancer private readonly Func>> _services; private readonly List _leases; private readonly string _serviceName; + private static readonly object _syncLock = new object(); public LeastConnectionLoadBalancer(Func>> services, string serviceName) { @@ -35,32 +36,38 @@ public async Task> Lease() return new ErrorResponse(new List() { new ServicesAreEmptyError($"services were empty for {_serviceName}") }); } - //todo - maybe this should be moved somewhere else...? Maybe on a repeater on seperate thread? loop every second and update or something? - UpdateServices(services); + 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(); + var leaseWithLeastConnections = GetLeaseWithLeastConnections(); - _leases.Remove(leaseWithLeastConnections); + _leases.Remove(leaseWithLeastConnections); - leaseWithLeastConnections = AddConnection(leaseWithLeastConnections); + leaseWithLeastConnections = AddConnection(leaseWithLeastConnections); - _leases.Add(leaseWithLeastConnections); - - return new OkResponse(new HostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort)); + _leases.Add(leaseWithLeastConnections); + + return new OkResponse(new HostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort)); + } } public Response Release(HostAndPort hostAndPort) { - var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost - && l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort); - - if (matchingLease != null) + lock(_syncLock) { - var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1); + var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost + && l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort); - _leases.Remove(matchingLease); + if (matchingLease != null) + { + var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1); + + _leases.Remove(matchingLease); - _leases.Add(replacementLease); + _leases.Add(replacementLease); + } } return new OkResponse(); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs index 12c040c01..63ac32431 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -32,8 +32,16 @@ public Response Get(string key) public Response Add(string key, ILoadBalancer loadBalancer) { - _loadBalancers[key] = loadBalancer; - return new OkResponse(); + try + { + _loadBalancers.Add(key, loadBalancer); + return new OkResponse(); + } + catch (System.Exception exception) + { + Console.WriteLine(exception.StackTrace); + throw; + } } } } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index b0cd3f7ee..352aa5012 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -18,6 +18,7 @@ namespace Ocelot.Middleware using System.Threading.Tasks; using Authorisation.Middleware; using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.Provider; using Ocelot.LoadBalancer.Middleware; public static class OcelotMiddlewareExtensions @@ -29,6 +30,7 @@ public static class OcelotMiddlewareExtensions /// public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder) { + CreateConfiguration(builder); builder.UseOcelot(new OcelotMiddlewareConfiguration()); return builder; } @@ -41,6 +43,8 @@ public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder) /// public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) { + CreateConfiguration(builder); + // This is registered to catch any global exceptions that are not handled builder.UseExceptionHandlerMiddleware(); @@ -118,6 +122,18 @@ public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder, Oc 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, Task> middleware) { if (middleware != null) diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index 06bee686d..d8e5149db 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Net; +using Consul; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -14,13 +15,19 @@ namespace Ocelot.AcceptanceTests { public class ServiceDiscoveryTests : IDisposable { - private IWebHost _builder; + private IWebHost _builderOne; + private IWebHost _builderTwo; private IWebHost _fakeConsulBuilder; private readonly Steps _steps; + private List _serviceEntries; + private int _counterOne; + private int _counterTwo; + private static readonly object _syncLock = new object(); public ServiceDiscoveryTests() { _steps = new Steps(); + _serviceEntries = new List(); } [Fact] @@ -32,6 +39,28 @@ public void should_use_service_discovery_and_load_balance_request() var fakeConsulServiceDiscoveryUrl = "http://localhost:8500"; var downstreamServiceOneCounter = 0; var downstreamServiceTwoCounter = 0; + 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 { @@ -52,38 +81,42 @@ public void should_use_service_discovery_and_load_balance_request() ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() { Provider = "Consul", - Host = "localhost" + Host = "localhost", + Port = 8500 } } }; - this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, 200, downstreamServiceOneCounter)) - .And(x => x.GivenThereIsAServiceRunningOn(downstreamServiceTwoUrl, 200, downstreamServiceTwoCounter)) + this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) + .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200)) .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceName, downstreamServiceOneUrl, downstreamServiceTwoUrl)) + .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, downstreamServiceOneCounter, downstreamServiceTwoCounter)) - .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(downstreamServiceOneCounter,downstreamServiceTwoCounter)) + .Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50)) + .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes()) .BDDfy(); } - private void ThenBothServicesCalledRealisticAmountOfTimes(int counterOne, int counterTwo) + private void ThenBothServicesCalledRealisticAmountOfTimes() { - counterOne.ShouldBeGreaterThan(10); - counterTwo.ShouldBeGreaterThan(10); + _counterOne.ShouldBeGreaterThan(25); + _counterTwo.ShouldBeGreaterThan(25); } - private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected, int counterOne, int counterTwo) + private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected) { - var total = counterOne + counterTwo; + var total = _counterOne + _counterTwo; total.ShouldBe(expected); } - private void GivenTheServicesAreRegisteredWithConsul(string serviceName, params string[] urls) + private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries) { - //register these services with fake consul + foreach(var serviceEntry in serviceEntries) + { + _serviceEntries.Add(serviceEntry); + } } private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) @@ -98,7 +131,10 @@ private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) { app.Run(async context => { - //do consul shit + if(context.Request.Path.Value == "/v1/health/service/product") + { + await context.Response.WriteJsonAsync(_serviceEntries); + } }); }) .Build(); @@ -106,9 +142,30 @@ private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) _fakeConsulBuilder.Start(); } - private void GivenThereIsAServiceRunningOn(string url, int statusCode, int counter) + 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 => + { + _counterOne++; + context.Response.StatusCode = statusCode; + }); + }) + .Build(); + + _builderOne.Start(); + } + + private void GivenProductServiceTwoIsRunning(string url, int statusCode) { - _builder = new WebHostBuilder() + _builderTwo = new WebHostBuilder() .UseUrls(url) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) @@ -118,18 +175,19 @@ private void GivenThereIsAServiceRunningOn(string url, int statusCode, int count { app.Run(async context => { - counter++; + _counterTwo++; context.Response.StatusCode = statusCode; }); }) .Build(); - _builder.Start(); + _builderTwo.Start(); } public void Dispose() { - _builder?.Dispose(); + _builderOne?.Dispose(); + _builderTwo?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index ce802efb6..6784391c3 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -28,7 +28,7 @@ private static string FormatConfigurationPath(string oSDescription, string osArc { var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); - var configPath = $"./bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; + var configPath = $"./test/Ocelot.AcceptanceTests/bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; return configPath; } diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 2e5f9ee89..f1aa378b3 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -32,7 +32,8 @@ "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", "Microsoft.NETCore.App": "1.1.0", "Shouldly": "2.8.2", - "TestStack.BDDfy": "4.3.2" + "TestStack.BDDfy": "4.3.2", + "Consul": "0.7.2.1" }, "runtimes": { "win10-x64": {}, diff --git a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs index 47b3a7d07..3896b68ea 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Threading.Tasks; using Ocelot.LoadBalancer.LoadBalancers; @@ -15,6 +16,50 @@ public class LeastConnectionTests private Response _result; private LeastConnectionLoadBalancer _leastConnection; private List _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 + { + 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]; + try + { + for(var i = 0; i < tasks.Length; i++) + { + tasks[i] = LeaseDelayAndRelease(); + } + + Task.WaitAll(tasks); + } + catch (System.Exception exception) + { + Console.WriteLine(exception.StackTrace); + throw; + } + } + + private async Task LeaseDelayAndRelease() + { + var hostAndPort = await _leastConnection.Lease(); + await Task.Delay(_random.Next(1, 100)); + var response = _leastConnection.Release(hostAndPort.Data); + } [Fact] public void should_get_next_url() From 932bcb73d463ec1b474fa7361e4d43a248dde788 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 5 Feb 2017 21:21:02 +0000 Subject: [PATCH 24/29] wip: removed some debug statements and all tests passing on my PC...feel there is something wrong with the service discovery test around task execution not completing --- .../LoadBalancers/LoadBalancerHouse.cs | 12 +++++------- .../ServiceDiscoveryTests.cs | 7 +++---- .../Ocelot.AcceptanceTests/TestConfiguration.cs | 2 +- .../LoadBalancer/LeastConnectionTests.cs | 17 +++++------------ 4 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs index 63ac32431..cc6ea73b0 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -32,16 +32,14 @@ public Response Get(string key) public Response Add(string key, ILoadBalancer loadBalancer) { - try + if (!_loadBalancers.ContainsKey(key)) { _loadBalancers.Add(key, loadBalancer); - return new OkResponse(); - } - catch (System.Exception exception) - { - Console.WriteLine(exception.StackTrace); - throw; } + + _loadBalancers.Remove(key); + _loadBalancers.Add(key, loadBalancer); + return new OkResponse(); } } } diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index d8e5149db..2d9d55afe 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -19,10 +19,9 @@ public class ServiceDiscoveryTests : IDisposable private IWebHost _builderTwo; private IWebHost _fakeConsulBuilder; private readonly Steps _steps; - private List _serviceEntries; + private readonly List _serviceEntries; private int _counterOne; private int _counterTwo; - private static readonly object _syncLock = new object(); public ServiceDiscoveryTests() { @@ -101,8 +100,8 @@ public void should_use_service_discovery_and_load_balance_request() private void ThenBothServicesCalledRealisticAmountOfTimes() { - _counterOne.ShouldBeGreaterThan(25); - _counterTwo.ShouldBeGreaterThan(25); + _counterOne.ShouldBe(25); + _counterTwo.ShouldBe(25); } private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected) diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index 6784391c3..ce802efb6 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -28,7 +28,7 @@ private static string FormatConfigurationPath(string oSDescription, string osArc { var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); - var configPath = $"./test/Ocelot.AcceptanceTests/bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; + var configPath = $"./bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; return configPath; } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs index 3896b68ea..f5ea4738e 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs @@ -38,20 +38,13 @@ public void should_be_able_to_lease_and_release_concurrently() _leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName); var tasks = new Task[100]; - try + + for(var i = 0; i < tasks.Length; i++) { - for(var i = 0; i < tasks.Length; i++) - { - tasks[i] = LeaseDelayAndRelease(); - } - - Task.WaitAll(tasks); - } - catch (System.Exception exception) - { - Console.WriteLine(exception.StackTrace); - throw; + tasks[i] = LeaseDelayAndRelease(); } + + Task.WaitAll(tasks); } private async Task LeaseDelayAndRelease() From d91242ac2c2b80d4ab39d4666657d59633dea224 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 5 Feb 2017 21:35:50 +0000 Subject: [PATCH 25/29] wip: modifications to service discovery acceptance test to see if it will work on mac --- README.md | 2 +- .../ServiceDiscoveryTests.cs | 21 +++++++++++++++---- test/Ocelot.AcceptanceTests/Steps.cs | 11 +++++++++- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 249a99a31..9d93546d0 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ touch either via gitter or create an issue. ## How to install Ocelot is designed to work with ASP.NET core only and is currently -built to netcoreapp1.4 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you. +built to netcoreapp1.1 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you. Install Ocelot and it's dependecies using nuget. At the moment all we have is the pre version. Once we have something working in diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index 2d9d55afe..1bc6723ad 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -22,6 +22,7 @@ public class ServiceDiscoveryTests : IDisposable private readonly List _serviceEntries; private int _counterOne; private int _counterTwo; + private static readonly object _syncLock = new object(); public ServiceDiscoveryTests() { @@ -152,9 +153,15 @@ private void GivenProductServiceOneIsRunning(string url, int statusCode) .Configure(app => { app.Run(async context => - { - _counterOne++; + { + var response = string.Empty; + lock (_syncLock) + { + _counterOne++; + response = _counterOne.ToString(); + } context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(response); }); }) .Build(); @@ -173,9 +180,15 @@ private void GivenProductServiceTwoIsRunning(string url, int statusCode) .Configure(app => { app.Run(async context => - { - _counterTwo++; + { + var response = string.Empty; + lock (_syncLock) + { + _counterTwo++; + response = _counterTwo.ToString(); + } context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(response); }); }) .Build(); diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 666c32562..5d0159940 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -160,12 +160,21 @@ public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) for (int i = 0; i < times; i++) { - tasks[i] = _ocelotClient.GetAsync(url); + var urlCopy = url; + tasks[i] = GetForServiceDiscoveryTest(urlCopy); } Task.WaitAll(tasks); } + private async Task GetForServiceDiscoveryTest(string url) + { + var response = await _ocelotClient.GetAsync(url); + var content = await response.Content.ReadAsStringAsync(); + var count = int.Parse(content); + count.ShouldBeGreaterThan(0); + } + public void WhenIGetUrlOnTheApiGateway(string url, string requestId) { _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId); From c3e60ac08a26f7374f517489c9bdb161616b5596 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sun, 5 Feb 2017 22:00:23 +0000 Subject: [PATCH 26/29] wip: added some sleep time into service discovery test as I think Im overloading the test server, sometimes it just returns 404 when Ocelot makes a request to it --- .../ServiceDiscoveryTests.cs | 42 ++++++++++++------- test/Ocelot.AcceptanceTests/Steps.cs | 10 ++++- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index 1bc6723ad..ef7e0dd7f 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -37,8 +37,6 @@ public void should_use_service_discovery_and_load_balance_request() var downstreamServiceOneUrl = "http://localhost:50879"; var downstreamServiceTwoUrl = "http://localhost:50880"; var fakeConsulServiceDiscoveryUrl = "http://localhost:8500"; - var downstreamServiceOneCounter = 0; - var downstreamServiceTwoCounter = 0; var serviceEntryOne = new ServiceEntry() { Service = new AgentService() @@ -154,14 +152,21 @@ private void GivenProductServiceOneIsRunning(string url, int statusCode) { app.Run(async context => { - var response = string.Empty; - lock (_syncLock) + try { - _counterOne++; - response = _counterOne.ToString(); + 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); } - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(response); }); }) .Build(); @@ -181,14 +186,23 @@ private void GivenProductServiceTwoIsRunning(string url, int statusCode) { app.Run(async context => { - var response = string.Empty; - lock (_syncLock) + try + { + var response = string.Empty; + lock (_syncLock) + { + _counterTwo++; + response = _counterTwo.ToString(); + } + + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(response); + } + catch (System.Exception exception) { - _counterTwo++; - response = _counterTwo.ToString(); + await context.Response.WriteAsync(exception.StackTrace); } - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(response); + }); }) .Build(); diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 5d0159940..9b5faa043 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Threading; using System.Threading.Tasks; using CacheManager.Core; using Microsoft.AspNetCore.Hosting; @@ -30,6 +31,12 @@ public class Steps : IDisposable private BearerToken _token; public HttpClient OcelotClient => _ocelotClient; public string RequestIdKey = "OcRequestId"; + private Random _random; + + public Steps() + { + _random = new Random(); + } public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) { @@ -162,6 +169,7 @@ public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) { var urlCopy = url; tasks[i] = GetForServiceDiscoveryTest(urlCopy); + Thread.Sleep(_random.Next(40,60)); } Task.WaitAll(tasks); @@ -171,7 +179,7 @@ private async Task GetForServiceDiscoveryTest(string url) { var response = await _ocelotClient.GetAsync(url); var content = await response.Content.ReadAsStringAsync(); - var count = int.Parse(content); + int count = int.Parse(content); count.ShouldBeGreaterThan(0); } From a4495b8fa991f2e9e2e4335ef1cb1735030908e3 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 6 Feb 2017 20:22:09 +0000 Subject: [PATCH 27/29] tests for error handling on load balancing middleware --- .../Middleware/LoadBalancingMiddleware.cs | 18 +++-- .../LoadBalancerMiddlewareTests.cs | 73 +++++++++++++++++++ 2 files changed, 83 insertions(+), 8 deletions(-) diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index d2e9ee8ae..7d09ea3a9 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -31,16 +31,18 @@ public async Task Invoke(HttpContext context) { _logger.LogDebug("started calling load balancing middleware"); - var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey); - if(loadBalancer.IsError) + var getLoadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey); + if(getLoadBalancer.IsError) { - //set errors and return + SetPipelineError(getLoadBalancer.Errors); + return; } - var hostAndPort = await loadBalancer.Data.Lease(); + var hostAndPort = await getLoadBalancer.Data.Lease(); if(hostAndPort.IsError) - { - //set errors and return + { + SetPipelineError(hostAndPort.Errors); + return; } SetHostAndPortForThisRequest(hostAndPort.Data); @@ -51,11 +53,11 @@ public async Task Invoke(HttpContext context) { await _next.Invoke(context); - loadBalancer.Data.Release(hostAndPort.Data); + getLoadBalancer.Data.Release(hostAndPort.Data); } catch (Exception) { - loadBalancer.Data.Release(hostAndPort.Data); + getLoadBalancer.Data.Release(hostAndPort.Data); _logger.LogDebug("error calling next middleware, exception will be thrown to global handler"); throw; } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index ab6a59a82..5d750f837 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -7,6 +7,7 @@ using Moq; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; +using Ocelot.Errors; using Ocelot.Infrastructure.RequestData; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.Middleware; @@ -31,6 +32,8 @@ public class LoadBalancerMiddlewareTests private OkResponse _request; private OkResponse _downstreamUrl; private OkResponse _downstreamRoute; + private ErrorResponse _getLoadBalancerHouseError; + private ErrorResponse _getHostAndPortError; public LoadBalancerMiddlewareTests() { @@ -77,6 +80,45 @@ public void should_call_scoped_data_repository_correctly() .BDDfy(); } + [Fact] + public void should_set_pipeline_error_if_cannot_get_load_balancer() + { + var downstreamRoute = new DownstreamRoute(new List(), + 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(), + 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(new List() { 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); @@ -100,12 +142,43 @@ private void GivenTheLoadBalancerHouseReturns() .Returns(new OkResponse(_loadBalancer.Object)); } + + private void GivenTheLoadBalancerHouseReturnsAnError() + { + _getLoadBalancerHouseError = new ErrorResponse(new List() + { + new UnableToFindLoadBalancerError($"unabe to find load balancer for bah") + }); + + _loadBalancerHouse + .Setup(x => x.Get(It.IsAny())) + .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 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; From 0a66051b92ee4cd3f032fcbe59d0fbb56d33f3db Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 6 Feb 2017 21:47:08 +0000 Subject: [PATCH 28/29] removed some code we dont need as not expecting any errors so should just throw an exception to the global handler --- .../Middleware/ExceptionHandlerMiddleware.cs | 4 ++-- .../LoadBalancer/LoadBalancers/ILoadBalancer.cs | 2 +- .../LeastConnectionLoadBalancer.cs | 4 +--- .../LoadBalancers/NoLoadBalancer.cs | 5 ++--- .../LoadBalancers/RoundRobinLoadBalancer.cs | 5 ++--- .../Middleware/LoadBalancingMiddleware.cs | 13 +++++++------ src/Ocelot/Responder/HttpContextResponder.cs | 6 ++---- src/Ocelot/Responder/IHttpResponder.cs | 5 ++--- .../Responder/Middleware/ResponderMiddleware.cs | 17 +++++------------ .../ConfigurationServiceProvider.cs | 2 +- .../LoadBalancer/LeastConnectionTests.cs | 2 +- .../LoadBalancer/LoadBalancerHouseTests.cs | 4 ++-- .../LoadBalancer/LoadBalancerMiddlewareTests.cs | 9 +++++++++ .../Responder/ResponderMiddlewareTests.cs | 8 -------- 14 files changed, 37 insertions(+), 49 deletions(-) diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index fc3ab2002..6e6b511e0 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -41,13 +41,13 @@ public async Task Invoke(HttpContext context) var message = CreateMessage(context, e); _logger.LogError(message, e); - await SetInternalServerErrorOnResponse(context); + SetInternalServerErrorOnResponse(context); } _logger.LogDebug("ocelot pipeline finished"); } - private async Task SetInternalServerErrorOnResponse(HttpContext context) + private void SetInternalServerErrorOnResponse(HttpContext context) { context.Response.OnStarting(x => { diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs index aa2a8f02f..73d25d487 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs @@ -8,6 +8,6 @@ namespace Ocelot.LoadBalancer.LoadBalancers public interface ILoadBalancer { Task> Lease(); - Response Release(HostAndPort hostAndPort); + void Release(HostAndPort hostAndPort); } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs index bfb4817bd..cd56ef914 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs @@ -53,7 +53,7 @@ public async Task> Lease() } } - public Response Release(HostAndPort hostAndPort) + public void Release(HostAndPort hostAndPort) { lock(_syncLock) { @@ -69,8 +69,6 @@ public Response Release(HostAndPort hostAndPort) _leases.Add(replacementLease); } } - - return new OkResponse(); } private Lease AddConnection(Lease lease) diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs index f654dca81..bf66950b8 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs @@ -17,13 +17,12 @@ public NoLoadBalancer(List services) public async Task> Lease() { - var service = _services.FirstOrDefault(); + var service = await Task.FromResult(_services.FirstOrDefault()); return new OkResponse(service.HostAndPort); } - public Response Release(HostAndPort hostAndPort) + public void Release(HostAndPort hostAndPort) { - return new OkResponse(); } } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs index 0bb1f8298..37efe22f7 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs @@ -22,14 +22,13 @@ public async Task> Lease() _last = 0; } - var next = _services[_last]; + var next = await Task.FromResult(_services[_last]); _last++; return new OkResponse(next.HostAndPort); } - public Response Release(HostAndPort hostAndPort) + public void Release(HostAndPort hostAndPort) { - return new OkResponse(); } } } diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index 7d09ea3a9..ce37f8288 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -31,14 +31,14 @@ public async Task Invoke(HttpContext context) { _logger.LogDebug("started calling load balancing middleware"); - var getLoadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey); - if(getLoadBalancer.IsError) + var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey); + if(loadBalancer.IsError) { - SetPipelineError(getLoadBalancer.Errors); + SetPipelineError(loadBalancer.Errors); return; } - var hostAndPort = await getLoadBalancer.Data.Lease(); + var hostAndPort = await loadBalancer.Data.Lease(); if(hostAndPort.IsError) { SetPipelineError(hostAndPort.Errors); @@ -53,11 +53,12 @@ public async Task Invoke(HttpContext context) { await _next.Invoke(context); - getLoadBalancer.Data.Release(hostAndPort.Data); + loadBalancer.Data.Release(hostAndPort.Data); } catch (Exception) { - getLoadBalancer.Data.Release(hostAndPort.Data); + loadBalancer.Data.Release(hostAndPort.Data); + _logger.LogDebug("error calling next middleware, exception will be thrown to global handler"); throw; } diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 8b61e13bc..40b60c309 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -24,7 +24,7 @@ public HttpContextResponder(IRemoveOutputHeaders removeOutputHeaders) _removeOutputHeaders = removeOutputHeaders; } - public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response) + public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response) { _removeOutputHeaders.Remove(response.Headers); @@ -56,7 +56,6 @@ public async Task SetResponseOnHttpContext(HttpContext context, HttpRe { await stream.CopyToAsync(context.Response.Body); } - return new OkResponse(); } private static void AddHeaderIfDoesntExist(HttpContext context, KeyValuePair> httpResponseHeader) @@ -67,14 +66,13 @@ private static void AddHeaderIfDoesntExist(HttpContext context, KeyValuePair SetErrorResponseOnContext(HttpContext context, int statusCode) + public void SetErrorResponseOnContext(HttpContext context, int statusCode) { context.Response.OnStarting(x => { context.Response.StatusCode = statusCode; return Task.CompletedTask; }, context); - return new OkResponse(); } } } \ No newline at end of file diff --git a/src/Ocelot/Responder/IHttpResponder.cs b/src/Ocelot/Responder/IHttpResponder.cs index 5292f4df9..f885c6731 100644 --- a/src/Ocelot/Responder/IHttpResponder.cs +++ b/src/Ocelot/Responder/IHttpResponder.cs @@ -1,13 +1,12 @@ using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Ocelot.Responses; namespace Ocelot.Responder { public interface IHttpResponder { - Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response); - Task SetErrorResponseOnContext(HttpContext context, int statusCode); + Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response); + void SetErrorResponseOnContext(HttpContext context, int statusCode); } } diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs index 06da92dc0..6bce4ac69 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs @@ -46,34 +46,27 @@ public async Task Invoke(HttpContext context) _logger.LogDebug("received errors setting error response"); - await SetErrorResponse(context, errors); + SetErrorResponse(context, errors); } else { _logger.LogDebug("no pipeline error, setting response"); - var setResponse = await _responder.SetResponseOnHttpContext(context, HttpResponseMessage); - - if (setResponse.IsError) - { - _logger.LogDebug("error setting response, returning error to client"); - - await SetErrorResponse(context, setResponse.Errors); - } + await _responder.SetResponseOnHttpContext(context, HttpResponseMessage); } } - private async Task SetErrorResponse(HttpContext context, List errors) + private void SetErrorResponse(HttpContext context, List errors) { var statusCode = _codeMapper.Map(errors); if (!statusCode.IsError) { - await _responder.SetErrorResponseOnContext(context, statusCode.Data); + _responder.SetErrorResponseOnContext(context, statusCode.Data); } else { - await _responder.SetErrorResponseOnContext(context, 500); + _responder.SetErrorResponseOnContext(context, 500); } } } diff --git a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs index f6280d7b4..98e7b0f69 100644 --- a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs +++ b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs @@ -15,7 +15,7 @@ public ConfigurationServiceProvider(List services) public async Task> Get() { - return _services; + return await Task.FromResult(_services); } } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs index f5ea4738e..07002ce36 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs @@ -51,7 +51,7 @@ private async Task LeaseDelayAndRelease() { var hostAndPort = await _leastConnection.Lease(); await Task.Delay(_random.Next(1, 100)); - var response = _leastConnection.Release(hostAndPort.Data); + _leastConnection.Release(hostAndPort.Data); } [Fact] diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index 471e4b70c..ac24b4906 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -114,7 +114,7 @@ public Task> Lease() throw new NotImplementedException(); } - public Response Release(HostAndPort hostAndPort) + public void Release(HostAndPort hostAndPort) { throw new NotImplementedException(); } @@ -127,7 +127,7 @@ public Task> Lease() throw new NotImplementedException(); } - public Response Release(HostAndPort hostAndPort) + public void Release(HostAndPort hostAndPort) { throw new NotImplementedException(); } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index 5d750f837..5a9eec872 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -170,6 +170,15 @@ private void ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline() .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>()), Times.Once); + } + private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline() { _scopedRepository diff --git a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs index b643028ed..09a5c22c7 100644 --- a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs @@ -62,19 +62,11 @@ public void should_not_return_any_errors() { this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage())) .And(x => x.GivenThereAreNoPipelineErrors()) - .And(x => x.GivenTheResponderReturns()) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenThereAreNoErrors()) .BDDfy(); } - private void GivenTheResponderReturns() - { - _responder - .Setup(x => x.SetResponseOnHttpContext(It.IsAny(), It.IsAny())) - .ReturnsAsync(new OkResponse()); - } - private void GivenThereAreNoPipelineErrors() { _scopedRepository From 201b470cb216c7556aca9924d3e6e1314ecfb04c Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 6 Feb 2017 22:03:48 +0000 Subject: [PATCH 29/29] updated readme for service discovery --- README.md | 39 +++++++++++++++++++++++++++++++++++ configuration-explanation.txt | 14 ++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d93546d0..9d46282ff 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,44 @@ This means that when Ocelot tries to match the incoming upstream url with an ups evaluation will be case sensitive. This setting defaults to false so only set it if you want the ReRoute to be case sensitive is my advice! + +## Service Discovery + +Ocelot allows you to specify a service discovery provider and will use this to find the host and port +for the downstream service Ocelot is forwarding a request to. At the moment this is only supported in the +GlobalConfiguration section which means the same service discovery provider will be used for all ReRoutes +you specify a ServiceName for at ReRoute level. + +In the future we can add a feature that allows ReRoute specfic configuration. + +At the moment the only supported service discovery provider is Consul. The following is required in the +GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default +will be used. + + "ServiceDiscoveryProvider": + { + "Provider":"Consul", + "Host":"localhost", + "Port":8500 + } + +In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the +ServiceName and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin +and LeastConnection algorithm you can use. If no load balancer is specified Ocelot will not load balance requests. + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "UpstreamTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Put", + "ServiceName": "product" + "LoadBalancer": "LeastConnection" + } + +When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balancer +requests across any available services. + + ## Authentication Ocelot currently supports the use of bearer tokens with Identity Server (more providers to @@ -389,3 +427,4 @@ that isnt available is annoying. Let alone it be null. You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1) + diff --git a/configuration-explanation.txt b/configuration-explanation.txt index ad0204699..b6db84a2f 100644 --- a/configuration-explanation.txt +++ b/configuration-explanation.txt @@ -80,12 +80,24 @@ # the caching a lot. "FileCacheOptions": { "TtlSeconds": 15 }, # The value of this is used when matching the upstream template to an upstream url. - "ReRouteIsCaseSensitive": false + "ReRouteIsCaseSensitive": false, + # Tells Ocelot the name of the service it is looking when making requests to service discovery + # for hosts and ports + "ServiceName": "product" + # Tells Ocelot which load balancer to use when making downstream requests. + "LoadBalancer": "RoundRobin" }, # This section is meant to be for global configuration settings "GlobalConfiguration": { # If this is set it will override any route specific request id keys, behaves the same # otherwise "RequestIdKey": "OcRequestId", + # If set Ocelot will try and use service discovery to locate downstream hosts and ports + "ServiceDiscoveryProvider": + { + "Provider":"Consul", + "Host":"localhost", + "Port":8500 + } } } \ No newline at end of file