diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index bad1e6c5..4790afc5 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -64,7 +64,6 @@ Here is an example ReRoute configuration, You don't need to set all of these thi "UseCookieContainer": true, "UseTracing": true }, - "UseServiceDiscovery": false, "DangerousAcceptAnyServerCertificateValidator": false } diff --git a/docs/features/loadbalancer.rst b/docs/features/loadbalancer.rst index 1aa65bed..3c05a913 100644 --- a/docs/features/loadbalancer.rst +++ b/docs/features/loadbalancer.rst @@ -1,111 +1,330 @@ -Load Balancer -============= - -Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively. - -The type of load balancer available are: - - LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's. - - RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's. - - NoLoadBalancer - takes the first available service from config or service discovery. - - CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below. - -You must choose in your configuration which load balancer to use. - -Configuration -^^^^^^^^^^^^^ - -The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeastConnection load balancer. This is the simplest way to get load balancing set up. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.1.10", - "Port": 5000, - }, - { - "Host": "10.0.1.11", - "Port": 5000, - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "LoadBalancerOptions": { - "Type": "LeastConnection" - }, - "UpstreamHttpMethod": [ "Put", "Delete" ] - } - - -Service Discovery -^^^^^^^^^^^^^^^^^ - -The following shows how to set up a ReRoute using service discovery then select the LeastConnection load balancer. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Put" ], - "ServiceName": "product", - "LoadBalancerOptions": { - "Type": "LeastConnection" - }, - "UseServiceDiscovery": true - } - -When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the -service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added. - -CookieStickySessions -^^^^^^^^^^^^^^^^^^^^ - -I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream -servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each -time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 `_ -though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have! - -In order to set up CookieStickySessions load balancer you need to do something like the following. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.1.10", - "Port": 5000, - }, - { - "Host": "10.0.1.11", - "Port": 5000, - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "LoadBalancerOptions": { - "Type": "CookieStickySessions", - "Key": "ASP.NET_SessionId", - "Expiry": 1800000 - }, - "UpstreamHttpMethod": [ "Put", "Delete" ] - } - -The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you -wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this -refreshes on every request which is meant to mimick how sessions work usually. - -If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there -subsequent requests. This means the sessions will be stuck across ReRoutes. - -Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul -and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the -moment but could be changed. + +Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively. + +The type of load balancer available are: + + LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's. + + RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's. + + NoLoadBalancer - takes the first available service from config or service discovery. + + CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below. + +You must choose in your configuration which load balancer to use. + +Configuration +^^^^^^^^^^^^^ + +The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + + +Service Discovery +^^^^^^^^^^^^^^^^^ + +The following shows how to set up a ReRoute using service discovery then select the LeadConnection load balancer. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put" ], + "ServiceName": "product", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + } + +When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the +service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added. + +CookieStickySessions +^^^^^^^^^^^^^^^^^^^^ + +I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream +servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each +time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 `_ +though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have! + +In order to set up CookieStickySessions load balancer you need to do something like the following. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancerOptions": { + "Type": "CookieStickySessions", + "Key": "ASP.NET_SessionId", + "Expiry": 1800000 + }, + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + +The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you +wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this +refreshes on every request which is meant to mimick how sessions work usually. + +If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there +subsequent requests. This means the sessions will be stuck across ReRoutes. + +Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul +and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the +moment but could be changed. +||||||| merged common ancestors +Load Balancer +============= + +Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively. + +The type of load balancer available are: + + LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's. + + RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's. + + NoLoadBalancer - takes the first available service from config or service discovery. + + CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below. + +You must choose in your configuration which load balancer to use. + +Configuration +^^^^^^^^^^^^^ + +The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + + +Service Discovery +^^^^^^^^^^^^^^^^^ + +The following shows how to set up a ReRoute using service discovery then select the LeadConnection load balancer. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put" ], + "ServiceName": "product", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + } + +When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the +service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added. + +CookieStickySessions +^^^^^^^^^^^^^^^^^^^^ + +I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream +servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each +time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 `_ +though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have! + +In order to set up CookieStickySessions load balancer you need to do something like the following. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancerOptions": { + "Type": "CookieStickySessions", + "Key": "ASP.NET_SessionId", + "Expiry": 1800000 + }, + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + +The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you +wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this +refreshes on every request which is meant to mimick how sessions work usually. + +If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there +subsequent requests. This means the sessions will be stuck across ReRoutes. + +Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul +and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the +moment but could be changed. +======= +Load Balancer +============= + +Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively. + +The type of load balancer available are: + + LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's. + + RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's. + + NoLoadBalancer - takes the first available service from config or service discovery. + + CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below. + +You must choose in your configuration which load balancer to use. + +Configuration +^^^^^^^^^^^^^ + +The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeastConnection load balancer. This is the simplest way to get load balancing set up. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + + +Service Discovery +^^^^^^^^^^^^^^^^^ + +The following shows how to set up a ReRoute using service discovery then select the LeastConnection load balancer. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put" ], + "ServiceName": "product", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + } + +When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the +service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added. + +CookieStickySessions +^^^^^^^^^^^^^^^^^^^^ + +I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream +servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each +time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 `_ +though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have! + +In order to set up CookieStickySessions load balancer you need to do something like the following. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancerOptions": { + "Type": "CookieStickySessions", + "Key": "ASP.NET_SessionId", + "Expiry": 1800000 + }, + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + +The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you +wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this +refreshes on every request which is meant to mimick how sessions work usually. + +If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there +subsequent requests. This means the sessions will be stuck across ReRoutes. + +Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul +and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the +moment but could be changed. diff --git a/docs/features/routing.rst b/docs/features/routing.rst index c27de9a6..2ac2230e 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -211,7 +211,6 @@ Ocelot allows you to specify a querystring as part of the DownstreamPathTemplate } ], "GlobalConfiguration": { - "UseServiceDiscovery": false } } @@ -239,9 +238,8 @@ Ocelot will also allow you to put query string parameters in the UpstreamPathTem } ], "GlobalConfiguration": { - "UseServiceDiscovery": false } } In this example Ocelot will only match requests that have a matching url path and the querystring starts with unitId=something. You can have other queries after this -but you must start with the matching parameter. Also Ocelot will swap the {unitId} parameter from the query string and use it in the downstream request path. \ No newline at end of file +but you must start with the matching parameter. Also Ocelot will swap the {unitId} parameter from the query string and use it in the downstream request path. diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index c24a2e39..5a1f85ac 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -36,7 +36,7 @@ will be used. In the future we can add a feature that allows ReRoute specfic configuration. In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the -ServiceName, UseServiceDiscovery and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin +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. .. code-block:: json @@ -50,7 +50,6 @@ and LeastConnection algorithm you can use. If no load balancer is specified Ocel "LoadBalancerOptions": { "Type": "LeastConnection" }, - "UseServiceDiscovery": true } When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. @@ -105,7 +104,8 @@ If you are using ACL with Consul Ocelot supports adding the X-Consul-Token heade "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8500, - "Token": "footoken" + "Token": "footoken", + "Type": "Consul" } Ocelot will add this token to the consul client that it uses to make requests and that is then used for every request. @@ -232,6 +232,7 @@ Ocelot also allows you to set DynamicReRoutes which lets you set rate limiting r "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8523, + "Type": "Consul" }, "RateLimitOptions": { "ClientIdHeader": "ClientId", diff --git a/docs/features/servicefabric.rst b/docs/features/servicefabric.rst index d3faa40f..0bc3ee1b 100644 --- a/docs/features/servicefabric.rst +++ b/docs/features/servicefabric.rst @@ -4,7 +4,7 @@ Service Fabric If you have services deployed in Service Fabric you will normally use the naming service to access them. The following example shows how to set up a ReRoute that will work in Service Fabric. The most important thing is the ServiceName which is made up of the -Service Fabric application name then the specific service name. We also need to set UseServiceDiscovery as true and set up the ServiceDiscoveryProvider in +Service Fabric application name then the specific service name. We also need to set up the ServiceDiscoveryProvider in GlobalConfiguration. The example here shows a typical configuration. It assumes service fabric is running on localhost and that the naming service is on port 19081. The example below is taken from the samples folder so please check it if this doesnt make sense! @@ -21,7 +21,6 @@ The example below is taken from the samples folder so please check it if this do ], "DownstreamScheme": "http", "ServiceName": "OcelotServiceApplication/OcelotApplicationService", - "UseServiceDiscovery" : true } ], "GlobalConfiguration": { diff --git a/samples/OcelotEureka/ApiGateway/ocelot.json b/samples/OcelotEureka/ApiGateway/ocelot.json index b8694ddd..963160f9 100644 --- a/samples/OcelotEureka/ApiGateway/ocelot.json +++ b/samples/OcelotEureka/ApiGateway/ocelot.json @@ -4,7 +4,6 @@ "DownstreamPathTemplate": "/api/Category", "DownstreamScheme": "http", "UpstreamPathTemplate": "/Category", - "UseServiceDiscovery": true, "ServiceName": "ncore-rat", "UpstreamHttpMethod": [ "Get" ], "QoSOptions": { diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json index dce3d55e..8a679243 100644 --- a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json @@ -7,8 +7,7 @@ "Get" ], "DownstreamScheme": "http", - "ServiceName": "OcelotServiceApplication/OcelotApplicationService", - "UseServiceDiscovery" : true + "ServiceName": "OcelotServiceApplication/OcelotApplicationService" } ], "GlobalConfiguration": { diff --git a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs index 89bd7a41..6aea0702 100644 --- a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs @@ -224,6 +224,8 @@ namespace Ocelot.Configuration.Creator var lbOptions = CreateLoadBalancerOptions(fileReRoute.LoadBalancerOptions); + var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName); + var reRoute = new DownstreamReRouteBuilder() .WithKey(fileReRoute.Key) .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) @@ -249,7 +251,7 @@ namespace Ocelot.Configuration.Creator .WithRateLimitOptions(rateLimitOption) .WithHttpHandlerOptions(httpHandlerOptions) .WithServiceName(fileReRoute.ServiceName) - .WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery) + .WithUseServiceDiscovery(useServiceDiscovery) .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) .WithUpstreamHost(fileReRoute.UpstreamHost) diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index 8d1ca3f5..a13284ad 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -1,30 +1,30 @@ -namespace Ocelot.Configuration.File -{ - public class FileGlobalConfiguration - { - public FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider(); - RateLimitOptions = new FileRateLimitOptions(); - LoadBalancerOptions = new FileLoadBalancerOptions(); - QoSOptions = new FileQoSOptions(); - HttpHandlerOptions = new FileHttpHandlerOptions(); - } - - public string RequestIdKey { get; set; } - - public FileServiceDiscoveryProvider ServiceDiscoveryProvider { get;set; } - - public FileRateLimitOptions RateLimitOptions { get; set; } - - public FileQoSOptions QoSOptions { get; set; } - - public string BaseUrl { get ;set; } - - public FileLoadBalancerOptions LoadBalancerOptions { get; set; } - - public string DownstreamScheme { get; set; } - - public FileHttpHandlerOptions HttpHandlerOptions { get; set; } - } -} +namespace Ocelot.Configuration.File +{ + public class FileGlobalConfiguration + { + public FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider(); + RateLimitOptions = new FileRateLimitOptions(); + LoadBalancerOptions = new FileLoadBalancerOptions(); + QoSOptions = new FileQoSOptions(); + HttpHandlerOptions = new FileHttpHandlerOptions(); + } + + public string RequestIdKey { get; set; } + + public FileServiceDiscoveryProvider ServiceDiscoveryProvider { get;set; } + + public FileRateLimitOptions RateLimitOptions { get; set; } + + public FileQoSOptions QoSOptions { get; set; } + + public string BaseUrl { get ;set; } + + public FileLoadBalancerOptions LoadBalancerOptions { get; set; } + + public string DownstreamScheme { get; set; } + + public FileHttpHandlerOptions HttpHandlerOptions { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 44f9cd9e..f03156e4 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -43,7 +43,6 @@ namespace Ocelot.Configuration.File public FileRateLimitRule RateLimitOptions { get; set; } public FileAuthenticationOptions AuthenticationOptions { get; set; } public FileHttpHandlerOptions HttpHandlerOptions { get; set; } - public bool UseServiceDiscovery { get;set; } public List DownstreamHostAndPorts {get;set;} public string UpstreamHost { get; set; } public string Key { get;set; } diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index 5e6ac61b..ef380a21 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -11,15 +11,19 @@ namespace Ocelot.Configuration.Validator { using System; using Microsoft.Extensions.DependencyInjection; + using Ocelot.ServiceDiscovery; using Requester; public class FileConfigurationFluentValidator : AbstractValidator, IConfigurationValidator { private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate; - + private readonly List _serviceDiscoveryFinderDelegates; public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, IServiceProvider provider) { _qosDelegatingHandlerDelegate = provider.GetService(); + _serviceDiscoveryFinderDelegates = provider + .GetServices() + .ToList(); RuleFor(configuration => configuration.ReRoutes) .SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider, _qosDelegatingHandlerDelegate)); @@ -31,6 +35,14 @@ namespace Ocelot.Configuration.Validator .Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.ReRoutes)) .WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate"); + RuleForEach(configuration => configuration.ReRoutes) + .Must((config, reRoute) => HaveServiceDiscoveryProviderRegitered(reRoute, config.GlobalConfiguration.ServiceDiscoveryProvider)) + .WithMessage((config, reRoute) => $"Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?"); + + RuleFor(configuration => configuration.GlobalConfiguration.ServiceDiscoveryProvider) + .Must((config) => HaveServiceDiscoveryProviderRegitered(config)) + .WithMessage((config, reRoute) => $"Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?"); + RuleForEach(configuration => configuration.ReRoutes) .Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.Aggregates)) .WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate aggregate"); @@ -48,6 +60,41 @@ namespace Ocelot.Configuration.Validator .WithMessage((config, aggregateReRoute) => $"{nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} contains ReRoute with specific RequestIdKey, this is not possible with Aggregates"); } + private bool HaveServiceDiscoveryProviderRegitered(FileReRoute reRoute, FileServiceDiscoveryProvider serviceDiscoveryProvider) + { + if (string.IsNullOrEmpty(reRoute.ServiceName)) + { + return true; + } + + if (serviceDiscoveryProvider?.Type?.ToLower() == "servicefabric") + { + return true; + } + + return _serviceDiscoveryFinderDelegates.Any(); + } + + private bool HaveServiceDiscoveryProviderRegitered(FileServiceDiscoveryProvider serviceDiscoveryProvider) + { + if(serviceDiscoveryProvider == null) + { + return true; + } + + if (serviceDiscoveryProvider?.Type?.ToLower() == "servicefabric") + { + return true; + } + + if(string.IsNullOrEmpty(serviceDiscoveryProvider.Type)) + { + return true; + } + + return _serviceDiscoveryFinderDelegates.Any(); + } + public async Task> IsValid(FileConfiguration configuration) { var validateResult = await ValidateAsync(configuration); diff --git a/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs b/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs index 74b0934e..a6ea82cb 100644 --- a/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs @@ -55,17 +55,12 @@ .MustAsync(IsSupportedAuthenticationProviders) .WithMessage("{PropertyValue} is unsupported authentication provider"); - When(reRoute => reRoute.UseServiceDiscovery, () => { - RuleFor(r => r.ServiceName).NotEmpty() - .WithMessage("ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!"); - }); - - When(reRoute => !reRoute.UseServiceDiscovery, () => { + When(reRoute => string.IsNullOrEmpty(reRoute.ServiceName), () => { RuleFor(r => r.DownstreamHostAndPorts).NotEmpty() .WithMessage("When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!"); }); - When(reRoute => !reRoute.UseServiceDiscovery, () => { + When(reRoute => string.IsNullOrEmpty(reRoute.ServiceName), () => { RuleFor(reRoute => reRoute.DownstreamHostAndPorts) .SetCollectionValidator(new HostAndPortValidator()); }); diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 81f5019b..02b40c07 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -122,7 +122,7 @@ namespace Ocelot.DownstreamUrlCreator.Middleware private static bool ServiceFabricRequest(DownstreamContext context) { - return context.Configuration.ServiceProviderConfiguration.Type == "ServiceFabric" && context.DownstreamReRoute.UseServiceDiscovery; + return context.Configuration.ServiceProviderConfiguration.Type?.ToLower() == "servicefabric" && context.DownstreamReRoute.UseServiceDiscovery; } } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs index 93082de6..7ca857f5 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs @@ -1,10 +1,11 @@ -using System.Threading.Tasks; -using Ocelot.Configuration; - -namespace Ocelot.LoadBalancer.LoadBalancers +namespace Ocelot.LoadBalancer.LoadBalancers { + using System.Threading.Tasks; + using Ocelot.Configuration; + using Ocelot.Responses; + public interface ILoadBalancerFactory { - Task Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config); + Task> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config); } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs index c9a9cbe8..dce8d405 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Ocelot.Configuration; using Ocelot.Infrastructure; +using Ocelot.Responses; using Ocelot.ServiceDiscovery; namespace Ocelot.LoadBalancer.LoadBalancers @@ -14,22 +15,29 @@ namespace Ocelot.LoadBalancer.LoadBalancers _serviceProviderFactory = serviceProviderFactory; } - public async Task Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config) + public async Task> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config) { - var serviceProvider = _serviceProviderFactory.Get(config, reRoute); + var response = _serviceProviderFactory.Get(config, reRoute); + + if(response.IsError) + { + return new ErrorResponse(response.Errors); + } + + var serviceProvider = response.Data; switch (reRoute.LoadBalancerOptions?.Type) { case nameof(RoundRobin): - return new RoundRobin(async () => await serviceProvider.Get()); + return new OkResponse(new RoundRobin(async () => await serviceProvider.Get())); case nameof(LeastConnection): - return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName); + return new OkResponse(new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName)); case nameof(CookieStickySessions): var loadBalancer = new RoundRobin(async () => await serviceProvider.Get()); var bus = new InMemoryBus(); - return new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs, bus); + return new OkResponse(new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs, bus)); default: - return new NoLoadBalancer(async () => await serviceProvider.Get()); + return new OkResponse(new NoLoadBalancer(async () => await serviceProvider.Get())); } } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs index 8ca3fab1..6d195d4d 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -22,24 +22,36 @@ namespace Ocelot.LoadBalancer.LoadBalancers { try { - if(_loadBalancers.TryGetValue(reRoute.LoadBalancerKey, out var loadBalancer)) + Response result; + + if (_loadBalancers.TryGetValue(reRoute.LoadBalancerKey, out var loadBalancer)) { loadBalancer = _loadBalancers[reRoute.LoadBalancerKey]; - if(reRoute.LoadBalancerOptions.Type != loadBalancer.GetType().Name) + if (reRoute.LoadBalancerOptions.Type != loadBalancer.GetType().Name) { - loadBalancer = await _factory.Get(reRoute, config); + result = await _factory.Get(reRoute, config); + if (result.IsError) + { + return new ErrorResponse(result.Errors); + } + loadBalancer = result.Data; AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer); } return new OkResponse(loadBalancer); } - loadBalancer = await _factory.Get(reRoute, config); + result = await _factory.Get(reRoute, config); + if (result.IsError) + { + return new ErrorResponse(result.Errors); + } + loadBalancer = result.Data; AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer); return new OkResponse(loadBalancer); } - catch(Exception ex) + catch (Exception ex) { return new ErrorResponse(new List() { diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs index 91e9c700..a3c3b18b 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs @@ -1,10 +1,11 @@ -using Ocelot.Configuration; -using Ocelot.ServiceDiscovery.Providers; - -namespace Ocelot.ServiceDiscovery -{ - public interface IServiceDiscoveryProviderFactory - { - IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute); - } -} +namespace Ocelot.ServiceDiscovery +{ + using Ocelot.Configuration; + using Ocelot.Responses; + using Ocelot.ServiceDiscovery.Providers; + + public interface IServiceDiscoveryProviderFactory + { + Response Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute); + } +} diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index 503814e9..1fa8c7c0 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -9,23 +9,22 @@ namespace Ocelot.ServiceDiscovery using System; using System.Linq; using Microsoft.Extensions.DependencyInjection; + using Ocelot.Responses; public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory { private readonly IOcelotLoggerFactory _factory; - private readonly List _delegates; + private readonly ServiceDiscoveryFinderDelegate _delegates; private readonly IServiceProvider _provider; public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IServiceProvider provider) { _factory = factory; _provider = provider; - _delegates = provider - .GetServices() - .ToList(); + _delegates = provider.GetService(); } - public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute) + public Response Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute) { if (reRoute.UseServiceDiscovery) { @@ -37,31 +36,32 @@ namespace Ocelot.ServiceDiscovery foreach (var downstreamAddress in reRoute.DownstreamAddresses) { var service = new Service(reRoute.ServiceName, new ServiceHostAndPort(downstreamAddress.Host, downstreamAddress.Port), string.Empty, string.Empty, new string[0]); - + services.Add(service); } - return new ConfigurationServiceProvider(services); + return new OkResponse(new ConfigurationServiceProvider(services)); } - private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration config, string key) + private Response GetServiceDiscoveryProvider(ServiceProviderConfiguration config, string key) { if (config.Type?.ToLower() == "servicefabric") { var sfConfig = new ServiceFabricConfiguration(config.Host, config.Port, key); - return new ServiceFabricServiceDiscoveryProvider(sfConfig); + return new OkResponse(new ServiceFabricServiceDiscoveryProvider(sfConfig)); } - foreach (var serviceDiscoveryFinderDelegate in _delegates) + if (_delegates != null) { - var provider = serviceDiscoveryFinderDelegate?.Invoke(_provider, config, key); - if (provider != null) + var provider = _delegates?.Invoke(_provider, config, key); + + if(provider.GetType().Name.ToLower() == config.Type.ToLower()) { - return provider; + return new OkResponse(provider); } } - return null; + return new ErrorResponse(new UnableToFindServiceDiscoveryProviderError($"Unable to find service discovery provider for type: {config.Type}")); } } } diff --git a/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs b/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs new file mode 100644 index 00000000..3ac838d2 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs @@ -0,0 +1,11 @@ +namespace Ocelot.ServiceDiscovery +{ + using Ocelot.Errors; + + public class UnableToFindServiceDiscoveryProviderError : Error + { + public UnableToFindServiceDiscoveryProviderError(string message) : base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError) + { + } + } +} diff --git a/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs b/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs index 35029a13..5ba30fa3 100644 --- a/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs +++ b/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs @@ -15,6 +15,79 @@ namespace Ocelot.AcceptanceTests _steps = new Steps(); } + [Fact] + public void should_throw_exception_if_cannot_start_because_service_discovery_provider_specified_in_config_but_no_service_discovery_provider_registered_with_dynamic_re_routes() + { + var invalidConfig = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "consul", + Port = 8500 + } + } + }; + + Exception exception = null; + _steps.GivenThereIsAConfiguration(invalidConfig); + try + { + _steps.GivenOcelotIsRunning(); + } + catch (Exception ex) + { + exception = ex; + } + + exception.ShouldNotBeNull(); + exception.Message.ShouldBe("One or more errors occurred. (Unable to start Ocelot, errors are: Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?)"); + } + + [Fact] + public void should_throw_exception_if_cannot_start_because_service_discovery_provider_specified_in_config_but_no_service_discovery_provider_registered() + { + var invalidConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + ServiceName = "test" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "consul", + Port = 8500 + } + } + }; + + Exception exception = null; + _steps.GivenThereIsAConfiguration(invalidConfig); + try + { + _steps.GivenOcelotIsRunning(); + } + catch (Exception ex) + { + exception = ex; + } + + exception.ShouldNotBeNull(); + exception.Message.ShouldBe("One or more errors occurred. (Unable to start Ocelot, errors are: Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?,Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?)"); + } + [Fact] public void should_throw_exception_if_cannot_start_because_no_qos_delegate_registered_globally() { diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 23cb19dd..d3379aa1 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -336,7 +336,6 @@ namespace Ocelot.AcceptanceTests }, UpstreamPathTemplate = "/vacancy/", UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, - ServiceName = "botCore", LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" } }, new FileReRoute @@ -353,7 +352,6 @@ namespace Ocelot.AcceptanceTests }, UpstreamPathTemplate = "/vacancy/{vacancyId}", UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, - ServiceName = "botCore", LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" } } } @@ -828,7 +826,6 @@ namespace Ocelot.AcceptanceTests }, UpstreamPathTemplate = "/vacancy/", UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, - ServiceName = "botCore", LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" } }, new FileReRoute @@ -845,7 +842,6 @@ namespace Ocelot.AcceptanceTests }, UpstreamPathTemplate = "/vacancy/{vacancyId}", UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, - ServiceName = "botCore", LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" } } } diff --git a/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs b/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs index 19268f0d..4e92b54d 100644 --- a/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs @@ -33,7 +33,6 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", UpstreamPathTemplate = "/{everything}", UpstreamHttpMethod = new List { "Get" }, - UseServiceDiscovery = true, ServiceName = "OcelotServiceApplication/OcelotApplicationService" } }, @@ -70,7 +69,6 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", UpstreamPathTemplate = "/EquipmentInterfaces", UpstreamHttpMethod = new List { "Get" }, - UseServiceDiscovery = true, ServiceName = "OcelotServiceApplication/OcelotApplicationService" } }, @@ -107,7 +105,6 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", UpstreamPathTemplate = "/EquipmentInterfaces", UpstreamHttpMethod = new List { "Get" }, - UseServiceDiscovery = true, ServiceName = "OcelotServiceApplication/OcelotApplicationService" } }, diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationFluentValidatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationFluentValidatorTests.cs index 9fec1700..9b473c6f 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationFluentValidatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationFluentValidatorTests.cs @@ -17,6 +17,9 @@ using Microsoft.Extensions.DependencyInjection; using Ocelot.Requester; using Requester; + using Ocelot.ServiceDiscovery.Providers; + using Ocelot.Values; + using Ocelot.ServiceDiscovery; public class FileConfigurationFluentValidatorTests { @@ -33,6 +36,191 @@ _configurationValidator = new FileConfigurationFluentValidator(_authProvider.Object, provider); } + [Fact] + public void configuration_is_valid_if_service_discovery_options_specified_and_has_service_fabric_as_option() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + ServiceName = "test" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "ServiceFabric", + Port = 8500 + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_if_service_discovery_options_specified_and_has_service_discovery_handler() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + ServiceName = "test" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "FakeServiceDiscoveryProvider", + Port = 8500 + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .And(x => x.GivenAServiceDiscoveryHandler()) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_if_service_discovery_options_specified_dynamically_and_has_service_discovery_handler() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "FakeServiceDiscoveryProvider", + Port = 8500 + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .And(x => x.GivenAServiceDiscoveryHandler()) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_service_discovery_options_specified_but_no_service_discovery_handler() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + ServiceName = "test" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "FakeServiceDiscoveryProvider", + Port = 8500 + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorIs()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_service_discovery_options_specified_dynamically_but_service_discovery_handler() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "FakeServiceDiscoveryProvider", + Port = 8500 + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorIs()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_service_discovery_options_specified_but_no_service_discovery_handler_with_matching_name() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + ServiceName = "test" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "consul", + Port = 8500 + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .And(x => x.GivenAServiceDiscoveryHandler()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorIs()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?")) + .BDDfy(); + } + [Fact] public void configuration_is_valid_if_qos_options_specified_and_has_qos_handler() { @@ -151,7 +339,7 @@ .BDDfy(); } - [Fact] + [Fact] public void configuration_is_invalid_if_qos_options_specified_globally_but_no_qos_handler() { var configuration = new FileConfiguration @@ -988,31 +1176,6 @@ .BDDfy(); } - [Theory] - [InlineData(null)] - [InlineData("")] - public void configuration_is_invalid_with_using_service_discovery_and_no_service_name(string serviceName) - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = true, - ServiceName = serviceName - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!")) - .BDDfy(); - } - [Fact] public void configuration_is_valid_with_using_service_discovery_and_service_name() { @@ -1025,9 +1188,17 @@ DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = true, ServiceName = "Test" } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Type = "servicefabric", + Host = "localhost", + Port = 1234 + } } })) .When(x => x.WhenIValidateTheConfiguration()) @@ -1049,7 +1220,6 @@ DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, DownstreamHostAndPorts = new List { new FileHostAndPort @@ -1078,7 +1248,6 @@ DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, DownstreamHostAndPorts = new List { new FileHostAndPort @@ -1106,7 +1275,6 @@ DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, DownstreamHostAndPorts = new List { new FileHostAndPort @@ -1134,7 +1302,6 @@ DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, DownstreamHostAndPorts = new List { } @@ -1159,7 +1326,6 @@ DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, DownstreamHostAndPorts = new List { new FileHostAndPort() @@ -1220,6 +1386,23 @@ _configurationValidator = new FileConfigurationFluentValidator(_authProvider.Object, provider); } + private void GivenAServiceDiscoveryHandler() + { + var collection = new ServiceCollection(); + ServiceDiscoveryFinderDelegate del = (a,b,c) => new FakeServiceDiscoveryProvider(); + collection.AddSingleton(del); + var provider = collection.BuildServiceProvider(); + _configurationValidator = new FileConfigurationFluentValidator(_authProvider.Object, provider); + } + + private class FakeServiceDiscoveryProvider : IServiceDiscoveryProvider + { + public Task> Get() + { + throw new System.NotImplementedException(); + } + } + private class TestOptions : AuthenticationSchemeOptions { } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index 703078ce..652193ca 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using Ocelot.ServiceDiscovery.Providers; using TestStack.BDDfy; using Xunit; +using Ocelot.Responses; namespace Ocelot.UnitTests.LoadBalancer { @@ -15,7 +16,7 @@ namespace Ocelot.UnitTests.LoadBalancer { private DownstreamReRoute _reRoute; private readonly LoadBalancerFactory _factory; - private ILoadBalancer _result; + private Response _result; private readonly Mock _serviceProviderFactory; private readonly Mock _serviceProvider; private ServiceProviderConfiguration _serviceProviderConfig; @@ -115,7 +116,7 @@ namespace Ocelot.UnitTests.LoadBalancer { _serviceProviderFactory .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .Returns(_serviceProvider.Object); + .Returns(new OkResponse(_serviceProvider.Object)); } private void ThenTheServiceProviderIsCalledCorrectly() @@ -136,7 +137,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void ThenTheLoadBalancerIsReturned() { - _result.ShouldBeOfType(); + _result.Data.ShouldBeOfType(); } } } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index dbab895d..e74f5728 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -111,7 +111,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(DownstreamReRoute reRoute) { _reRoute = reRoute; - _factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(new LeastConnection(null, null)); + _factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(new OkResponse(new LeastConnection(null, null))); _getResult = _loadBalancerHouse.Get(_reRoute, _serviceProviderConfig).Result; } @@ -138,7 +138,7 @@ namespace Ocelot.UnitTests.LoadBalancer { _reRoute = reRoute; _loadBalancer = loadBalancer; - _factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(loadBalancer); + _factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(new OkResponse(loadBalancer)); _getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result; } diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceDiscoveryProviderFactoryTests.cs similarity index 80% rename from test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs rename to test/Ocelot.UnitTests/ServiceDiscovery/ServiceDiscoveryProviderFactoryTests.cs index 6db38012..46cec86e 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceDiscoveryProviderFactoryTests.cs @@ -14,11 +14,12 @@ namespace Ocelot.UnitTests.ServiceDiscovery using Shouldly; using TestStack.BDDfy; using Xunit; + using Ocelot.Responses; - public class ServiceProviderFactoryTests + public class ServiceDiscoveryProviderFactoryTests { private ServiceProviderConfiguration _serviceConfig; - private IServiceDiscoveryProvider _result; + private Response _result; private ServiceDiscoveryProviderFactory _factory; private DownstreamReRoute _reRoute; private readonly Mock _loggerFactory; @@ -26,7 +27,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery private IServiceProvider _provider; private readonly IServiceCollection _collection; - public ServiceProviderFactoryTests() + public ServiceDiscoveryProviderFactoryTests() { _loggerFactory = new Mock(); _logger = new Mock(); @@ -71,7 +72,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery } [Fact] - public void should_call_delegate() + public void should_return_provider_because_type_matches_reflected_type_from_delegate() { var reRoute = new DownstreamReRouteBuilder() .WithServiceName("product") @@ -79,6 +80,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery .Build(); var serviceConfig = new ServiceProviderConfigurationBuilder() + .WithType(nameof(Fake)) .Build(); this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) @@ -88,6 +90,25 @@ namespace Ocelot.UnitTests.ServiceDiscovery .BDDfy(); } + [Fact] + public void should_not_return_provider_because_type_doesnt_match_reflected_type_from_delegate() + { + var reRoute = new DownstreamReRouteBuilder() + .WithServiceName("product") + .WithUseServiceDiscovery(true) + .Build(); + + var serviceConfig = new ServiceProviderConfigurationBuilder() + .WithType("Wookie") + .Build(); + + this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) + .And(x => GivenAFakeDelegate()) + .When(x => x.WhenIGetTheServiceProvider()) + .Then(x => x.ThenTheResultIsError()) + .BDDfy(); + } + [Fact] public void should_return_service_fabric_provider() { @@ -124,12 +145,17 @@ namespace Ocelot.UnitTests.ServiceDiscovery private void ThenTheDelegateIsCalled() { - _result.GetType().Name.ShouldBe("Fake"); + _result.Data.GetType().Name.ShouldBe("Fake"); + } + + private void ThenTheResultIsError() + { + _result.IsError.ShouldBeTrue(); } private void ThenTheFollowingServicesAreReturned(List downstreamAddresses) { - var result = (ConfigurationServiceProvider)_result; + var result = (ConfigurationServiceProvider)_result.Data; var services = result.Get().Result; for (int i = 0; i < services.Count; i++) @@ -155,7 +181,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery private void ThenTheServiceProviderIs() { - _result.ShouldBeOfType(); + _result.Data.ShouldBeOfType(); } } }