mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-06-17 11:53:22 +08:00
Feature/should not start if specified using service discovery but no provider registered (#584)
* #580 added failing test * #580 added failing test for dynamic reroutes * #580 added failing test for validator * #580 validation tests passing * #580 acceptance tests passing * #580 got rid of the list in sdp factory * +semver: breaking #580 service discovery provider returned by delegate must be the same as name in config, this is a breaking change because you have to specify consul now * #580 removed use servide discovery property from file config, we dont need it, just use the service name as indicator the user wants to use service discovery for the given reroute
This commit is contained in:
parent
0a7d81038e
commit
55277cac45
@ -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
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,223 @@
|
||||
|
||||
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 <https://github.com/ThreeMammals/Ocelot/issues/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 <https://github.com/ThreeMammals/Ocelot/issues/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
|
||||
=============
|
||||
|
||||
@ -59,7 +279,6 @@ The following shows how to set up a ReRoute using service discovery then select
|
||||
"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
|
||||
|
@ -211,7 +211,6 @@ Ocelot allows you to specify a querystring as part of the DownstreamPathTemplate
|
||||
}
|
||||
],
|
||||
"GlobalConfiguration": {
|
||||
"UseServiceDiscovery": false
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,7 +238,6 @@ Ocelot will also allow you to put query string parameters in the UpstreamPathTem
|
||||
}
|
||||
],
|
||||
"GlobalConfiguration": {
|
||||
"UseServiceDiscovery": false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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": {
|
||||
|
@ -4,7 +4,6 @@
|
||||
"DownstreamPathTemplate": "/api/Category",
|
||||
"DownstreamScheme": "http",
|
||||
"UpstreamPathTemplate": "/Category",
|
||||
"UseServiceDiscovery": true,
|
||||
"ServiceName": "ncore-rat",
|
||||
"UpstreamHttpMethod": [ "Get" ],
|
||||
"QoSOptions": {
|
||||
|
@ -7,8 +7,7 @@
|
||||
"Get"
|
||||
],
|
||||
"DownstreamScheme": "http",
|
||||
"ServiceName": "OcelotServiceApplication/OcelotApplicationService",
|
||||
"UseServiceDiscovery" : true
|
||||
"ServiceName": "OcelotServiceApplication/OcelotApplicationService"
|
||||
}
|
||||
],
|
||||
"GlobalConfiguration": {
|
||||
|
@ -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)
|
||||
|
@ -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<FileHostAndPort> DownstreamHostAndPorts {get;set;}
|
||||
public string UpstreamHost { get; set; }
|
||||
public string Key { get;set; }
|
||||
|
@ -11,15 +11,19 @@ namespace Ocelot.Configuration.Validator
|
||||
{
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
using Requester;
|
||||
|
||||
public class FileConfigurationFluentValidator : AbstractValidator<FileConfiguration>, IConfigurationValidator
|
||||
{
|
||||
private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate;
|
||||
|
||||
private readonly List<ServiceDiscoveryFinderDelegate> _serviceDiscoveryFinderDelegates;
|
||||
public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, IServiceProvider provider)
|
||||
{
|
||||
_qosDelegatingHandlerDelegate = provider.GetService<QosDelegatingHandlerDelegate>();
|
||||
_serviceDiscoveryFinderDelegates = provider
|
||||
.GetServices<ServiceDiscoveryFinderDelegate>()
|
||||
.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<Response<ConfigurationValidationResult>> IsValid(FileConfiguration configuration)
|
||||
{
|
||||
var validateResult = await ValidateAsync(configuration);
|
||||
|
@ -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());
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<ILoadBalancer> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config);
|
||||
Task<Response<ILoadBalancer>> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config);
|
||||
}
|
||||
}
|
||||
|
@ -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<ILoadBalancer> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config)
|
||||
public async Task<Response<ILoadBalancer>> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config)
|
||||
{
|
||||
var serviceProvider = _serviceProviderFactory.Get(config, reRoute);
|
||||
var response = _serviceProviderFactory.Get(config, reRoute);
|
||||
|
||||
if(response.IsError)
|
||||
{
|
||||
return new ErrorResponse<ILoadBalancer>(response.Errors);
|
||||
}
|
||||
|
||||
var serviceProvider = response.Data;
|
||||
|
||||
switch (reRoute.LoadBalancerOptions?.Type)
|
||||
{
|
||||
case nameof(RoundRobin):
|
||||
return new RoundRobin(async () => await serviceProvider.Get());
|
||||
return new OkResponse<ILoadBalancer>(new RoundRobin(async () => await serviceProvider.Get()));
|
||||
case nameof(LeastConnection):
|
||||
return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName);
|
||||
return new OkResponse<ILoadBalancer>(new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName));
|
||||
case nameof(CookieStickySessions):
|
||||
var loadBalancer = new RoundRobin(async () => await serviceProvider.Get());
|
||||
var bus = new InMemoryBus<StickySession>();
|
||||
return new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs, bus);
|
||||
return new OkResponse<ILoadBalancer>(new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs, bus));
|
||||
default:
|
||||
return new NoLoadBalancer(async () => await serviceProvider.Get());
|
||||
return new OkResponse<ILoadBalancer>(new NoLoadBalancer(async () => await serviceProvider.Get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,20 +22,32 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
try
|
||||
{
|
||||
Response<ILoadBalancer> result;
|
||||
|
||||
if (_loadBalancers.TryGetValue(reRoute.LoadBalancerKey, out var loadBalancer))
|
||||
{
|
||||
loadBalancer = _loadBalancers[reRoute.LoadBalancerKey];
|
||||
|
||||
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<ILoadBalancer>(result.Errors);
|
||||
}
|
||||
loadBalancer = result.Data;
|
||||
AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer);
|
||||
}
|
||||
|
||||
return new OkResponse<ILoadBalancer>(loadBalancer);
|
||||
}
|
||||
|
||||
loadBalancer = await _factory.Get(reRoute, config);
|
||||
result = await _factory.Get(reRoute, config);
|
||||
if (result.IsError)
|
||||
{
|
||||
return new ErrorResponse<ILoadBalancer>(result.Errors);
|
||||
}
|
||||
loadBalancer = result.Data;
|
||||
AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer);
|
||||
return new OkResponse<ILoadBalancer>(loadBalancer);
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.ServiceDiscovery.Providers;
|
||||
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.ServiceDiscovery.Providers;
|
||||
|
||||
public interface IServiceDiscoveryProviderFactory
|
||||
{
|
||||
IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute);
|
||||
Response<IServiceDiscoveryProvider> Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute);
|
||||
}
|
||||
}
|
||||
|
@ -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<ServiceDiscoveryFinderDelegate> _delegates;
|
||||
private readonly ServiceDiscoveryFinderDelegate _delegates;
|
||||
private readonly IServiceProvider _provider;
|
||||
|
||||
public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IServiceProvider provider)
|
||||
{
|
||||
_factory = factory;
|
||||
_provider = provider;
|
||||
_delegates = provider
|
||||
.GetServices<ServiceDiscoveryFinderDelegate>()
|
||||
.ToList();
|
||||
_delegates = provider.GetService<ServiceDiscoveryFinderDelegate>();
|
||||
}
|
||||
|
||||
public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute)
|
||||
public Response<IServiceDiscoveryProvider> Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute)
|
||||
{
|
||||
if (reRoute.UseServiceDiscovery)
|
||||
{
|
||||
@ -41,27 +40,28 @@ namespace Ocelot.ServiceDiscovery
|
||||
services.Add(service);
|
||||
}
|
||||
|
||||
return new ConfigurationServiceProvider(services);
|
||||
return new OkResponse<IServiceDiscoveryProvider>(new ConfigurationServiceProvider(services));
|
||||
}
|
||||
|
||||
private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration config, string key)
|
||||
private Response<IServiceDiscoveryProvider> 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<IServiceDiscoveryProvider>(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<IServiceDiscoveryProvider>(provider);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return new ErrorResponse<IServiceDiscoveryProvider>(new UnableToFindServiceDiscoveryProviderError($"Unable to find service discovery provider for type: {config.Type}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
namespace Ocelot.ServiceDiscovery
|
||||
{
|
||||
using Ocelot.Errors;
|
||||
|
||||
public class UnableToFindServiceDiscoveryProviderError : Error
|
||||
{
|
||||
public UnableToFindServiceDiscoveryProviderError(string message) : base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -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<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamPathTemplate = "/",
|
||||
DownstreamScheme = "http",
|
||||
UpstreamPathTemplate = "/laura",
|
||||
UpstreamHttpMethod = new List<string> { "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()
|
||||
{
|
||||
|
@ -336,7 +336,6 @@ namespace Ocelot.AcceptanceTests
|
||||
},
|
||||
UpstreamPathTemplate = "/vacancy/",
|
||||
UpstreamHttpMethod = new List<string> { "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<string> { "Options", "Put", "Get", "Post", "Delete" },
|
||||
ServiceName = "botCore",
|
||||
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }
|
||||
}
|
||||
}
|
||||
@ -828,7 +826,6 @@ namespace Ocelot.AcceptanceTests
|
||||
},
|
||||
UpstreamPathTemplate = "/vacancy/",
|
||||
UpstreamHttpMethod = new List<string> { "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<string> { "Options", "Put", "Get", "Post", "Delete" },
|
||||
ServiceName = "botCore",
|
||||
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,6 @@ namespace Ocelot.AcceptanceTests
|
||||
DownstreamScheme = "http",
|
||||
UpstreamPathTemplate = "/{everything}",
|
||||
UpstreamHttpMethod = new List<string> { "Get" },
|
||||
UseServiceDiscovery = true,
|
||||
ServiceName = "OcelotServiceApplication/OcelotApplicationService"
|
||||
}
|
||||
},
|
||||
@ -70,7 +69,6 @@ namespace Ocelot.AcceptanceTests
|
||||
DownstreamScheme = "http",
|
||||
UpstreamPathTemplate = "/EquipmentInterfaces",
|
||||
UpstreamHttpMethod = new List<string> { "Get" },
|
||||
UseServiceDiscovery = true,
|
||||
ServiceName = "OcelotServiceApplication/OcelotApplicationService"
|
||||
}
|
||||
},
|
||||
@ -107,7 +105,6 @@ namespace Ocelot.AcceptanceTests
|
||||
DownstreamScheme = "http",
|
||||
UpstreamPathTemplate = "/EquipmentInterfaces",
|
||||
UpstreamHttpMethod = new List<string> { "Get" },
|
||||
UseServiceDiscovery = true,
|
||||
ServiceName = "OcelotServiceApplication/OcelotApplicationService"
|
||||
}
|
||||
},
|
||||
|
@ -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<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamPathTemplate = "/",
|
||||
DownstreamScheme = "http",
|
||||
UpstreamPathTemplate = "/laura",
|
||||
UpstreamHttpMethod = new List<string> { "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<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamPathTemplate = "/",
|
||||
DownstreamScheme = "http",
|
||||
UpstreamPathTemplate = "/laura",
|
||||
UpstreamHttpMethod = new List<string> { "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<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamPathTemplate = "/",
|
||||
DownstreamScheme = "http",
|
||||
UpstreamPathTemplate = "/laura",
|
||||
UpstreamHttpMethod = new List<string> { "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<FileValidationFailedError>())
|
||||
.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<FileValidationFailedError>())
|
||||
.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<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamPathTemplate = "/",
|
||||
DownstreamScheme = "http",
|
||||
UpstreamPathTemplate = "/laura",
|
||||
UpstreamHttpMethod = new List<string> { "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<FileValidationFailedError>())
|
||||
.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()
|
||||
{
|
||||
@ -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<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamPathTemplate = "/api/products/",
|
||||
UpstreamPathTemplate = "/asdf/",
|
||||
UpstreamHttpMethod = new List<string> {"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<string> {"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<string> {"Get"},
|
||||
UseServiceDiscovery = false,
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
@ -1078,7 +1248,6 @@
|
||||
DownstreamPathTemplate = "/api/products/",
|
||||
UpstreamPathTemplate = "/asdf/",
|
||||
UpstreamHttpMethod = new List<string> {"Get"},
|
||||
UseServiceDiscovery = false,
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
@ -1106,7 +1275,6 @@
|
||||
DownstreamPathTemplate = "/api/products/",
|
||||
UpstreamPathTemplate = "/asdf/",
|
||||
UpstreamHttpMethod = new List<string> {"Get"},
|
||||
UseServiceDiscovery = false,
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
@ -1134,7 +1302,6 @@
|
||||
DownstreamPathTemplate = "/api/products/",
|
||||
UpstreamPathTemplate = "/asdf/",
|
||||
UpstreamHttpMethod = new List<string> {"Get"},
|
||||
UseServiceDiscovery = false,
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
}
|
||||
@ -1159,7 +1326,6 @@
|
||||
DownstreamPathTemplate = "/api/products/",
|
||||
UpstreamPathTemplate = "/asdf/",
|
||||
UpstreamHttpMethod = new List<string> {"Get"},
|
||||
UseServiceDiscovery = false,
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
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<ServiceDiscoveryFinderDelegate>(del);
|
||||
var provider = collection.BuildServiceProvider();
|
||||
_configurationValidator = new FileConfigurationFluentValidator(_authProvider.Object, provider);
|
||||
}
|
||||
|
||||
private class FakeServiceDiscoveryProvider : IServiceDiscoveryProvider
|
||||
{
|
||||
public Task<List<Service>> Get()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
}
|
||||
|
@ -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<ILoadBalancer> _result;
|
||||
private readonly Mock<IServiceDiscoveryProviderFactory> _serviceProviderFactory;
|
||||
private readonly Mock<IServiceDiscoveryProvider> _serviceProvider;
|
||||
private ServiceProviderConfiguration _serviceProviderConfig;
|
||||
@ -115,7 +116,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
_serviceProviderFactory
|
||||
.Setup(x => x.Get(It.IsAny<ServiceProviderConfiguration>(), It.IsAny<DownstreamReRoute>()))
|
||||
.Returns(_serviceProvider.Object);
|
||||
.Returns(new OkResponse<IServiceDiscoveryProvider>(_serviceProvider.Object));
|
||||
}
|
||||
|
||||
private void ThenTheServiceProviderIsCalledCorrectly()
|
||||
@ -136,7 +137,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
||||
|
||||
private void ThenTheLoadBalancerIsReturned<T>()
|
||||
{
|
||||
_result.ShouldBeOfType<T>();
|
||||
_result.Data.ShouldBeOfType<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<ILoadBalancer>(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<ILoadBalancer>(loadBalancer));
|
||||
_getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result;
|
||||
}
|
||||
|
||||
|
@ -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<IServiceDiscoveryProvider> _result;
|
||||
private ServiceDiscoveryProviderFactory _factory;
|
||||
private DownstreamReRoute _reRoute;
|
||||
private readonly Mock<IOcelotLoggerFactory> _loggerFactory;
|
||||
@ -26,7 +27,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery
|
||||
private IServiceProvider _provider;
|
||||
private readonly IServiceCollection _collection;
|
||||
|
||||
public ServiceProviderFactoryTests()
|
||||
public ServiceDiscoveryProviderFactoryTests()
|
||||
{
|
||||
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
||||
_logger = new Mock<IOcelotLogger>();
|
||||
@ -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<DownstreamHostAndPort> 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<T>()
|
||||
{
|
||||
_result.ShouldBeOfType<T>();
|
||||
_result.Data.ShouldBeOfType<T>();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user