From 6bf2d4677cfa2ccd44f0b72ea4df9de11b06204f Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 22 Jan 2017 20:22:04 +0000 Subject: [PATCH 01/42] 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 7200d09c..73d3bf7e 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 984f851c..f28abefe 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 00000000..82689b62 --- /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/42] 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 00000000..8866f1ae --- /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/42] 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 5ebd0660..0165beb0 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 dd5f1043..249a99a3 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 69ddc117..83110722 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 898be89f..ad020469 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 3f5ef40b..1e06a440 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 @@ namespace Ocelot.Configuration.Builder private string _serviceDiscoveryAddress; private string _downstreamScheme; private string _downstreamHost; + private int _dsPort; public ReRouteBuilder() { @@ -72,9 +74,9 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithDownstreamTemplate(string input) + public ReRouteBuilder WithDownstreamPathTemplate(string input) { - _downstreamTemplate = input; + _downstreamPathTemplate = input; return this; } @@ -184,11 +186,17 @@ namespace Ocelot.Configuration.Builder 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 c71aba23..8884f0d9 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -8,6 +8,7 @@ using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; using Ocelot.Responses; using Ocelot.Utilities; +using Ocelot.Values; namespace Ocelot.Configuration.Creator { @@ -96,7 +97,7 @@ namespace Ocelot.Configuration.Creator && !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 @@ namespace Ocelot.Configuration.Creator 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 3afa03ce..a653224a 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -14,7 +14,7 @@ namespace Ocelot.Configuration.File 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 @@ namespace Ocelot.Configuration.File 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 8778e5f7..960374cc 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 @@ namespace Ocelot.Configuration 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 @@ namespace Ocelot.Configuration 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 00000000..e350753c --- /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 00000000..a3dfa309 --- /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 b836b1eb..00000000 --- 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 8d9dba92..00000000 --- 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 1901fc88..00000000 --- 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 3a675d41..412613eb 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs @@ -26,7 +26,7 @@ namespace Ocelot.Configuration.Validator return new OkResponse(result); } - result = CheckForReRoutesContainingDownstreamScheme(configuration); + result = CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(configuration); if (result.IsError) { @@ -70,25 +70,25 @@ namespace Ocelot.Configuration.Validator 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 @@ namespace Ocelot.Configuration.Validator .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 04839abb..9f40b009 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -18,6 +18,7 @@ using Ocelot.Configuration.Repository; 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 @@ namespace Ocelot.DependencyInjection services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -69,7 +71,7 @@ namespace Ocelot.DependencyInjection 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 a74b6316..f445b46b 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -44,7 +44,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware 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 00000000..8978f665 --- /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 00000000..fbc1a5f5 --- /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 00000000..e52d3488 --- /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 00000000..18683e62 --- /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 8d0af0bd..8144b42b 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 @@ namespace Ocelot.DownstreamUrlCreator.Middleware { _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 00000000..2124ce3b --- /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 9c19f2f9..9e925631 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 164c42ef..72d5d4b6 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 f0a336f0..5de770cd 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -4,7 +4,7 @@ { UnauthenticatedError, UnknownError, - DownstreamTemplateAlreadyUsedError, + DownstreampathTemplateAlreadyUsedError, UnableToFindDownstreamRouteError, CannotAddDataError, CannotFindDataError, @@ -18,7 +18,9 @@ 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 00000000..90f2e83e --- /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 00000000..a4c720eb --- /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 ea90179e..f809c84b 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 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 00000000..cd336dec --- /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 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions @@ -91,7 +101,7 @@ namespace Ocelot.AcceptanceTests }; 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 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions @@ -126,7 +139,7 @@ namespace Ocelot.AcceptanceTests }; 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 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions @@ -163,7 +179,7 @@ namespace Ocelot.AcceptanceTests }; 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 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions @@ -200,7 +219,7 @@ namespace Ocelot.AcceptanceTests }; 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 4ceb03f6..1f86c6ff 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -37,7 +37,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51876/", + DownstreamPathTemplate = "/", + DownstreamPort = 51876, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions @@ -91,7 +94,10 @@ namespace Ocelot.AcceptanceTests { 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 34f10b7a..e4e628af 100644 --- a/test/Ocelot.AcceptanceTests/CachingTests.cs +++ b/test/Ocelot.AcceptanceTests/CachingTests.cs @@ -31,7 +31,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", FileCacheOptions = new FileCacheOptions @@ -64,7 +67,10 @@ namespace Ocelot.AcceptanceTests { 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 81a12381..81824602 100644 --- a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs @@ -30,7 +30,10 @@ namespace Ocelot.AcceptanceTests { 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 @@ namespace Ocelot.AcceptanceTests { 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 @@ namespace Ocelot.AcceptanceTests { 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 @@ namespace Ocelot.AcceptanceTests { 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 @@ namespace Ocelot.AcceptanceTests { 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 @@ namespace Ocelot.AcceptanceTests { 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 160686d5..08bbd968 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs @@ -51,7 +51,10 @@ namespace Ocelot.AcceptanceTests { 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 36583018..04dc25db 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs @@ -51,7 +51,10 @@ namespace Ocelot.AcceptanceTests { 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 1a267a98..f6f6de20 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -45,7 +45,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -79,7 +82,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -113,7 +119,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "41879/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -147,7 +156,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -181,7 +193,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -215,7 +230,10 @@ namespace Ocelot.AcceptanceTests { 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 512736ae..9334786b 100644 --- a/test/Ocelot.AcceptanceTests/RequestIdTests.cs +++ b/test/Ocelot.AcceptanceTests/RequestIdTests.cs @@ -33,7 +33,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", RequestIdKey = _steps.RequestIdKey @@ -58,7 +61,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", RequestIdKey = _steps.RequestIdKey @@ -85,7 +91,10 @@ namespace Ocelot.AcceptanceTests { 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 902de662..81307781 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -29,7 +29,7 @@ namespace Ocelot.AcceptanceTests { 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 84e00d5b..4f97114f 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -40,7 +40,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -56,6 +59,62 @@ namespace Ocelot.AcceptanceTests .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 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/products", + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products/", UpstreamHttpMethod = "Get", } @@ -90,7 +152,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/products", + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products", UpstreamHttpMethod = "Get", } @@ -115,7 +180,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/products", + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } @@ -139,7 +207,10 @@ namespace Ocelot.AcceptanceTests { 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 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamHost = "localhost", + DownstreamPort = 51879, + DownstreamScheme = "http", UpstreamTemplate = "/", UpstreamHttpMethod = "Post" } @@ -189,8 +263,11 @@ namespace Ocelot.AcceptanceTests { 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 7b6b8535..a049d3ea 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -10,7 +10,6 @@ namespace Ocelot.ManualTest 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 15276d6e..f7e2bb75 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 e3cccdc0..8822e6b2 100644 --- a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs @@ -67,7 +67,7 @@ namespace Ocelot.UnitTests.Claims { 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 174ea8bc..8a3e24f9 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 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void configuration_is_invalid_if_scheme_in_downstream_template() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + 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() - { - 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 @@ namespace Ocelot.UnitTests.Configuration [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 @@ namespace Ocelot.UnitTests.Configuration [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 @@ namespace Ocelot.UnitTests.Configuration [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 @@ namespace Ocelot.UnitTests.Configuration [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 @@ namespace Ocelot.UnitTests.Configuration _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 4207a333..fc80a478 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -46,7 +46,7 @@ namespace Ocelot.UnitTests.Configuration { DownstreamHost = "127.0.0.1", UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } }, @@ -57,7 +57,7 @@ namespace Ocelot.UnitTests.Configuration { 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 @@ namespace Ocelot.UnitTests.Configuration { DownstreamScheme = "https", UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } }, @@ -87,7 +87,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamScheme("https") - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -106,7 +106,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, ServiceName = "ProductService" @@ -126,7 +126,7 @@ namespace Ocelot.UnitTests.Configuration .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 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, } @@ -160,7 +160,7 @@ namespace Ocelot.UnitTests.Configuration .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 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false } @@ -193,7 +193,7 @@ namespace Ocelot.UnitTests.Configuration .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 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get" } } @@ -222,7 +222,7 @@ namespace Ocelot.UnitTests.Configuration .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 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -252,7 +252,7 @@ namespace Ocelot.UnitTests.Configuration .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 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -286,7 +286,7 @@ namespace Ocelot.UnitTests.Configuration .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 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -317,7 +317,7 @@ namespace Ocelot.UnitTests.Configuration .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 @@ namespace Ocelot.UnitTests.Configuration 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 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, AuthenticationOptions = new FileAuthenticationOptions @@ -395,7 +395,7 @@ namespace Ocelot.UnitTests.Configuration 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 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, AuthenticationOptions = new FileAuthenticationOptions @@ -446,7 +446,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}/variants/{variantId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -457,7 +457,7 @@ namespace Ocelot.UnitTests.Configuration .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 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}/variants/{variantId}/", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -487,7 +487,7 @@ namespace Ocelot.UnitTests.Configuration .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 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/", - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -517,7 +517,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/api/products/") + .WithDownstreamPathTemplate("/api/products/") .WithUpstreamTemplate("/") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/$") @@ -553,7 +553,7 @@ namespace Ocelot.UnitTests.Configuration 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 8f7af24e..ec46f914 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -44,7 +44,7 @@ namespace Ocelot.UnitTests.Configuration 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 @@ namespace Ocelot.UnitTests.Configuration 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 22dfea80..0d5a6d48 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -61,7 +61,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder [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 bb390d32..c0afca42 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -44,7 +44,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPath") + .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("someUpstreamPath") @@ -57,7 +57,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPath") + .WithDownstreamPathTemplate("someDownstreamPath") .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -75,13 +75,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .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 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPathForAPost") + .WithDownstreamPathTemplate("someDownstreamPathForAPost") .Build() ))) .BDDfy(); @@ -107,7 +107,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamTemplate("somPath") + .WithDownstreamPathTemplate("somPath") .WithUpstreamTemplate("somePath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("somePath") @@ -174,7 +174,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder 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 98bc5f0b..5581a32e 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -7,15 +7,18 @@ using Microsoft.AspNetCore.TestHost; 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 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator x.AddLogging(); x.AddSingleton(_downstreamUrlTemplateVariableReplacer.Object); x.AddSingleton(_scopedRepository.Object); + x.AddSingleton(_urlBuilder.Object); }) .UseUrls(_url) .UseKestrel() @@ -61,21 +67,30 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator } [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 00000000..7e512798 --- /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 b1ad369b..a7a5a89b 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs @@ -4,6 +4,7 @@ using Ocelot.DownstreamRouteFinder; 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 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [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 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [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 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [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 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [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 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer 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 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer 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 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer 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 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer 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 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer 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 b85802af..3516d26b 100644 --- a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs @@ -67,7 +67,7 @@ namespace Ocelot.UnitTests.Headers { 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 e4c7375e..39b32937 100644 --- a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs @@ -65,7 +65,7 @@ namespace Ocelot.UnitTests.QueryStrings { 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 8c023783..543613a8 100644 --- a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs @@ -71,7 +71,7 @@ namespace Ocelot.UnitTests.RequestId { 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 @@ namespace Ocelot.UnitTests.RequestId { 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 2cd69d1908b7009637c915ed2d6e6c5edd06f3b9 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Tue, 24 Jan 2017 21:11:15 +0000 Subject: [PATCH 04/42] #20 - added cake build to do same functionality as batch scripts. Also does semver versioning of assemblies, if running in AppVeyor, generates release notes and publishes packages to appveyor. --- .gitignore | 1 + Ocelot.sln | 2 + build.cake | 218 +++++++++++++++++++++++ build.ps1 | 189 ++++++++++++++++++++ src/Ocelot/project.json | 2 +- test/Ocelot.AcceptanceTests/project.json | 8 +- test/Ocelot.Benchmarks/project.json | 4 +- test/Ocelot.ManualTest/project.json | 4 +- test/Ocelot.UnitTests/project.json | 4 +- version.ps1 | 1 + 10 files changed, 422 insertions(+), 11 deletions(-) create mode 100644 build.cake create mode 100644 build.ps1 create mode 100644 version.ps1 diff --git a/.gitignore b/.gitignore index 61b1c211..1a7759c2 100644 --- a/.gitignore +++ b/.gitignore @@ -235,3 +235,4 @@ _Pvt_Extensions # FAKE - F# Make .fake/ +tools/ diff --git a/Ocelot.sln b/Ocelot.sln index 0165beb0..c1f647af 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution appveyor.yml = appveyor.yml build-and-run-tests.bat = build-and-run-tests.bat build.bat = build.bat + build.cake = build.cake + build.ps1 = build.ps1 configuration-explanation.txt = configuration-explanation.txt global.json = global.json LICENSE.md = LICENSE.md diff --git a/build.cake b/build.cake new file mode 100644 index 00000000..513f99dd --- /dev/null +++ b/build.cake @@ -0,0 +1,218 @@ +#tool "nuget:?package=GitVersion.CommandLine" +#tool "nuget:?package=OpenCover" +#tool "nuget:?package=ReportGenerator" +#tool "nuget:?package=GitReleaseNotes" +#addin nuget:?package=Cake.DoInDirectory + +var target = Argument("target", "Default"); +var artifactsDir = Directory("artifacts"); + +Information("target is " +target); + +// versioning +var committedVersion = "0.0.0-dev"; +var buildVersion = committedVersion; + +//compile +var compileConfig = Argument("configuration", "Release"); +Information("Build configuration is " + compileConfig); + +// unit testing +var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests"); +var unitTestAssemblies = @"./test/Ocelot.UnitTests"; + +// acceptance testing +var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests"); +var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests"; + +//benchmark testing +var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); +var benchmarkTestAssemblies = @"./test/Ocelot.Benchmarks"; + +// packaging +var packagesDir = artifactsDir + Directory("Packages"); +var projectJson = "./src/Ocelot/project.json"; + +// release notes +var releaseNotesFile = packagesDir + File("releasenotes.md"); + +Task("Default") + .IsDependentOn("RunTests") + .IsDependentOn("Package") + .Does(() => + { + }); + +Task("Clean") + .Does(() => + { + if (DirectoryExists(artifactsDir)) + { + DeleteDirectory(artifactsDir, recursive:true); + } + CreateDirectory(artifactsDir); + }); + +Task("Version") + .Does(() => + { + var nugetVersion = GetVersion(); + Information("SemVer version number: " + nugetVersion); + + if (AppVeyor.IsRunningOnAppVeyor) + { + Information("Persisting version number..."); + PersistVersion(nugetVersion); + buildVersion = nugetVersion; + } + else + { + Information("We are not running on build server, so we won't persist the version number."); + } + }); + +Task("Restore") + .IsDependentOn("Clean") + .IsDependentOn("Version") + .Does(() => + { + DotNetCoreRestore("./src"); + DotNetCoreRestore("./test"); + }); + +Task("RunUnitTests") + .IsDependentOn("Restore") + .Does(() => + { + var buildSettings = new DotNetCoreTestSettings + { + Configuration = compileConfig, + }; + + EnsureDirectoryExists(artifactsForUnitTestsDir); + DotNetCoreTest(unitTestAssemblies, buildSettings); + }); + +Task("RunAcceptanceTests") + .IsDependentOn("Restore") + .Does(() => + { + var buildSettings = new DotNetCoreTestSettings + { + Configuration = "Debug", //acceptance test config is hard-coded for debug + }; + + EnsureDirectoryExists(artifactsForAcceptanceTestsDir); + + DoInDirectory("test/Ocelot.AcceptanceTests", () => + { + DotNetCoreTest(".", buildSettings); + }); + + }); + +Task("RunBenchmarkTests") + .IsDependentOn("Restore") + .Does(() => + { + var buildSettings = new DotNetCoreRunSettings + { + Configuration = compileConfig, + }; + + EnsureDirectoryExists(artifactsForBenchmarkTestsDir); + + DoInDirectory(benchmarkTestAssemblies, () => + { + DotNetCoreRun(".", "--args", buildSettings); + }); + }); + +Task("RunTests") + .IsDependentOn("RunUnitTests") + .IsDependentOn("RunAcceptanceTests") + .Does(() => + { + }); + +Task("Package") + .Does(() => + { + EnsureDirectoryExists(packagesDir); + + GenerateReleaseNotes(); + + var settings = new DotNetCorePackSettings + { + OutputDirectory = packagesDir, + NoBuild = true + }; + + DotNetCorePack(projectJson, settings); + + System.IO.File.WriteAllLines(packagesDir + File("artifacts"), new[]{ + "nuget:Ocelot." + buildVersion + ".nupkg", + "nugetSymbols:Ocelot." + buildVersion + ".symbols.nupkg", + "releaseNotes:releasenotes.md" + }); + + if (AppVeyor.IsRunningOnAppVeyor) + { + var path = packagesDir.ToString() + @"/**/*"; + + foreach (var file in GetFiles(path)) + { + AppVeyor.UploadArtifact(file.FullPath); + } + } + }); + +RunTarget(target); + +private string GetVersion() +{ + GitVersion(new GitVersionSettings{ + UpdateAssemblyInfo = false, + OutputType = GitVersionOutput.BuildServer + }); + + var versionInfo = GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json }); + return versionInfo.NuGetVersion; +} + +private void PersistVersion(string version) +{ + Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version)); + var projectJsonFiles = GetFiles("./**/project.json"); + + foreach(var projectJsonFile in projectJsonFiles) + { + var file = projectJsonFile.ToString(); + + Information(string.Format("Updating {0}...", file)); + + var updatedProjectJson = System.IO.File.ReadAllText(file) + .Replace(committedVersion, version); + + System.IO.File.WriteAllText(file, updatedProjectJson); + } +} + +private void GenerateReleaseNotes() +{ + Information("Generating release notes at " + releaseNotesFile); + + var releaseNotesExitCode = StartProcess( + @"tools/GitReleaseNotes/tools/gitreleasenotes.exe", + new ProcessSettings { Arguments = ". /o " + releaseNotesFile }); + + if (string.IsNullOrEmpty(System.IO.File.ReadAllText(releaseNotesFile))) + { + System.IO.File.WriteAllText(releaseNotesFile, "No issues closed since last release"); + } + + if (releaseNotesExitCode != 0) + { + throw new Exception("Failed to generate release notes"); + } +} \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 00000000..44de5793 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,189 @@ +########################################################################## +# This is the Cake bootstrapper script for PowerShell. +# This file was downloaded from https://github.com/cake-build/resources +# Feel free to change this file to fit your needs. +########################################################################## + +<# + +.SYNOPSIS +This is a Powershell script to bootstrap a Cake build. + +.DESCRIPTION +This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) +and execute your Cake build script with the parameters you provide. + +.PARAMETER Script +The build script to execute. +.PARAMETER Target +The build script target to run. +.PARAMETER Configuration +The build configuration to use. +.PARAMETER Verbosity +Specifies the amount of information to be displayed. +.PARAMETER Experimental +Tells Cake to use the latest Roslyn release. +.PARAMETER WhatIf +Performs a dry run of the build script. +No tasks will be executed. +.PARAMETER Mono +Tells Cake to use the Mono scripting engine. +.PARAMETER SkipToolPackageRestore +Skips restoring of packages. +.PARAMETER ScriptArgs +Remaining arguments are added here. + +.LINK +http://cakebuild.net + +#> + +[CmdletBinding()] +Param( + [string]$Script = "build.cake", + [string]$Target = "Default", + [ValidateSet("Release", "Debug")] + [string]$Configuration = "Release", + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Verbosity = "Verbose", + [switch]$Experimental, + [Alias("DryRun","Noop")] + [switch]$WhatIf, + [switch]$Mono, + [switch]$SkipToolPackageRestore, + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$ScriptArgs +) + +[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null +function MD5HashFile([string] $filePath) +{ + if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) + { + return $null + } + + [System.IO.Stream] $file = $null; + [System.Security.Cryptography.MD5] $md5 = $null; + try + { + $md5 = [System.Security.Cryptography.MD5]::Create() + $file = [System.IO.File]::OpenRead($filePath) + return [System.BitConverter]::ToString($md5.ComputeHash($file)) + } + finally + { + if ($file -ne $null) + { + $file.Dispose() + } + } +} + +Write-Host "Preparing to run build script..." + +if(!$PSScriptRoot){ + $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +} + +$TOOLS_DIR = Join-Path $PSScriptRoot "tools" +$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" +$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" +$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" +$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" +$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" + +# Should we use mono? +$UseMono = ""; +if($Mono.IsPresent) { + Write-Verbose -Message "Using the Mono based scripting engine." + $UseMono = "-mono" +} + +# Should we use the new Roslyn? +$UseExperimental = ""; +if($Experimental.IsPresent -and !($Mono.IsPresent)) { + Write-Verbose -Message "Using experimental version of Roslyn." + $UseExperimental = "-experimental" +} + +# Is this a dry run? +$UseDryRun = ""; +if($WhatIf.IsPresent) { + $UseDryRun = "-dryrun" +} + +# Make sure tools folder exists +if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { + Write-Verbose -Message "Creating tools directory..." + New-Item -Path $TOOLS_DIR -Type directory | out-null +} + +# Make sure that packages.config exist. +if (!(Test-Path $PACKAGES_CONFIG)) { + Write-Verbose -Message "Downloading packages.config..." + try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { + Throw "Could not download packages.config." + } +} + +# Try find NuGet.exe in path if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Trying to find nuget.exe in PATH..." + $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) } + $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 + if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { + Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." + $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName + } +} + +# Try download NuGet.exe if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Downloading NuGet.exe..." + try { + (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE) + } catch { + Throw "Could not download NuGet.exe." + } +} + +# Save nuget.exe path to environment to be available to child processed +$ENV:NUGET_EXE = $NUGET_EXE + +# Restore tools from NuGet? +if(-Not $SkipToolPackageRestore.IsPresent) { + Push-Location + Set-Location $TOOLS_DIR + + # Check for changes in packages.config and remove installed tools if true. + [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) + if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or + ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { + Write-Verbose -Message "Missing or changed package.config hash..." + Remove-Item * -Recurse -Exclude packages.config,nuget.exe + } + + Write-Verbose -Message "Restoring tools from NuGet..." + $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occured while restoring NuGet tools." + } + else + { + $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" + } + Write-Verbose -Message ($NuGetOutput | out-string) + Pop-Location +} + +# Make sure that Cake has been installed. +if (!(Test-Path $CAKE_EXE)) { + Throw "Could not find Cake.exe at $CAKE_EXE" +} + +# Start Cake +Write-Host "Running build script..." +Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" +exit $LASTEXITCODE \ No newline at end of file diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 85008568..8d259469 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "dependencies": { "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 4b364510..17f35a3c 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "buildOptions": { "copyToOutput": { @@ -22,10 +22,10 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", "Microsoft.AspNetCore.Http": "1.1.0", "Microsoft.DotNet.InternalAbstractions": "1.0.0", - "Ocelot": "1.0.0-*", + "Ocelot": "0.0.0-dev", "xunit": "2.2.0-beta2-build3300", "dotnet-test-xunit": "2.2.0-preview2-build1029", - "Ocelot.ManualTest": "1.0.0-*", + "Ocelot.ManualTest": "0.0.0-dev", "Microsoft.AspNetCore.TestHost": "1.1.0", "IdentityServer4": "1.0.1", "Microsoft.AspNetCore.Mvc": "1.1.0", @@ -36,7 +36,7 @@ }, "runtimes": { "win10-x64": {}, - "osx.10.11-x64":{}, + "osx.10.11-x64": {}, "win7-x64": {} }, "frameworks": { diff --git a/test/Ocelot.Benchmarks/project.json b/test/Ocelot.Benchmarks/project.json index da310ddd..5f7a4987 100644 --- a/test/Ocelot.Benchmarks/project.json +++ b/test/Ocelot.Benchmarks/project.json @@ -1,11 +1,11 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "buildOptions": { "emitEntryPoint": true }, "dependencies": { - "Ocelot": "1.0.0-*", + "Ocelot": "0.0.0-dev", "BenchmarkDotNet": "0.10.1" }, "runtimes": { diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index 181bdb07..3ae09ccb 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "dependencies": { "Microsoft.AspNetCore.Http": "1.1.0", @@ -10,7 +10,7 @@ "Microsoft.Extensions.Logging.Console": "1.1.0", "Microsoft.Extensions.Logging.Debug": "1.1.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", - "Ocelot": "1.0.0-*", + "Ocelot": "0.0.0-dev", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", "Microsoft.NETCore.App": "1.1.0" }, diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index 605b25d6..ab3e6cb1 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "testRunner": "xunit", @@ -13,7 +13,7 @@ "Microsoft.Extensions.Logging.Debug": "1.1.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", "Microsoft.AspNetCore.Http": "1.1.0", - "Ocelot": "1.0.0-*", + "Ocelot": "0.0.0-dev", "xunit": "2.2.0-beta2-build3300", "dotnet-test-xunit": "2.2.0-preview2-build1029", "Moq": "4.6.38-alpha", diff --git a/version.ps1 b/version.ps1 new file mode 100644 index 00000000..621201b6 --- /dev/null +++ b/version.ps1 @@ -0,0 +1 @@ +.\tools\GitVersion.CommandLine\tools\GitVersion.exe \ No newline at end of file From 93ebb6a0d369c62c215f33b9ead1dabbe13fb7e6 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Tue, 24 Jan 2017 21:27:25 +0000 Subject: [PATCH 05/42] #20 - bump commit --- test.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 test.txt diff --git a/test.txt b/test.txt new file mode 100644 index 00000000..30d74d25 --- /dev/null +++ b/test.txt @@ -0,0 +1 @@ +test \ No newline at end of file From 4a43accc4688012bbb6d07ee6d24a1dcf8789214 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sun, 29 Jan 2017 09:41:05 +0000 Subject: [PATCH 06/42] 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 8144b42b..b4d73c7c 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -46,12 +46,18 @@ namespace Ocelot.DownstreamUrlCreator.Middleware 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 @@ namespace Ocelot.DownstreamUrlCreator.Middleware 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 5de770cd..85c3e097 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -21,6 +21,8 @@ DownstreamPathTemplateContainsSchemeError, DownstreamPathNullOrEmptyError, DownstreamSchemeNullOrEmptyError, - DownstreamHostNullOrEmptyError + DownstreamHostNullOrEmptyError, + ServicesAreNullError, + ServicesAreEmptyError } } diff --git a/src/Ocelot/Values/HostAndPort.cs b/src/Ocelot/Values/HostAndPort.cs index cd336dec..f8769743 100644 --- a/src/Ocelot/Values/HostAndPort.cs +++ b/src/Ocelot/Values/HostAndPort.cs @@ -10,5 +10,10 @@ 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 00000000..758d1917 --- /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 82689b62..5b7da070 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 @@ namespace Ocelot.UnitTests { private readonly RoundRobin _roundRobin; private readonly List _hostAndPorts; + private Response _hostAndPort; public RoundRobinTests() { @@ -26,12 +29,13 @@ namespace Ocelot.UnitTests [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 @@ namespace Ocelot.UnitTests 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 @@ namespace Ocelot.UnitTests _hostAndPorts = hostAndPorts; } - public HostAndPort Next() + public Response Lease() { if (_last >= _hostAndPorts.Count) { @@ -75,7 +90,12 @@ namespace Ocelot.UnitTests 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 8866f1ae..27987d42 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 @@ namespace Ocelot.UnitTests [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 @@ namespace Ocelot.UnitTests 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 @@ namespace Ocelot.UnitTests 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 @@ namespace Ocelot.UnitTests _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 @@ namespace Ocelot.UnitTests 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 @@ namespace Ocelot.UnitTests 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 e747d3386a63bb935eef6d25bb9554b2caf09995 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 29 Jan 2017 13:00:50 +0000 Subject: [PATCH 07/42] #20 - add release scripts --- Ocelot.sln | 2 + build.cake | 141 ++++++++++++++++++++++++++++++++++++++++++++++++---- release.ps1 | 1 + 3 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 release.ps1 diff --git a/Ocelot.sln b/Ocelot.sln index c1f647af..3bc1f60a 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -19,6 +19,8 @@ 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 + release.cake = release.cake + release.ps1 = release.ps1 run-acceptance-tests.bat = run-acceptance-tests.bat run-benchmarks.bat = run-benchmarks.bat run-tests.bat = run-tests.bat diff --git a/build.cake b/build.cake index 513f99dd..73dbae18 100644 --- a/build.cake +++ b/build.cake @@ -3,6 +3,7 @@ #tool "nuget:?package=ReportGenerator" #tool "nuget:?package=GitReleaseNotes" #addin nuget:?package=Cake.DoInDirectory +#addin "Cake.Json" var target = Argument("target", "Default"); var artifactsDir = Directory("artifacts"); @@ -25,20 +26,32 @@ var unitTestAssemblies = @"./test/Ocelot.UnitTests"; var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests"); var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests"; -//benchmark testing +// benchmark testing var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); var benchmarkTestAssemblies = @"./test/Ocelot.Benchmarks"; // packaging -var packagesDir = artifactsDir + Directory("Packages"); var projectJson = "./src/Ocelot/project.json"; - -// release notes +var packagesDir = artifactsDir + Directory("Packages"); var releaseNotesFile = packagesDir + File("releasenotes.md"); +var artifactsFile = packagesDir + File("artifacts.txt"); + +//unstable releases +var publishUnstableBuilds = true; +var nugetFeedUnstableKey = EnvironmentVariable("nuget-apikey-unstable"); +var nugetFeedUnstableUploadUrl = "https://www.myget.org/F/ocelot-unstable/api/v2/package"; +var nugetFeedUnstableSymbolsUploadUrl = "https://www.myget.org/F/ocelot-unstable/symbols/api/v2/package"; + +//stable releases +var releaseTag = ""; +var nugetFeedStableKey = EnvironmentVariable("nuget-apikey-stable"); +var nugetFeedStableUploadUrl = "https://www.myget.org/F/ocelot-stable/api/v2/package"; +var nugetFeedStableSymbolsUploadUrl = "https://www.myget.org/F/ocelot-stable/symbols/api/v2/package"; Task("Default") .IsDependentOn("RunTests") - .IsDependentOn("Package") + .IsDependentOn("CreatePackages") + .IsDependentOn("ReleasePackagesToUnstableFeed") .Does(() => { }); @@ -56,7 +69,7 @@ Task("Clean") Task("Version") .Does(() => { - var nugetVersion = GetVersion(); + var nugetVersion = GetNuGetVersionForCommit(); Information("SemVer version number: " + nugetVersion); if (AppVeyor.IsRunningOnAppVeyor) @@ -135,7 +148,7 @@ Task("RunTests") { }); -Task("Package") +Task("CreatePackages") .Does(() => { EnsureDirectoryExists(packagesDir); @@ -150,7 +163,7 @@ Task("Package") DotNetCorePack(projectJson, settings); - System.IO.File.WriteAllLines(packagesDir + File("artifacts"), new[]{ + System.IO.File.WriteAllLines(artifactsFile, new[]{ "nuget:Ocelot." + buildVersion + ".nupkg", "nugetSymbols:Ocelot." + buildVersion + ".symbols.nupkg", "releaseNotes:releasenotes.md" @@ -167,9 +180,71 @@ Task("Package") } }); +Task("ReleasePackagesToUnstableFeed") + .IsDependentOn("CreatePackages") + .Does(() => + { + PublishPackages(nugetFeedUnstableKey, nugetFeedUnstableUploadUrl, nugetFeedUnstableSymbolsUploadUrl); + }); + +Task("EnsureStableReleaseRequirements") + .Does(() => + { + if (!AppVeyor.IsRunningOnAppVeyor) + { + throw new Exception("Stable release should happen via appveyor"); + } + + var isTag = + AppVeyor.Environment.Repository.Tag.IsTag && + !string.IsNullOrWhiteSpace(AppVeyor.Environment.Repository.Tag.Name); + + if (!isTag) + { + throw new Exception("Stable release should happen from a published GitHub release"); + } + }); + +Task("UpdateVersionInfo") + .IsDependentOn("EnsureStableReleaseRequirements") + .Does(() => + { + releaseTag = AppVeyor.Environment.Repository.Tag.Name; + AppVeyor.UpdateBuildVersion(releaseTag); + }); + +Task("DownloadGitHubReleaseArtifacts") + .IsDependentOn("UpdateVersionInfo") + .Does(() => + { + EnsureDirectoryExists(packagesDir); + + var assets_url = ParseJson(GetResource("https://api.github.com/repos/binarymash/pipelinetesting/releases/tags/" + releaseTag)) + .GetValue("assets_url") + .Value(); + + foreach(var asset in DeserializeJson(GetResource(assets_url))) + { + var file = packagesDir + File(asset.Value("name")); + Information("Downloading " + file); + DownloadFile(asset.Value("browser_download_url"), file); + } + }); + +Task("ReleasePackagesToStableFeed") + .IsDependentOn("DownloadGitHubReleaseArtifacts") + .Does(() => + { + PublishPackages(nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl); + }); + +Task("Release") + .IsDependentOn("ReleasePackagesToStableFeed"); + RunTarget(target); -private string GetVersion() +/// Gets nuique nuget version for this commit +private string GetNuGetVersionForCommit() { GitVersion(new GitVersionSettings{ UpdateAssemblyInfo = false, @@ -180,9 +255,11 @@ private string GetVersion() return versionInfo.NuGetVersion; } +/// 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)); + var projectJsonFiles = GetFiles("./**/project.json"); foreach(var projectJsonFile in projectJsonFiles) @@ -198,6 +275,7 @@ private void PersistVersion(string version) } } +/// generates release notes based on issues closed in GitHub since the last release private void GenerateReleaseNotes() { Information("Generating release notes at " + releaseNotesFile); @@ -215,4 +293,49 @@ private void GenerateReleaseNotes() { throw new Exception("Failed to generate release notes"); } +} + +/// Publishes code and symbols packages to nuget feed, based on contents of artifacts file +private void PublishPackages(string feedApiKey, string codeFeedUrl, string symbolFeedUrl) +{ + var artifacts = System.IO.File + .ReadAllLines(artifactsFile) + .Select(l => l.Split(':')) + .ToDictionary(v => v[0], v => v[1]); + + var codePackage = packagesDir + File(artifacts["nuget"]); + var symbolsPackage = packagesDir + File(artifacts["nugetSymbols"]); + + NuGetPush( + codePackage, + new NuGetPushSettings { + ApiKey = feedApiKey, + Source = codeFeedUrl + }); + + NuGetPush( + symbolsPackage, + new NuGetPushSettings { + ApiKey = feedApiKey, + Source = symbolFeedUrl + }); + +} + +/// gets the resource from the specified url +private string GetResource(string url) +{ + Information("Getting resource from " + url); + + var assetsRequest = System.Net.WebRequest.CreateHttp(url); + assetsRequest.Method = "GET"; + assetsRequest.Accept = "application/vnd.github.v3+json"; + assetsRequest.UserAgent = "BuildScript"; + + using (var assetsResponse = assetsRequest.GetResponse()) + { + var assetsStream = assetsResponse.GetResponseStream(); + var assetsReader = new StreamReader(assetsStream); + return assetsReader.ReadToEnd(); + } } \ No newline at end of file diff --git a/release.ps1 b/release.ps1 new file mode 100644 index 00000000..6cf4c66b --- /dev/null +++ b/release.ps1 @@ -0,0 +1 @@ +./build.ps1 -target Release \ No newline at end of file From b50d2b1acae5ea8c420b155fc87023b7c38e7afa Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 29 Jan 2017 13:17:57 +0000 Subject: [PATCH 08/42] #20 - remove obsolete file --- Ocelot.sln | 1 - 1 file changed, 1 deletion(-) diff --git a/Ocelot.sln b/Ocelot.sln index 3bc1f60a..282c7120 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -19,7 +19,6 @@ 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 - release.cake = release.cake release.ps1 = release.ps1 run-acceptance-tests.bat = run-acceptance-tests.bat run-benchmarks.bat = run-benchmarks.bat From 0172950dab730150b86336f525ffffa3265d321c Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 29 Jan 2017 13:47:59 +0000 Subject: [PATCH 09/42] #20 - added release notes to repo --- ReleaseNotes.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 ReleaseNotes.md diff --git a/ReleaseNotes.md b/ReleaseNotes.md new file mode 100644 index 00000000..a647c6c3 --- /dev/null +++ b/ReleaseNotes.md @@ -0,0 +1 @@ +No issues closed since last release \ No newline at end of file From e4a22e57d715506b1f55e073d0b01ba5578c4e48 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 29 Jan 2017 14:10:49 +0000 Subject: [PATCH 10/42] #20 - fix download of github release tags --- build.cake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.cake b/build.cake index 73dbae18..0781fcfc 100644 --- a/build.cake +++ b/build.cake @@ -43,6 +43,7 @@ var nugetFeedUnstableUploadUrl = "https://www.myget.org/F/ocelot-unstable/api/v2 var nugetFeedUnstableSymbolsUploadUrl = "https://www.myget.org/F/ocelot-unstable/symbols/api/v2/package"; //stable releases +var tagsUrl = "https://api.github.com/repos/binarymash/ocelot/releases/tags/"; var releaseTag = ""; var nugetFeedStableKey = EnvironmentVariable("nuget-apikey-stable"); var nugetFeedStableUploadUrl = "https://www.myget.org/F/ocelot-stable/api/v2/package"; @@ -219,7 +220,8 @@ Task("DownloadGitHubReleaseArtifacts") { EnsureDirectoryExists(packagesDir); - var assets_url = ParseJson(GetResource("https://api.github.com/repos/binarymash/pipelinetesting/releases/tags/" + releaseTag)) + var releaseUrl = tagsUrl + releaseTag; + var assets_url = ParseJson(GetResource(releaseUrl)) .GetValue("assets_url") .Value(); From 33854067d44c24937cf1f4b633bf8fd397e59c5a Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 29 Jan 2017 15:14:31 +0000 Subject: [PATCH 11/42] #20 - Tidy up. Update command line scripts. Fix running benchmarks. --- Ocelot.sln | 10 +++--- build-and-release-unstable.ps1 | 1 + build-and-run-tests.bat | 2 -- build-and-run-tests.ps1 | 1 + build.bat | 8 ----- build.cake | 56 ++++++++++++++++++---------------- run-acceptance-tests.bat | 8 ----- run-acceptance-tests.ps1 | 1 + run-benchmarks.bat | 15 --------- run-benchmarks.ps1 | 1 + run-tests.bat | 2 -- run-unit-tests.bat | 8 ----- run-unit-tests.ps1 | 1 + test.txt | 1 - 14 files changed, 39 insertions(+), 76 deletions(-) create mode 100644 build-and-release-unstable.ps1 delete mode 100755 build-and-run-tests.bat create mode 100644 build-and-run-tests.ps1 delete mode 100755 build.bat delete mode 100755 run-acceptance-tests.bat create mode 100644 run-acceptance-tests.ps1 delete mode 100644 run-benchmarks.bat create mode 100644 run-benchmarks.ps1 delete mode 100755 run-tests.bat delete mode 100755 run-unit-tests.bat create mode 100644 run-unit-tests.ps1 delete mode 100644 test.txt diff --git a/Ocelot.sln b/Ocelot.sln index 282c7120..2f8fd206 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -9,21 +9,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .gitignore = .gitignore appveyor.yml = appveyor.yml - build-and-run-tests.bat = build-and-run-tests.bat - build.bat = build.bat + build-and-run-tests.ps1 = build-and-run-tests.ps1 build.cake = build.cake build.ps1 = build.ps1 configuration-explanation.txt = configuration-explanation.txt global.json = global.json LICENSE.md = LICENSE.md Ocelot.nuspec = Ocelot.nuspec - push-to-nuget.bat = push-to-nuget.bat README.md = README.md release.ps1 = release.ps1 - run-acceptance-tests.bat = run-acceptance-tests.bat + run-acceptance-tests.ps1 = run-acceptance-tests.ps1 run-benchmarks.bat = run-benchmarks.bat - run-tests.bat = run-tests.bat - run-unit-tests.bat = run-unit-tests.bat + run-benchmarks.ps1 = run-benchmarks.ps1 + run-unit-tests.ps1 = run-unit-tests.ps1 EndProjectSection EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot", "src\Ocelot\Ocelot.xproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}" diff --git a/build-and-release-unstable.ps1 b/build-and-release-unstable.ps1 new file mode 100644 index 00000000..9a29f95f --- /dev/null +++ b/build-and-release-unstable.ps1 @@ -0,0 +1 @@ +./build.ps1 -target build-full \ No newline at end of file diff --git a/build-and-run-tests.bat b/build-and-run-tests.bat deleted file mode 100755 index 764682b6..00000000 --- a/build-and-run-tests.bat +++ /dev/null @@ -1,2 +0,0 @@ -./run-tests.bat -./build.bat \ No newline at end of file diff --git a/build-and-run-tests.ps1 b/build-and-run-tests.ps1 new file mode 100644 index 00000000..f82502e5 --- /dev/null +++ b/build-and-run-tests.ps1 @@ -0,0 +1 @@ +./build.ps1 -target RunTests \ No newline at end of file diff --git a/build.bat b/build.bat deleted file mode 100755 index 656515d4..00000000 --- a/build.bat +++ /dev/null @@ -1,8 +0,0 @@ -echo ------------------------- - -echo Building Ocelot -dotnet restore src/Ocelot -dotnet build src/Ocelot -c Release - - - diff --git a/build.cake b/build.cake index 0781fcfc..3fbeeaf7 100644 --- a/build.cake +++ b/build.cake @@ -2,21 +2,15 @@ #tool "nuget:?package=OpenCover" #tool "nuget:?package=ReportGenerator" #tool "nuget:?package=GitReleaseNotes" -#addin nuget:?package=Cake.DoInDirectory -#addin "Cake.Json" +#addin "nuget:?package=Cake.DoInDirectory" +#addin "nuget:?package=Cake.Json" -var target = Argument("target", "Default"); -var artifactsDir = Directory("artifacts"); - -Information("target is " +target); - -// versioning -var committedVersion = "0.0.0-dev"; -var buildVersion = committedVersion; - -//compile +// compile var compileConfig = Argument("configuration", "Release"); -Information("Build configuration is " + compileConfig); +var projectJson = "./src/Ocelot/project.json"; + +// build artifacts +var artifactsDir = Directory("artifacts"); // unit testing var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests"); @@ -31,32 +25,42 @@ var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); var benchmarkTestAssemblies = @"./test/Ocelot.Benchmarks"; // packaging -var projectJson = "./src/Ocelot/project.json"; var packagesDir = artifactsDir + Directory("Packages"); var releaseNotesFile = packagesDir + File("releasenotes.md"); var artifactsFile = packagesDir + File("artifacts.txt"); -//unstable releases -var publishUnstableBuilds = true; +// unstable releases var nugetFeedUnstableKey = EnvironmentVariable("nuget-apikey-unstable"); var nugetFeedUnstableUploadUrl = "https://www.myget.org/F/ocelot-unstable/api/v2/package"; var nugetFeedUnstableSymbolsUploadUrl = "https://www.myget.org/F/ocelot-unstable/symbols/api/v2/package"; -//stable releases +// stable releases var tagsUrl = "https://api.github.com/repos/binarymash/ocelot/releases/tags/"; -var releaseTag = ""; var nugetFeedStableKey = EnvironmentVariable("nuget-apikey-stable"); var nugetFeedStableUploadUrl = "https://www.myget.org/F/ocelot-stable/api/v2/package"; var nugetFeedStableSymbolsUploadUrl = "https://www.myget.org/F/ocelot-stable/symbols/api/v2/package"; -Task("Default") - .IsDependentOn("RunTests") - .IsDependentOn("CreatePackages") - .IsDependentOn("ReleasePackagesToUnstableFeed") - .Does(() => - { - }); +// internal build variables - don't change these. +var releaseTag = ""; +var buildVersion = committedVersion; +var committedVersion = "0.0.0-dev"; +var target = Argument("target", "Default"); + +Information("target is " +target); +Information("Build configuration is " + compileConfig); + +Task("Default") + .IsDependentOn("Build"); + +Task("Build") + .IsDependentOn("RunTests") + .IsDependentOn("CreatePackages"); + +Task("BuildAndReleaseUnstable") + .IsDependentOn("Build") + .IsDependentOn("ReleasePackagesToUnstableFeed"); + Task("Clean") .Does(() => { @@ -138,7 +142,7 @@ Task("RunBenchmarkTests") DoInDirectory(benchmarkTestAssemblies, () => { - DotNetCoreRun(".", "--args", buildSettings); + DotNetCoreRun(".", "", buildSettings); }); }); diff --git a/run-acceptance-tests.bat b/run-acceptance-tests.bat deleted file mode 100755 index ba8a3489..00000000 --- a/run-acceptance-tests.bat +++ /dev/null @@ -1,8 +0,0 @@ -echo Running Ocelot.AcceptanceTests -cd test/Ocelot.AcceptanceTests/ -dotnet restore -dotnet test -cd ../../ - -echo Restoring Ocelot.ManualTest -dotnet restore test/Ocelot.ManualTest/ \ No newline at end of file diff --git a/run-acceptance-tests.ps1 b/run-acceptance-tests.ps1 new file mode 100644 index 00000000..480e1d4c --- /dev/null +++ b/run-acceptance-tests.ps1 @@ -0,0 +1 @@ +./build -target RunAcceptanceTests \ No newline at end of file diff --git a/run-benchmarks.bat b/run-benchmarks.bat deleted file mode 100644 index 1376f17a..00000000 --- a/run-benchmarks.bat +++ /dev/null @@ -1,15 +0,0 @@ -echo ------------------------- - -echo Running Ocelot.Benchmarks - -cd test/Ocelot.Benchmarks - -dotnet restore - -dotnet run - -cd ../../ - - - - diff --git a/run-benchmarks.ps1 b/run-benchmarks.ps1 new file mode 100644 index 00000000..e05490fd --- /dev/null +++ b/run-benchmarks.ps1 @@ -0,0 +1 @@ +./build.ps1 -target RunBenchmarkTests \ No newline at end of file diff --git a/run-tests.bat b/run-tests.bat deleted file mode 100755 index 39532229..00000000 --- a/run-tests.bat +++ /dev/null @@ -1,2 +0,0 @@ -./run-unit-tests.bat -./run-acceptance-tests.bat \ No newline at end of file diff --git a/run-unit-tests.bat b/run-unit-tests.bat deleted file mode 100755 index 9ad6a4f2..00000000 --- a/run-unit-tests.bat +++ /dev/null @@ -1,8 +0,0 @@ -echo ------------------------- - -echo Restoring Ocelot -dotnet restore src/Ocelot - -echo Running Ocelot.UnitTests -dotnet restore test/Ocelot.UnitTests/ -dotnet test test/Ocelot.UnitTests/ diff --git a/run-unit-tests.ps1 b/run-unit-tests.ps1 new file mode 100644 index 00000000..0e6a91bd --- /dev/null +++ b/run-unit-tests.ps1 @@ -0,0 +1 @@ +./build.ps1 -target RunUnitTests \ No newline at end of file diff --git a/test.txt b/test.txt deleted file mode 100644 index 30d74d25..00000000 --- a/test.txt +++ /dev/null @@ -1 +0,0 @@ -test \ No newline at end of file From 17dac3d3a2066fb496ada79fb81f0cdeb0a5ebdb Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 29 Jan 2017 16:16:08 +0000 Subject: [PATCH 12/42] #20 - added readme for build and release process. --- build.readme.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 build.readme.md diff --git a/build.readme.md b/build.readme.md new file mode 100644 index 00000000..fccff4d5 --- /dev/null +++ b/build.readme.md @@ -0,0 +1,22 @@ +#1. Overview + +This document summarises the build and release process for the project. The build scripts are written using [Cake](http://cakebuild.net/), and are defined in `./build.cake`. The scripts have been designed to be run by either developers locally or by a build server (currently [AppVeyor](https://www.appveyor.com/)), with minimal logic defined in the build server itself. + +#2. Building + * You'll generally want to run the `./build.ps1` script. This will compile, run unit and acceptance tests and build the output packages locally. Output will got to the `./artifacts` directory. + * You can view the current commit's [SemVer](http://semver.org/) build information by running `./version.ps1`. + * The other `./*.ps1` scripts perform subsets of the build process, if you don't want to run the full build. + * The release process works best with GitFlow branching; this allows us to publish every development commit to an unstable feed with a unique SemVer version, and then choose when to release to a stable feed. + +#3. Release process +This section defines the release process for the maintainers of the project. + * Merge pull requests to the `release` branch. + * Every commit pushed to the Origin repo will kick off the [ocelot-build](https://ci.appveyor.com/project/binarymash/ocelot) project in AppVeyor. This performs the same tasks as the command line build, and in addition pushes the packages to the unstable nuget feed. + * When you're ready for a release, create a release branch. You'll probably want to update the committed `./ReleaseNotes.md` based on the contents of the equivalent file in the `./artifacts` directory. + * When the `release` branch has built successfully in Appveyor, select the build and then Deploy to the `GitHub Release` environment. This will create a new release in GitHub. + * In Github, navigate to the [release](https://github.com/binarymash/Ocelot/releases). Modify the release name and tag as desired. + * When you're ready, publish the release. This will tag the commit with the specified release number. + * The [ocelot-release](https://ci.appveyor.com/project/binarymash/ocelot-wtaj9) project will detect the newly created tag and kick off the release process. This will download the artifacts from GitHub, and publish the packages to the stable nuget feed. + * When you have a final stable release build, merge the `release` branch into `master` and `develop`. Deploy the master branch to github and following the full release process as described above. Don't forget to uncheck the "This is a pre-release" checkbox in GitHub before publishing. + * Note - because the release builds are initiated by tagging a commit, if for some reason a release build fails in AppVeyor you'll need to delete the tag from the repo and republish the release in GitHub. + From 727dbb5f7d23c26ea594cca83da3b1c2315a9125 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 29 Jan 2017 18:19:16 +0000 Subject: [PATCH 13/42] #20 - add gitversion config --- GitVersion.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 GitVersion.yml diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 00000000..05e9ac41 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,4 @@ +mode: ContinuousDelivery +branches: {} +ignore: + sha: [] From 0e92976df8204f76c25712409966025bc352efb5 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 1 Feb 2017 06:40:29 +0000 Subject: [PATCH 14/42] 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 1e06a440..ecc0b67f 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -32,12 +32,19 @@ namespace Ocelot.Configuration.Builder 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 @@ namespace Ocelot.Configuration.Builder _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 8884f0d9..d09d738c 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -97,6 +97,13 @@ namespace Ocelot.Configuration.Creator && !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 @@ namespace Ocelot.Configuration.Creator 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 @@ namespace Ocelot.Configuration.Creator 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 a653224a..0d0fc7bd 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -29,5 +29,6 @@ namespace Ocelot.Configuration.File public string DownstreamScheme {get;set;} public string DownstreamHost {get;set;} public int DownstreamPort { get; set; } + public string LoadBalancer {get;set;} } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 960374cc..9faab4ab 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -10,8 +10,10 @@ namespace Ocelot.Configuration 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 @@ namespace Ocelot.Configuration 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 @@ namespace Ocelot.Configuration 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 b4d73c7c..957acb8e 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -51,6 +51,8 @@ namespace Ocelot.DownstreamUrlCreator.Middleware //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 00000000..6d2a3a59 --- /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 5b7da070..f1271460 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 @@ namespace Ocelot.UnitTests new HostAndPort("127.0.0.1", 5001) }; - _roundRobin = new RoundRobin(_hostAndPorts); + _roundRobin = new RoundRobinLoadBalancer(_hostAndPorts); } [Fact] @@ -71,12 +71,12 @@ namespace Ocelot.UnitTests 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 15/42] 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 d09d738c..24c4db72 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -7,6 +7,7 @@ using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; using Ocelot.Responses; +using Ocelot.ServiceDiscovery; using Ocelot.Utilities; using Ocelot.Values; @@ -104,7 +105,22 @@ namespace Ocelot.Configuration.Creator //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 00000000..60e428c8 --- /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 00000000..0043edaa --- /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 00000000..9f8b0239 --- /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 00000000..583774e7 --- /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 00000000..104fbc09 --- /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 758d1917..ede2105f 100644 --- a/test/Ocelot.UnitTests/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LeastConnectionTests.cs @@ -14,7 +14,7 @@ namespace Ocelot.UnitTests { private HostAndPort _hostAndPort; private Response _result; - private LeastConnection _leastConnection; + private LeastConnectionLoadBalancer _leastConnection; private List _services; public LeastConnectionTests() @@ -53,7 +53,7 @@ namespace Ocelot.UnitTests }; _services = availableServices; - _leastConnection = new LeastConnection(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); var response = _leastConnection.Lease(); @@ -80,7 +80,7 @@ namespace Ocelot.UnitTests }; _services = availableServices; - _leastConnection = new LeastConnection(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); var response = _leastConnection.Lease(); @@ -111,7 +111,7 @@ namespace Ocelot.UnitTests }; _services = availableServices; - _leastConnection = new LeastConnection(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); var response = _leastConnection.Lease(); @@ -178,7 +178,7 @@ namespace Ocelot.UnitTests 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 @@ namespace Ocelot.UnitTests } } - 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 6d2a3a59..12f59ceb 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 @@ namespace Ocelot.UnitTests 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 @@ namespace Ocelot.UnitTests 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 @@ namespace Ocelot.UnitTests .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 @@ namespace Ocelot.UnitTests } } - 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() { - throw new NotImplementedException(); + 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(); } - Response ILoadBalancer.Release(HostAndPort hostAndPort) + private void GivenServices(List services) { - throw new NotImplementedException(); + _services = services; + } + + private void WhenIGetTheNextHostAndPort() + { + _loadBalancer = new NoLoadBalancer(_services); + _result = _loadBalancer.Lease(); + } + + private void ThenTheHostAndPortIs(HostAndPort expected) + { + _result.Data.ShouldBe(expected); + } + } + + public class NoLoadBalancer : ILoadBalancer + { + private List _services; + + public NoLoadBalancer(List services) + { + _services = services; + } + + public Response Lease() + { + var service = _services.FirstOrDefault(); + return new OkResponse(service.HostAndPort); + } + + public Response Release(HostAndPort hostAndPort) + { + return new OkResponse(); } } @@ -81,14 +164,23 @@ namespace Ocelot.UnitTests 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 00000000..f85ef13c --- /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 f1271460..39d33441 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 @@ namespace Ocelot.UnitTests 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 @@ namespace Ocelot.UnitTests private void ThenTheNextAddressIndexIs(int index) { - _hostAndPort.Data.ShouldBe(_hostAndPorts[index]); + _hostAndPort.Data.ShouldBe(_services[index].HostAndPort); } } @@ -73,24 +73,24 @@ namespace Ocelot.UnitTests 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 00000000..05609816 --- /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 27987d42..09012213 100644 --- a/test/Ocelot.UnitTests/ServiceRegistryTests.cs +++ b/test/Ocelot.UnitTests/ServiceRegistryTests.cs @@ -86,6 +86,7 @@ namespace Ocelot.UnitTests { _repository = repository; } + public void Register(Service serviceNameAndAddress) { _repository.Set(serviceNameAndAddress); @@ -97,17 +98,6 @@ namespace Ocelot.UnitTests } } - 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 3642aac20defade863750fd82a671544443682f3 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Wed, 1 Feb 2017 20:43:39 +0000 Subject: [PATCH 16/42] Removed push-to-nuget build script and changed cake nuget feed urls --- build.cake | 10 +++++----- push-to-nuget.bat | 11 ----------- 2 files changed, 5 insertions(+), 16 deletions(-) delete mode 100644 push-to-nuget.bat diff --git a/build.cake b/build.cake index 3fbeeaf7..1d798d74 100644 --- a/build.cake +++ b/build.cake @@ -31,14 +31,14 @@ var artifactsFile = packagesDir + File("artifacts.txt"); // unstable releases var nugetFeedUnstableKey = EnvironmentVariable("nuget-apikey-unstable"); -var nugetFeedUnstableUploadUrl = "https://www.myget.org/F/ocelot-unstable/api/v2/package"; -var nugetFeedUnstableSymbolsUploadUrl = "https://www.myget.org/F/ocelot-unstable/symbols/api/v2/package"; +var nugetFeedUnstableUploadUrl = "https://www.nuget.org/api/v2/package"; +var nugetFeedUnstableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; // stable releases -var tagsUrl = "https://api.github.com/repos/binarymash/ocelot/releases/tags/"; +var tagsUrl = "https://api.github.com/repos/tompallister/ocelot/releases/tags/"; var nugetFeedStableKey = EnvironmentVariable("nuget-apikey-stable"); -var nugetFeedStableUploadUrl = "https://www.myget.org/F/ocelot-stable/api/v2/package"; -var nugetFeedStableSymbolsUploadUrl = "https://www.myget.org/F/ocelot-stable/symbols/api/v2/package"; +var nugetFeedStableUploadUrl = "https://www.nuget.org/api/v2/package"; +var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; // internal build variables - don't change these. var releaseTag = ""; diff --git a/push-to-nuget.bat b/push-to-nuget.bat deleted file mode 100644 index 7200d09c..00000000 --- a/push-to-nuget.bat +++ /dev/null @@ -1,11 +0,0 @@ -echo ------------------------- - -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 - - - - From 9cc5c2c9648161de54dcc12da03a8ad0dad75090 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Wed, 1 Feb 2017 20:45:27 +0000 Subject: [PATCH 17/42] Call build.ps1 from AppVeyor --- appveyor.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 83110722..6e8b16f6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,10 +3,6 @@ configuration: - Release platform: Any CPU build_script: -- build.bat -test_script: -- run-tests.bat -after_test: -- push-to-nuget.bat %appveyor_build_version% %nugetApiKey% +- build.ps1 cache: - '%USERPROFILE%\.nuget\packages' \ No newline at end of file From 92f492f0ad37d212f4f9307be96d9e25bf0dfac5 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Wed, 1 Feb 2017 20:51:59 +0000 Subject: [PATCH 18/42] Call build.ps1 in same manner as mr binary mash --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 6e8b16f6..b5cd7c0c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,6 +3,6 @@ configuration: - Release platform: Any CPU build_script: -- build.ps1 +- ./build.ps1 cache: - '%USERPROFILE%\.nuget\packages' \ No newline at end of file From 074ae4d609ff785aa79e02d45d0572848bc1c82f Mon Sep 17 00:00:00 2001 From: TomPallister Date: Wed, 1 Feb 2017 22:30:28 +0000 Subject: [PATCH 19/42] 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 fe4e5baf..100ee6f0 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 00000000..065ae2ac --- /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 00000000..2bb8b966 --- /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 00000000..2fd15ee7 --- /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 20/42] 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 85c3e097..f2c479df 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -23,6 +23,8 @@ DownstreamSchemeNullOrEmptyError, DownstreamHostNullOrEmptyError, ServicesAreNullError, - ServicesAreEmptyError + ServicesAreEmptyError, + UnableToFindServiceProviderError, + UnableToFindLoadBalancerError } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs index 2bb8b966..12c040c0 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -6,7 +6,7 @@ using Ocelot.Responses; namespace Ocelot.LoadBalancer.LoadBalancers { - public class LoadBalancerHouse + public class LoadBalancerHouse : ILoadBalancerHouse { private readonly Dictionary _loadBalancers; @@ -17,7 +17,17 @@ namespace Ocelot.LoadBalancer.LoadBalancers 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 00000000..3dd3ede4 --- /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 00000000..0f85f528 --- /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 00000000..63d14dce --- /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 00000000..b8ed1a47 --- /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 2fd15ee7..31a7bd37 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -38,7 +38,7 @@ namespace Ocelot.UnitTests.LoadBalancer 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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 @@ namespace Ocelot.UnitTests.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 00000000..9443c113 --- /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 7fc92da01298f36206af0703ccb970c01655abce Mon Sep 17 00:00:00 2001 From: "tom.pallister" Date: Thu, 2 Feb 2017 15:53:02 +0000 Subject: [PATCH 21/42] Updated release script to use correct target name --- build-and-release-unstable.ps1 | 2 +- test/Ocelot.AcceptanceTests/configuration.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build-and-release-unstable.ps1 b/build-and-release-unstable.ps1 index 9a29f95f..51c6f0d5 100644 --- a/build-and-release-unstable.ps1 +++ b/build-and-release-unstable.ps1 @@ -1 +1 @@ -./build.ps1 -target build-full \ No newline at end of file +./build.ps1 -target BuildAndReleaseUnstable \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index f28abefe..cdcb1624 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"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 +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Address":null}}} \ No newline at end of file From 07ca7989b012366436a4e90ccdbe32bad9a36472 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Thu, 2 Feb 2017 21:34:15 +0000 Subject: [PATCH 22/42] 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 827dcbad..61c61fae 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -27,13 +27,19 @@ namespace Ocelot.Configuration.Creator 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 @@ namespace Ocelot.Configuration.Creator 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 9f40b009..86564f92 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -23,11 +23,13 @@ using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Headers; using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Infrastructure.RequestData; +using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Logging; using Ocelot.QueryStrings; using Ocelot.Request.Builder; using Ocelot.Requester; using Ocelot.Responder; +using Ocelot.ServiceDiscovery; namespace Ocelot.DependencyInjection { @@ -59,6 +61,10 @@ namespace Ocelot.DependencyInjection { 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 930a5211..55089cde 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 d5203062..7e11df39 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 5c66e98a..e762186d 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -14,37 +14,30 @@ namespace Ocelot.LoadBalancer.Middleware { 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 @@ namespace Ocelot.LoadBalancer.Middleware { 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 00000000..52d47bdd --- /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 0bb51040..b8937108 100644 --- a/src/Ocelot/Middleware/OcelotMiddleware.cs +++ b/src/Ocelot/Middleware/OcelotMiddleware.cs @@ -3,6 +3,7 @@ using System.Net.Http; using Ocelot.DownstreamRouteFinder; using Ocelot.Errors; using Ocelot.Infrastructure.RequestData; +using Ocelot.Values; namespace Ocelot.Middleware { @@ -69,6 +70,20 @@ namespace Ocelot.Middleware } } + public HostAndPort HostAndPort + { + get + { + var hostAndPort = _requestScopedDataRepository.Get("HostAndPort"); + 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 67b84c69..62c55f53 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 0f85f528..00000000 --- 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 63d14dce..00000000 --- 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 fc80a478..b6e28033 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -8,6 +8,7 @@ using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; +using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; @@ -24,6 +25,8 @@ namespace Ocelot.UnitTests.Configuration 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 @@ namespace Ocelot.UnitTests.Configuration _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 c4eb736b..7361c726 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -14,12 +14,12 @@ namespace Ocelot.UnitTests.LoadBalancer 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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 00000000..ed6d30c0 --- /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 9443c113..00000000 --- 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 23/42] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 249a99a3..d96b9868 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 24/42] 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 00000000..04731adf --- /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 86564f92..1615a69b 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -64,7 +64,6 @@ namespace Ocelot.DependencyInjection 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 e762186d..0ef74324 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -29,7 +29,7 @@ namespace Ocelot.LoadBalancer.Middleware 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 ed6d30c0..7f34c903 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -27,6 +27,7 @@ namespace Ocelot.UnitTests.LoadBalancer 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 @@ namespace Ocelot.UnitTests.LoadBalancer _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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 25/42] 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 b6e28033..b1893b5d 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -27,6 +27,7 @@ namespace Ocelot.UnitTests.Configuration private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator; private readonly Mock _loadBalancerFactory; private readonly Mock _loadBalancerHouse; + private readonly Mock _loadBalancer; public FileConfigurationCreatorTests() { @@ -34,11 +35,39 @@ namespace Ocelot.UnitTests.Configuration _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 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } + [Fact] public void should_use_downstream_scheme() { this.Given(x => x.GivenTheConfigIs(new FileConfiguration @@ -580,5 +610,24 @@ namespace Ocelot.UnitTests.Configuration } } + + 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 7361c726..d645b1b6 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -15,13 +15,22 @@ namespace Ocelot.UnitTests.LoadBalancer 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 @@ namespace Ocelot.UnitTests.LoadBalancer .Build(); this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => x.GivenTheServiceProviderFactoryReturns()) .When(x => x.WhenIGetTheLoadBalancer()) .Then(x => x.ThenTheLoadBalancerIsReturned()) .BDDfy(); @@ -42,6 +52,7 @@ namespace Ocelot.UnitTests.LoadBalancer .Build(); this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => x.GivenTheServiceProviderFactoryReturns()) .When(x => x.WhenIGetTheLoadBalancer()) .Then(x => x.ThenTheLoadBalancerIsReturned()) .BDDfy(); @@ -55,6 +66,7 @@ namespace Ocelot.UnitTests.LoadBalancer .Build(); this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => x.GivenTheServiceProviderFactoryReturns()) .When(x => x.WhenIGetTheLoadBalancer()) .Then(x => x.ThenTheLoadBalancerIsReturned()) .BDDfy(); @@ -68,6 +80,7 @@ namespace Ocelot.UnitTests.LoadBalancer .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 26/42] 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 9579330c..e12b1e4b 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 @@ namespace Ocelot.Configuration.Builder 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 @@ namespace Ocelot.Configuration.Builder _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 61c61fae..e9ceb41a 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -104,6 +104,9 @@ namespace Ocelot.Configuration.Creator && !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 @@ namespace Ocelot.Configuration.Creator 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 @@ namespace Ocelot.Configuration.Creator 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 5cbaaebb..987f5be7 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -11,8 +11,9 @@ namespace Ocelot.Configuration 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 @@ namespace Ocelot.Configuration 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 bca20466..80365074 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -47,15 +47,12 @@ namespace Ocelot.DownstreamUrlCreator.Middleware 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 @@ namespace Ocelot.DownstreamUrlCreator.Middleware 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 0ef74324..99bf5167 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -31,26 +31,32 @@ namespace Ocelot.LoadBalancer.Middleware { _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 52d47bdd..0d0224b8 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 dfa3b3f4..b0cd3f7e 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 @@ namespace Ocelot.Middleware // Now we can run any query string transformation logic builder.UseQueryStringBuilderMiddleware(); + // Get the load balancer for this request + builder.UseLoadBalancingMiddleware(); + // This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used builder.UseDownstreamUrlCreatorMiddleware(); diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 5581a32e..a01677b2 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -36,6 +36,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator private HttpResponseMessage _result; private OkResponse _downstreamPath; private OkResponse _downstreamUrl; + private HostAndPort _hostAndPort; public DownstreamUrlCreatorMiddlewareTests() { @@ -69,14 +70,25 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator [Fact] public void should_call_dependencies_correctly() { + var hostAndPort = new HostAndPort("127.0.0.1", 80); + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), 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 7f34c903..93c63884 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -54,7 +54,7 @@ namespace Ocelot.UnitTests.LoadBalancer .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 27/42] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d96b9868..2e57bd6c 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 28/42] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e57bd6c..ae1ef033 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 29/42] fixed failing tests --- .../Creator/FileOcelotConfigurationCreator.cs | 34 +++++++++++-------- .../ServiceCollectionExtensions.cs | 4 +-- .../FileConfigurationCreatorTests.cs | 2 ++ 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index e9ceb41a..2bbc6705 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -108,7 +108,7 @@ namespace Ocelot.Configuration.Creator var loadBalancerKey = $"{fileReRoute.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}"; ReRoute reRoute; - + if (isAuthenticated) { var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider, @@ -120,24 +120,30 @@ namespace Ocelot.Configuration.Creator 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); - } - - 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); + } + 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, + globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, + 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 1615a69b..839a825b 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -61,8 +61,8 @@ namespace Ocelot.DependencyInjection { 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 b1893b5d..65a61240 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -410,6 +410,7 @@ namespace Ocelot.UnitTests.Configuration })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheConfigHeaderExtractorReturns(new ClaimToThing("CustomerId", "CustomerId", "", 0))) + .And(x => x.GivenTheLoadBalancerFactoryReturns()) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(expected)) .And(x => x.ThenTheAuthenticationOptionsAre(expected)) @@ -464,6 +465,7 @@ namespace Ocelot.UnitTests.Configuration } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheLoadBalancerFactoryReturns()) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(expected)) .And(x => x.ThenTheAuthenticationOptionsAre(expected)) From 9828c3b427afe744f7f60dcf6ddf2c49bf559feb Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 3 Feb 2017 22:50:57 +0000 Subject: [PATCH 30/42] 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 1d798d74..26b45b25 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 987f5be7..d9fe60c9 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -11,7 +11,8 @@ namespace Ocelot.Configuration 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 839a825b..0a6bd42c 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -61,7 +61,7 @@ namespace Ocelot.DependencyInjection { 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 f2c479df..d24988b9 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -24,7 +24,7 @@ DownstreamHostNullOrEmptyError, ServicesAreNullError, ServicesAreEmptyError, - UnableToFindServiceProviderError, + UnableToFindServiceDiscoveryProviderError, UnableToFindLoadBalancerError } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs index 7e11df39..082f3b61 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 b207f772..f1045be3 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 60e428c8..2732b5e3 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceProvider.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs @@ -3,7 +3,7 @@ using Ocelot.Values; 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 00000000..fe2acaa8 --- /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 62c55f53..00000000 --- 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 00000000..cb06e99c --- /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 00000000..70638aaa --- /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 be3b8b8c..00000000 --- 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 00000000..163e63ef --- /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 b8ed1a47..00000000 --- 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 00000000..5994b86b --- /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 c2bd7ee7..666c3256 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -5,6 +5,7 @@ using System.Linq; 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 @@ namespace Ocelot.AcceptanceTests _response = _ocelotClient.GetAsync(url).Result; } + public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) + { + var tasks = new Task[times]; + + for (int i = 0; i < times; i++) + { + 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 d645b1b6..e8e0210b 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -14,20 +14,20 @@ namespace Ocelot.UnitTests.LoadBalancer 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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 9bcc2672..82e5cb73 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 @@ namespace Ocelot.UnitTests.ServiceDiscovery .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 31/42] added cake log back in --- build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cake b/build.cake index 26b45b25..1a1f005d 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 32/42] 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 5994b86b..b2134a70 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -27,8 +27,8 @@ namespace Ocelot.AcceptanceTests 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 33/42] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae1ef033..cb5e8bd5 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 34/42] 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 00000000..af5c4ddc --- /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 cb06e99c..e8701418 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -7,6 +7,11 @@ namespace Ocelot.ServiceDiscovery { 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 @@ namespace Ocelot.ServiceDiscovery 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 8d259469..6ce4ffbb 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 0aa730be..ce802efb 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 17f35a3c..2e5f9ee8 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 5f7a4987..061a2223 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 3ae09ccb..cf67f9bd 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 82e5cb73..97fb265a 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -27,6 +27,17 @@ namespace Ocelot.UnitTests.ServiceDiscovery .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 ab3e6cb1..3151ac57 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 35/42] 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 e12b1e4b..caa09d3f 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -34,6 +34,8 @@ namespace Ocelot.Configuration.Builder private string _downstreamHost; private int _dsPort; private string _loadBalancer; + private string _serviceProviderHost; + private int _serviceProviderPort; public ReRouteBuilder() { @@ -206,14 +208,25 @@ namespace Ocelot.Configuration.Builder 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 2bbc6705..703239d0 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.Parser; using Ocelot.Configuration.Validator; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; -using Ocelot.ServiceDiscovery; using Ocelot.Utilities; using Ocelot.Values; @@ -46,9 +46,9 @@ namespace Ocelot.Configuration.Creator _logger = logger; } - public Response Create() + public async Task> Create() { - var config = SetUpConfiguration(); + var config = await SetUpConfiguration(); return new OkResponse(config); } @@ -57,7 +57,7 @@ namespace Ocelot.Configuration.Creator /// This method is meant to be tempoary to convert a config to an ocelot config...probably wont keep this but we will see /// will need a refactor at some point as its crap /// - private IOcelotConfiguration SetUpConfiguration() + private async Task SetUpConfiguration() { var response = _configurationValidator.IsValid(_options.Value); @@ -77,14 +77,14 @@ namespace Ocelot.Configuration.Creator foreach (var reRoute in _options.Value.ReRoutes) { - var ocelotReRoute = SetUpReRoute(reRoute, _options.Value.GlobalConfiguration); + var ocelotReRoute = await SetUpReRoute(reRoute, _options.Value.GlobalConfiguration); reRoutes.Add(ocelotReRoute); } return new OcelotConfiguration(reRoutes); } - private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + private async Task SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) { var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); @@ -101,7 +101,6 @@ namespace Ocelot.Configuration.Creator : 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 @@ namespace Ocelot.Configuration.Creator 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 @@ namespace Ocelot.Configuration.Creator 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 @@ namespace Ocelot.Configuration.Creator 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 6cc7c2e8..7547d91f 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 47efc6df..2f26b6ea 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 30ded2e9..3256e44a 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 4b6c5fd2..80fd5697 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 @@ namespace Ocelot.Configuration.Provider _creator = creator; } - public Response Get() + public async Task> Get() { var repoConfig = _repo.Get(); @@ -30,7 +31,7 @@ namespace Ocelot.Configuration.Provider if (repoConfig.Data == null) { - var creatorConfig = _creator.Create(); + var creatorConfig = await _creator.Create(); if (creatorConfig.IsError) { diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index d9fe60c9..278d0746 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 @@ namespace Ocelot.Configuration ?? 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 @@ namespace Ocelot.Configuration public string RequestIdKey { get; private set; } public bool IsCached { get; private set; } public CacheOptions FileCacheOptions { get; private set; } - public string ServiceName { get; private set;} - public bool UseServiceDiscovery { get; private set;} - public string ServiceDiscoveryProvider { get; private set;} - public string ServiceDiscoveryAddress { get; private set;} public 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 70638aaa..d471a9e5 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 752da281..eacd6912 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 @@ namespace Ocelot.DownstreamRouteFinder.Finder _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 e351ab2f..7ae3ff79 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 f445b46b..e88bfde8 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -34,7 +34,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware _logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); - var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method); + var downstreamRoute = await _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method); if (downstreamRoute.IsError) { diff --git a/src/Ocelot/Infrastructure/Extensions/StringExtensions.cs b/src/Ocelot/Infrastructure/Extensions/StringExtensions.cs new file mode 100644 index 00000000..d7458381 --- /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 100ee6f0..aa2a8f02 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 55089cde..19fdf3eb 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 4799ab12..38984567 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 082f3b61..08e45d2b 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 @@ namespace Ocelot.LoadBalancer.LoadBalancers _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 2788656a..f654dca8 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 @@ namespace Ocelot.LoadBalancer.LoadBalancers _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 1ffb46ce..0bb1f829 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 @@ namespace Ocelot.LoadBalancer.LoadBalancers _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 99bf5167..d2e9ee8a 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -37,7 +37,7 @@ namespace Ocelot.LoadBalancer.Middleware //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 f1045be3..f6280d7b 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 00000000..8d496a85 --- /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 af5c4ddc..c74c90f0 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) { - throw new NotImplementedException(); + 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) + { + 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 2732b5e3..2c643d4b 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 fe2acaa8..6c6c3d4c 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 e8701418..00622190 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 @@ namespace Ocelot.ServiceDiscovery { 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 104fbc09..0ba12b79 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 b2134a70..06bee686 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -29,7 +29,7 @@ namespace Ocelot.AcceptanceTests 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 @@ namespace Ocelot.AcceptanceTests { ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() { - Provider = "Consul" + Provider = "Consul", + Host = "localhost" } } }; diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index 713b93f1..d7db55c7 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 65a61240..e1da7de0 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -151,7 +151,7 @@ namespace Ocelot.UnitTests.Configuration ServiceDiscoveryProvider = new FileServiceDiscoveryProvider { Provider = "consul", - Address = "127.0.0.1" + Host = "127.0.0.1" } } })) @@ -579,7 +579,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenICreateTheConfig() { - _config = _ocelotConfigurationCreator.Create(); + _config = _ocelotConfigurationCreator.Create().Result; } private void ThenTheReRoutesAre(List expectedReRoutes) @@ -617,7 +617,7 @@ namespace Ocelot.UnitTests.Configuration { _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 56fb6487..98e01293 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs @@ -81,7 +81,7 @@ namespace Ocelot.UnitTests.Configuration { _creator .Setup(x => x.Create()) - .Returns(config); + .ReturnsAsync(config); } private void GivenTheRepoReturns(Response config) @@ -93,7 +93,7 @@ namespace Ocelot.UnitTests.Configuration 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 0d5a6d48..a80a3168 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -84,7 +84,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _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 c0afca42..dc9978b3 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -159,7 +159,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _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 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder 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 a8617b22..47b3a7d0 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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 e8e0210b..d030eb99 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -99,7 +99,7 @@ namespace Ocelot.UnitTests.LoadBalancer 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 31a7bd37..471e4b70 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 @@ namespace Ocelot.UnitTests.LoadBalancer class FakeLoadBalancer : ILoadBalancer { - public Response Lease() + public Task> Lease() { throw new NotImplementedException(); } @@ -121,7 +122,7 @@ namespace Ocelot.UnitTests.LoadBalancer 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 93c63884..ab6a59a8 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -82,7 +82,7 @@ namespace Ocelot.UnitTests.LoadBalancer _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 a2fd2be8..ac89a6d0 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs @@ -21,7 +21,7 @@ namespace Ocelot.UnitTests.LoadBalancer 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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 cb934af8..f2ef5367 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs @@ -19,9 +19,9 @@ namespace Ocelot.UnitTests.LoadBalancer { _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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 182dd514..f1e732e7 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs @@ -21,7 +21,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery 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 @@ namespace Ocelot.UnitTests.ServiceDiscovery 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 97fb265a..7dae5e47 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 @@ namespace Ocelot.UnitTests.ServiceDiscovery [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 @@ namespace Ocelot.UnitTests.ServiceDiscovery [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 61f7c975..87425329 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs @@ -50,13 +50,13 @@ namespace Ocelot.UnitTests.ServiceDiscovery 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 36/42] 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 38984567..bfb4817b 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs @@ -13,6 +13,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers 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 @@ namespace Ocelot.LoadBalancer.LoadBalancers 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.Add(replacementLease); + _leases.Remove(matchingLease); + + _leases.Add(replacementLease); + } } return new OkResponse(); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs index 12c040c0..63ac3243 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -32,8 +32,16 @@ namespace Ocelot.LoadBalancer.LoadBalancers 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 b0cd3f7e..352aa501 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 @@ namespace Ocelot.Middleware /// public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder) { + CreateConfiguration(builder); builder.UseOcelot(new OcelotMiddlewareConfiguration()); return builder; } @@ -41,6 +43,8 @@ namespace Ocelot.Middleware /// 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 @@ namespace Ocelot.Middleware return builder; } + private static void CreateConfiguration(IApplicationBuilder builder) + { + var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider)); + + var config = configProvider.Get(); + + if(config == null) + { + throw new Exception("Unable to start Ocelot: configuration was null"); + } + } + private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) { if (middleware != null) diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index 06bee686..d8e5149d 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Net; +using Consul; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -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 @@ namespace Ocelot.AcceptanceTests 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 @@ namespace Ocelot.AcceptanceTests 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 @@ namespace Ocelot.AcceptanceTests { 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,9 @@ namespace Ocelot.AcceptanceTests _fakeConsulBuilder.Start(); } - private void GivenThereIsAServiceRunningOn(string url, int statusCode, int counter) + private void GivenProductServiceOneIsRunning(string url, int statusCode) { - _builder = new WebHostBuilder() + _builderOne = new WebHostBuilder() .UseUrls(url) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) @@ -118,18 +154,40 @@ namespace Ocelot.AcceptanceTests { app.Run(async context => { - counter++; + _counterOne++; context.Response.StatusCode = statusCode; }); }) .Build(); - _builder.Start(); + _builderOne.Start(); + } + + private void GivenProductServiceTwoIsRunning(string url, int statusCode) + { + _builderTwo = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + _counterTwo++; + context.Response.StatusCode = statusCode; + }); + }) + .Build(); + + _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 ce802efb..6784391c 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -28,7 +28,7 @@ { 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 2e5f9ee8..f1aa378b 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 47b3a7d0..3896b68e 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 @@ namespace Ocelot.UnitTests.LoadBalancer 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 37/42] 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 63ac3243..cc6ea73b 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -32,16 +32,14 @@ namespace Ocelot.LoadBalancer.LoadBalancers 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 d8e5149d..2d9d55af 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -19,10 +19,9 @@ namespace Ocelot.AcceptanceTests 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 @@ namespace Ocelot.AcceptanceTests 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 6784391c..ce802efb 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -28,7 +28,7 @@ { 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 3896b68e..f5ea4738 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs @@ -38,20 +38,13 @@ namespace Ocelot.UnitTests.LoadBalancer _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(); - } + tasks[i] = LeaseDelayAndRelease(); + } - Task.WaitAll(tasks); - } - catch (System.Exception exception) - { - Console.WriteLine(exception.StackTrace); - throw; - } + 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 38/42] 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 249a99a3..9d93546d 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 2d9d55af..1bc6723a 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -22,6 +22,7 @@ namespace Ocelot.AcceptanceTests private readonly List _serviceEntries; private int _counterOne; private int _counterTwo; + private static readonly object _syncLock = new object(); public ServiceDiscoveryTests() { @@ -152,9 +153,15 @@ namespace Ocelot.AcceptanceTests .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 @@ namespace Ocelot.AcceptanceTests .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 666c3256..5d015994 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -160,12 +160,21 @@ namespace Ocelot.AcceptanceTests 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 39/42] 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 1bc6723a..ef7e0dd7 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -37,8 +37,6 @@ namespace Ocelot.AcceptanceTests 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 @@ namespace Ocelot.AcceptanceTests { 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 @@ namespace Ocelot.AcceptanceTests { app.Run(async context => { - var response = string.Empty; - lock (_syncLock) + try { - _counterTwo++; - response = _counterTwo.ToString(); + var response = string.Empty; + lock (_syncLock) + { + _counterTwo++; + response = _counterTwo.ToString(); + } + + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(response); } - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(response); + catch (System.Exception exception) + { + await context.Response.WriteAsync(exception.StackTrace); + } + }); }) .Build(); diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 5d015994..9b5faa04 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Threading; using System.Threading.Tasks; using CacheManager.Core; using Microsoft.AspNetCore.Hosting; @@ -30,6 +31,12 @@ namespace Ocelot.AcceptanceTests private BearerToken _token; public HttpClient OcelotClient => _ocelotClient; public string RequestIdKey = "OcRequestId"; + private Random _random; + + public Steps() + { + _random = new Random(); + } public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) { @@ -162,6 +169,7 @@ namespace Ocelot.AcceptanceTests { var urlCopy = url; tasks[i] = GetForServiceDiscoveryTest(urlCopy); + Thread.Sleep(_random.Next(40,60)); } Task.WaitAll(tasks); @@ -171,7 +179,7 @@ namespace Ocelot.AcceptanceTests { 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 40/42] 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 d2e9ee8a..7d09ea3a 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -31,16 +31,18 @@ namespace Ocelot.LoadBalancer.Middleware { _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 @@ namespace Ocelot.LoadBalancer.Middleware { 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 ab6a59a8..5d750f83 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; +using Ocelot.Errors; using Ocelot.Infrastructure.RequestData; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.Middleware; @@ -31,6 +32,8 @@ namespace Ocelot.UnitTests.LoadBalancer private OkResponse _request; private OkResponse _downstreamUrl; private OkResponse _downstreamRoute; + private ErrorResponse _getLoadBalancerHouseError; + private ErrorResponse _getHostAndPortError; public LoadBalancerMiddlewareTests() { @@ -77,6 +80,45 @@ namespace Ocelot.UnitTests.LoadBalancer .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 @@ namespace Ocelot.UnitTests.LoadBalancer .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 41/42] 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 fc3ab200..6e6b511e 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -41,13 +41,13 @@ namespace Ocelot.Errors.Middleware var message = CreateMessage(context, e); _logger.LogError(message, e); - await SetInternalServerErrorOnResponse(context); + SetInternalServerErrorOnResponse(context); } _logger.LogDebug("ocelot pipeline finished"); } - private async Task SetInternalServerErrorOnResponse(HttpContext context) + private void SetInternalServerErrorOnResponse(HttpContext context) { context.Response.OnStarting(x => { diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs index aa2a8f02..73d25d48 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 bfb4817b..cd56ef91 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs @@ -53,7 +53,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers } } - public Response Release(HostAndPort hostAndPort) + public void Release(HostAndPort hostAndPort) { lock(_syncLock) { @@ -69,8 +69,6 @@ namespace Ocelot.LoadBalancer.LoadBalancers _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 f654dca8..bf66950b 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs @@ -17,13 +17,12 @@ namespace Ocelot.LoadBalancer.LoadBalancers 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 0bb1f829..37efe22f 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs @@ -22,14 +22,13 @@ namespace Ocelot.LoadBalancer.LoadBalancers _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 7d09ea3a..ce37f828 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -31,14 +31,14 @@ namespace Ocelot.LoadBalancer.Middleware { _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 @@ namespace Ocelot.LoadBalancer.Middleware { 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 8b61e13b..40b60c30 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -24,7 +24,7 @@ namespace Ocelot.Responder _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 @@ namespace Ocelot.Responder { await stream.CopyToAsync(context.Response.Body); } - return new OkResponse(); } private static void AddHeaderIfDoesntExist(HttpContext context, KeyValuePair> httpResponseHeader) @@ -67,14 +66,13 @@ namespace Ocelot.Responder } } - public async Task 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 5292f4df..f885c673 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 06da92dc..6bce4ac6 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs @@ -46,34 +46,27 @@ namespace Ocelot.Responder.Middleware _logger.LogDebug("received errors setting error response"); - await SetErrorResponse(context, errors); + SetErrorResponse(context, errors); } else { _logger.LogDebug("no pipeline error, setting response"); - var setResponse = await _responder.SetResponseOnHttpContext(context, HttpResponseMessage); - - if (setResponse.IsError) - { - _logger.LogDebug("error setting response, returning error to client"); - - await SetErrorResponse(context, setResponse.Errors); - } + await _responder.SetResponseOnHttpContext(context, HttpResponseMessage); } } - private async Task SetErrorResponse(HttpContext context, List 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 f6280d7b..98e7b0f6 100644 --- a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs +++ b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs @@ -15,7 +15,7 @@ namespace Ocelot.ServiceDiscovery 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 f5ea4738..07002ce3 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs @@ -51,7 +51,7 @@ namespace Ocelot.UnitTests.LoadBalancer { 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 471e4b70..ac24b490 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -114,7 +114,7 @@ namespace Ocelot.UnitTests.LoadBalancer throw new NotImplementedException(); } - public Response Release(HostAndPort hostAndPort) + public void Release(HostAndPort hostAndPort) { throw new NotImplementedException(); } @@ -127,7 +127,7 @@ namespace Ocelot.UnitTests.LoadBalancer 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 5d750f83..5a9eec87 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -170,6 +170,15 @@ namespace Ocelot.UnitTests.LoadBalancer .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 b643028e..09a5c22c 100644 --- a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs @@ -62,19 +62,11 @@ namespace Ocelot.UnitTests.Responder { this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage())) .And(x => x.GivenThereAreNoPipelineErrors()) - .And(x => x.GivenTheResponderReturns()) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenThereAreNoErrors()) .BDDfy(); } - private void GivenTheResponderReturns() - { - _responder - .Setup(x => x.SetResponseOnHttpContext(It.IsAny(), 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 42/42] 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 9d93546d..9d46282f 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 ad020469..b6db84a2 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