mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 09:52:50 +08:00
commit
4a0d355446
@ -55,8 +55,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio
|
||||
|
||||
## How to install
|
||||
|
||||
Ocelot is designed to work with ASP.NET core only and is currently
|
||||
built to netcoreapp2.0 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you.
|
||||
Ocelot is designed to work with ASP.NET Core only and it targets `netstandard2.0`. This means it can be used anywhere `.NET Standard 2.0` is supported, including `.NET Core 2.0` and `.NET Framework 4.6.1` and up. [This](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) documentation may prove helpful when working out if Ocelot would be suitable for you.
|
||||
|
||||
Install Ocelot and it's dependencies using NuGet.
|
||||
|
||||
|
@ -1,174 +1,193 @@
|
||||
Configuration
|
||||
============
|
||||
|
||||
An example configuration can be found `here <https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/ocelot.json>`_.
|
||||
There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration.
|
||||
The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global
|
||||
configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful
|
||||
if you don't want to manage lots of ReRoute specific settings.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"ReRoutes": [],
|
||||
"GlobalConfiguration": {}
|
||||
}
|
||||
|
||||
Here is an example ReRoute configuration, You don't need to set all of these things but this is everything that is available at the moment:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"DownstreamPathTemplate": "/",
|
||||
"UpstreamPathTemplate": "/",
|
||||
"UpstreamHttpMethod": [
|
||||
"Get"
|
||||
],
|
||||
"AddHeadersToRequest": {},
|
||||
"AddClaimsToRequest": {},
|
||||
"RouteClaimsRequirement": {},
|
||||
"AddQueriesToRequest": {},
|
||||
"RequestIdKey": "",
|
||||
"FileCacheOptions": {
|
||||
"TtlSeconds": 0,
|
||||
"Region": ""
|
||||
},
|
||||
"ReRouteIsCaseSensitive": false,
|
||||
"ServiceName": "",
|
||||
"DownstreamScheme": "http",
|
||||
"DownstreamHostAndPorts": [
|
||||
{
|
||||
"Host": "localhost",
|
||||
"Port": 51876,
|
||||
}
|
||||
],
|
||||
"QoSOptions": {
|
||||
"ExceptionsAllowedBeforeBreaking": 0,
|
||||
"DurationOfBreak": 0,
|
||||
"TimeoutValue": 0
|
||||
},
|
||||
"LoadBalancer": "",
|
||||
"RateLimitOptions": {
|
||||
"ClientWhitelist": [],
|
||||
"EnableRateLimiting": false,
|
||||
"Period": "",
|
||||
"PeriodTimespan": 0,
|
||||
"Limit": 0
|
||||
},
|
||||
"AuthenticationOptions": {
|
||||
"AuthenticationProviderKey": "",
|
||||
"AllowedScopes": []
|
||||
},
|
||||
"HttpHandlerOptions": {
|
||||
"AllowAutoRedirect": true,
|
||||
"UseCookieContainer": true,
|
||||
"UseTracing": true
|
||||
},
|
||||
"UseServiceDiscovery": false,
|
||||
"DangerousAcceptAnyServerCertificateValidator": false
|
||||
}
|
||||
|
||||
More information on how to use these options is below..
|
||||
|
||||
Multiple environments
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Like any other asp.net core project Ocelot supports configuration file names such as configuration.dev.json, configuration.test.json etc. In order to implement this add the following
|
||||
to you
|
||||
|
||||
.. code-block:: csharp
|
||||
|
||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||
{
|
||||
config
|
||||
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
|
||||
.AddJsonFile("appsettings.json", true, true)
|
||||
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
|
||||
.AddJsonFile("ocelot.json")
|
||||
.AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json")
|
||||
.AddEnvironmentVariables();
|
||||
})
|
||||
|
||||
Ocelot will now use the environment specific configuration and fall back to ocelot.json if there isnt one.
|
||||
|
||||
You also need to set the corresponding environment variable which is ASPNETCORE_ENVIRONMENT. More info on this can be found in the `asp.net core docs <https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments>`_.
|
||||
|
||||
Merging configuration files
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This feature was requested in `Issue 296 <https://github.com/ThreeMammals/Ocelot/issues/296>`_ and allows users to have multiple configuration files to make managing large configurations easier.
|
||||
|
||||
Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you can call AddOcelot() like below.
|
||||
|
||||
.. code-block:: csharp
|
||||
|
||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||
{
|
||||
config
|
||||
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
|
||||
.AddJsonFile("appsettings.json", true, true)
|
||||
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
|
||||
.AddOcelot()
|
||||
.AddEnvironmentVariables();
|
||||
})
|
||||
|
||||
In this scenario Ocelot will look for any files that match the pattern (?i)ocelot.([a-zA-Z0-9]*).json and then merge these together. If you want to set the GlobalConfiguration property you must have a file called ocelot.global.json.
|
||||
|
||||
The way Ocelot merges the files is basically load them, loop over them, add any ReRoutes, add any AggregateReRoutes and if the file is called ocelot.global.json add the GlobalConfiguration aswell as any ReRoutes or AggregateReRoutes. Ocelot will then save the merged configuration to a file called ocelot.json and this will be used as the source of truth while ocelot is running.
|
||||
|
||||
At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. I would advise always checking what is in ocelot.json if you have any problems.
|
||||
|
||||
Store configuration in consul
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store.
|
||||
|
||||
.. code-block:: csharp
|
||||
|
||||
services
|
||||
.AddOcelot()
|
||||
.AddStoreOcelotConfigurationInConsul();
|
||||
|
||||
You also need to add the following to your ocelot.json. This is how Ocelot
|
||||
finds your Consul agent and interacts to load and store the configuration from Consul.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
"GlobalConfiguration": {
|
||||
"ServiceDiscoveryProvider": {
|
||||
"Host": "localhost",
|
||||
"Port": 9500
|
||||
}
|
||||
}
|
||||
|
||||
I decided to create this feature after working on the raft consensus algorithm and finding out its super hard. Why not take advantage of the fact Consul already gives you this!
|
||||
I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now.
|
||||
|
||||
This feature has a 3 second ttl cache before making a new request to your local consul agent.
|
||||
|
||||
Follow Redirects / Use CookieContainer
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior:
|
||||
|
||||
1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically
|
||||
follow redirection responses from the Downstream resource; otherwise false. The default value is false.
|
||||
2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer
|
||||
property to store server cookies and uses these cookies when sending requests. The default value is false. Please note
|
||||
that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests
|
||||
to that DownstreamService will share the same cookies. `Issue 274 <https://github.com/ThreeMammals/Ocelot/issues/274>`_ was created because a user
|
||||
noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients
|
||||
that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight
|
||||
requests. This would also mean that subsequent requests dont use the cookies from the previous response! All in all not a great situation. I would avoid setting
|
||||
UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request!
|
||||
|
||||
SSL Errors
|
||||
^^^^^^^^^^
|
||||
|
||||
Id you want to ignore SSL warnings / errors set the following in your ReRoute config.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
"DangerousAcceptAnyServerCertificateValidator": false
|
||||
|
||||
I don't reccomend doing this, I suggest creating your own certificate and then getting it trusted by your local / remote machine if you can.
|
||||
Configuration
|
||||
============
|
||||
|
||||
An example configuration can be found `here <https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/ocelot.json>`_.
|
||||
There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration.
|
||||
The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global
|
||||
configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful
|
||||
if you don't want to manage lots of ReRoute specific settings.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"ReRoutes": [],
|
||||
"GlobalConfiguration": {}
|
||||
}
|
||||
|
||||
Here is an example ReRoute configuration, You don't need to set all of these things but this is everything that is available at the moment:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"DownstreamPathTemplate": "/",
|
||||
"UpstreamPathTemplate": "/",
|
||||
"UpstreamHttpMethod": [
|
||||
"Get"
|
||||
],
|
||||
"AddHeadersToRequest": {},
|
||||
"AddClaimsToRequest": {},
|
||||
"RouteClaimsRequirement": {},
|
||||
"AddQueriesToRequest": {},
|
||||
"RequestIdKey": "",
|
||||
"FileCacheOptions": {
|
||||
"TtlSeconds": 0,
|
||||
"Region": ""
|
||||
},
|
||||
"ReRouteIsCaseSensitive": false,
|
||||
"ServiceName": "",
|
||||
"DownstreamScheme": "http",
|
||||
"DownstreamHostAndPorts": [
|
||||
{
|
||||
"Host": "localhost",
|
||||
"Port": 51876,
|
||||
}
|
||||
],
|
||||
"QoSOptions": {
|
||||
"ExceptionsAllowedBeforeBreaking": 0,
|
||||
"DurationOfBreak": 0,
|
||||
"TimeoutValue": 0
|
||||
},
|
||||
"LoadBalancer": "",
|
||||
"RateLimitOptions": {
|
||||
"ClientWhitelist": [],
|
||||
"EnableRateLimiting": false,
|
||||
"Period": "",
|
||||
"PeriodTimespan": 0,
|
||||
"Limit": 0
|
||||
},
|
||||
"AuthenticationOptions": {
|
||||
"AuthenticationProviderKey": "",
|
||||
"AllowedScopes": []
|
||||
},
|
||||
"HttpHandlerOptions": {
|
||||
"AllowAutoRedirect": true,
|
||||
"UseCookieContainer": true,
|
||||
"UseTracing": true
|
||||
},
|
||||
"UseServiceDiscovery": false,
|
||||
"DangerousAcceptAnyServerCertificateValidator": false
|
||||
}
|
||||
|
||||
More information on how to use these options is below..
|
||||
|
||||
Multiple environments
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Like any other asp.net core project Ocelot supports configuration file names such as configuration.dev.json, configuration.test.json etc. In order to implement this add the following
|
||||
to you
|
||||
|
||||
.. code-block:: csharp
|
||||
|
||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||
{
|
||||
config
|
||||
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
|
||||
.AddJsonFile("appsettings.json", true, true)
|
||||
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
|
||||
.AddJsonFile("ocelot.json")
|
||||
.AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json")
|
||||
.AddEnvironmentVariables();
|
||||
})
|
||||
|
||||
Ocelot will now use the environment specific configuration and fall back to ocelot.json if there isnt one.
|
||||
|
||||
You also need to set the corresponding environment variable which is ASPNETCORE_ENVIRONMENT. More info on this can be found in the `asp.net core docs <https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments>`_.
|
||||
|
||||
Merging configuration files
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This feature was requested in `Issue 296 <https://github.com/ThreeMammals/Ocelot/issues/296>`_ and allows users to have multiple configuration files to make managing large configurations easier.
|
||||
|
||||
Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you can call AddOcelot() like below.
|
||||
|
||||
.. code-block:: csharp
|
||||
|
||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||
{
|
||||
config
|
||||
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
|
||||
.AddJsonFile("appsettings.json", true, true)
|
||||
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
|
||||
.AddOcelot()
|
||||
.AddEnvironmentVariables();
|
||||
})
|
||||
|
||||
In this scenario Ocelot will look for any files that match the pattern (?i)ocelot.([a-zA-Z0-9]*).json and then merge these together. If you want to set the GlobalConfiguration property you must have a file called ocelot.global.json.
|
||||
|
||||
The way Ocelot merges the files is basically load them, loop over them, add any ReRoutes, add any AggregateReRoutes and if the file is called ocelot.global.json add the GlobalConfiguration aswell as any ReRoutes or AggregateReRoutes. Ocelot will then save the merged configuration to a file called ocelot.json and this will be used as the source of truth while ocelot is running.
|
||||
|
||||
At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. I would advise always checking what is in ocelot.json if you have any problems.
|
||||
|
||||
Store configuration in consul
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store.
|
||||
|
||||
.. code-block:: csharp
|
||||
|
||||
services
|
||||
.AddOcelot()
|
||||
.AddStoreOcelotConfigurationInConsul();
|
||||
|
||||
You also need to add the following to your ocelot.json. This is how Ocelot
|
||||
finds your Consul agent and interacts to load and store the configuration from Consul.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
"GlobalConfiguration": {
|
||||
"ServiceDiscoveryProvider": {
|
||||
"Host": "localhost",
|
||||
"Port": 9500
|
||||
}
|
||||
}
|
||||
|
||||
I decided to create this feature after working on the raft consensus algorithm and finding out its super hard. Why not take advantage of the fact Consul already gives you this!
|
||||
I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now.
|
||||
|
||||
This feature has a 3 second ttl cache before making a new request to your local consul agent.
|
||||
|
||||
Configuration Key
|
||||
-----------------
|
||||
|
||||
If you are using Consul for configuration (or other providers in the future) you might want to key your configurations so you can have multiple configurations :) This feature was requested in `issue 346 <https://github.com/ThreeMammals/Ocelot/issues/346>`_! In order to specify the key you need to set the ConfigurationKey property in the ServiceDiscoveryProvider section of the configuration json file e.g.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
"GlobalConfiguration": {
|
||||
"ServiceDiscoveryProvider": {
|
||||
"Host": "localhost",
|
||||
"Port": 9500,
|
||||
"ConfigurationKey": "Oceolot_A"
|
||||
}
|
||||
}
|
||||
|
||||
In this example Ocelot will use Oceolot_A as the key for your configuration when looking it up in Consul.
|
||||
|
||||
If you do not set the ConfigurationKey Ocelot will use the string InternalConfiguration as the key.
|
||||
|
||||
Follow Redirects / Use CookieContainer
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior:
|
||||
|
||||
1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically
|
||||
follow redirection responses from the Downstream resource; otherwise false. The default value is false.
|
||||
2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer
|
||||
property to store server cookies and uses these cookies when sending requests. The default value is false. Please note
|
||||
that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests
|
||||
to that DownstreamService will share the same cookies. `Issue 274 <https://github.com/ThreeMammals/Ocelot/issues/274>`_ was created because a user
|
||||
noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients
|
||||
that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight
|
||||
requests. This would also mean that subsequent requests dont use the cookies from the previous response! All in all not a great situation. I would avoid setting
|
||||
UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request!
|
||||
|
||||
SSL Errors
|
||||
^^^^^^^^^^
|
||||
|
||||
Id you want to ignore SSL warnings / errors set the following in your ReRoute config.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
"DangerousAcceptAnyServerCertificateValidator": false
|
||||
|
||||
I don't reccomend doing this, I suggest creating your own certificate and then getting it trusted by your local / remote machine if you can.
|
||||
|
@ -2,14 +2,14 @@ Getting Started
|
||||
===============
|
||||
|
||||
Ocelot is designed to work with .NET Core only and is currently
|
||||
built to netcoreapp2.0 `this <https://docs.microsoft.com/en-us/dotnet/articles/standard/library>`_ documentation may prove helpful when working out if Ocelot would be suitable for you.
|
||||
built to netstandard2.0 `this <https://docs.microsoft.com/en-us/dotnet/articles/standard/library>`_ documentation may prove helpful when working out if Ocelot would be suitable for you.
|
||||
|
||||
.NET Core 2.0
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
**Install NuGet package**
|
||||
|
||||
Install Ocelot and it's dependencies using nuget. You will need to create a netcoreapp2.0 project and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections
|
||||
Install Ocelot and it's dependencies using nuget. You will need to create a netstandard2.0 project and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections
|
||||
to get up and running.
|
||||
|
||||
``Install-Package Ocelot``
|
||||
|
@ -1,39 +1,46 @@
|
||||
namespace Ocelot.Configuration.Builder
|
||||
{
|
||||
public class ServiceProviderConfigurationBuilder
|
||||
{
|
||||
private string _serviceDiscoveryProviderHost;
|
||||
private int _serviceDiscoveryProviderPort;
|
||||
private string _type;
|
||||
private string _token;
|
||||
|
||||
public ServiceProviderConfigurationBuilder WithHost(string serviceDiscoveryProviderHost)
|
||||
{
|
||||
_serviceDiscoveryProviderHost = serviceDiscoveryProviderHost;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ServiceProviderConfigurationBuilder WithPort(int serviceDiscoveryProviderPort)
|
||||
{
|
||||
_serviceDiscoveryProviderPort = serviceDiscoveryProviderPort;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ServiceProviderConfigurationBuilder WithType(string type)
|
||||
{
|
||||
_type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ServiceProviderConfigurationBuilder WithToken(string token)
|
||||
{
|
||||
_token = token;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ServiceProviderConfiguration Build()
|
||||
{
|
||||
return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort, _token);
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace Ocelot.Configuration.Builder
|
||||
{
|
||||
public class ServiceProviderConfigurationBuilder
|
||||
{
|
||||
private string _serviceDiscoveryProviderHost;
|
||||
private int _serviceDiscoveryProviderPort;
|
||||
private string _type;
|
||||
private string _token;
|
||||
private string _configurationKey;
|
||||
|
||||
public ServiceProviderConfigurationBuilder WithHost(string serviceDiscoveryProviderHost)
|
||||
{
|
||||
_serviceDiscoveryProviderHost = serviceDiscoveryProviderHost;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ServiceProviderConfigurationBuilder WithPort(int serviceDiscoveryProviderPort)
|
||||
{
|
||||
_serviceDiscoveryProviderPort = serviceDiscoveryProviderPort;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ServiceProviderConfigurationBuilder WithType(string type)
|
||||
{
|
||||
_type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ServiceProviderConfigurationBuilder WithToken(string token)
|
||||
{
|
||||
_token = token;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ServiceProviderConfigurationBuilder WithConfigurationKey(string configurationKey)
|
||||
{
|
||||
_configurationKey = configurationKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ServiceProviderConfiguration Build()
|
||||
{
|
||||
return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort, _token, _configurationKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Infrastructure;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.Responses;
|
||||
|
@ -1,21 +1,22 @@
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public class ServiceProviderConfigurationCreator : IServiceProviderConfigurationCreator
|
||||
{
|
||||
public ServiceProviderConfiguration Create(FileGlobalConfiguration globalConfiguration)
|
||||
{
|
||||
//todo log or return error here dont just default to something that wont work..
|
||||
var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0;
|
||||
|
||||
return new ServiceProviderConfigurationBuilder()
|
||||
.WithHost(globalConfiguration?.ServiceDiscoveryProvider?.Host)
|
||||
.WithPort(serviceProviderPort)
|
||||
.WithType(globalConfiguration?.ServiceDiscoveryProvider?.Type)
|
||||
.WithToken(globalConfiguration?.ServiceDiscoveryProvider?.Token)
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public class ServiceProviderConfigurationCreator : IServiceProviderConfigurationCreator
|
||||
{
|
||||
public ServiceProviderConfiguration Create(FileGlobalConfiguration globalConfiguration)
|
||||
{
|
||||
//todo log or return error here dont just default to something that wont work..
|
||||
var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0;
|
||||
|
||||
return new ServiceProviderConfigurationBuilder()
|
||||
.WithHost(globalConfiguration?.ServiceDiscoveryProvider?.Host)
|
||||
.WithPort(serviceProviderPort)
|
||||
.WithType(globalConfiguration?.ServiceDiscoveryProvider?.Type)
|
||||
.WithToken(globalConfiguration?.ServiceDiscoveryProvider?.Token)
|
||||
.WithConfigurationKey(globalConfiguration?.ServiceDiscoveryProvider?.ConfigurationKey)
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
|
||||
namespace Ocelot.Configuration.File
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
|
||||
namespace Ocelot.Configuration.File
|
||||
{
|
||||
|
@ -1,10 +1,11 @@
|
||||
namespace Ocelot.Configuration.File
|
||||
{
|
||||
public class FileServiceDiscoveryProvider
|
||||
{
|
||||
public string Host {get;set;}
|
||||
public int Port { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Token { get; set; }
|
||||
}
|
||||
}
|
||||
namespace Ocelot.Configuration.File
|
||||
{
|
||||
public class FileServiceDiscoveryProvider
|
||||
{
|
||||
public string Host {get;set;}
|
||||
public int Port { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Token { get; set; }
|
||||
public string ConfigurationKey { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,69 +1,76 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Ocelot.Raft;
|
||||
using Rafty.Concensus;
|
||||
|
||||
namespace Ocelot.Configuration
|
||||
{
|
||||
using Repository;
|
||||
|
||||
[Authorize]
|
||||
[Route("configuration")]
|
||||
public class FileConfigurationController : Controller
|
||||
{
|
||||
private readonly IFileConfigurationRepository _repo;
|
||||
private readonly IFileConfigurationSetter _setter;
|
||||
private readonly IServiceProvider _provider;
|
||||
|
||||
public FileConfigurationController(IFileConfigurationRepository repo, IFileConfigurationSetter setter, IServiceProvider provider)
|
||||
{
|
||||
_repo = repo;
|
||||
_setter = setter;
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Get()
|
||||
{
|
||||
var response = await _repo.Get();
|
||||
|
||||
if(response.IsError)
|
||||
{
|
||||
return new BadRequestObjectResult(response.Errors);
|
||||
}
|
||||
|
||||
return new OkObjectResult(response.Data);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Post([FromBody]FileConfiguration fileConfiguration)
|
||||
{
|
||||
//todo - this code is a bit shit sort it out..
|
||||
var test = _provider.GetService(typeof(INode));
|
||||
if (test != null)
|
||||
{
|
||||
var node = (INode)test;
|
||||
var result = node.Accept(new UpdateFileConfiguration(fileConfiguration));
|
||||
if (result.GetType() == typeof(Rafty.Concensus.ErrorResponse<UpdateFileConfiguration>))
|
||||
{
|
||||
return new BadRequestObjectResult("There was a problem. This error message sucks raise an issue in GitHub.");
|
||||
}
|
||||
|
||||
return new OkObjectResult(result.Command.Configuration);
|
||||
}
|
||||
|
||||
var response = await _setter.Set(fileConfiguration);
|
||||
|
||||
if (response.IsError)
|
||||
{
|
||||
return new BadRequestObjectResult(response.Errors);
|
||||
}
|
||||
|
||||
return new OkObjectResult(fileConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Ocelot.Raft;
|
||||
using Rafty.Concensus;
|
||||
|
||||
namespace Ocelot.Configuration
|
||||
{
|
||||
using Repository;
|
||||
|
||||
[Authorize]
|
||||
[Route("configuration")]
|
||||
public class FileConfigurationController : Controller
|
||||
{
|
||||
private readonly IFileConfigurationRepository _repo;
|
||||
private readonly IFileConfigurationSetter _setter;
|
||||
private readonly IServiceProvider _provider;
|
||||
|
||||
public FileConfigurationController(IFileConfigurationRepository repo, IFileConfigurationSetter setter, IServiceProvider provider)
|
||||
{
|
||||
_repo = repo;
|
||||
_setter = setter;
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Get()
|
||||
{
|
||||
var response = await _repo.Get();
|
||||
|
||||
if(response.IsError)
|
||||
{
|
||||
return new BadRequestObjectResult(response.Errors);
|
||||
}
|
||||
|
||||
return new OkObjectResult(response.Data);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Post([FromBody]FileConfiguration fileConfiguration)
|
||||
{
|
||||
try
|
||||
{
|
||||
//todo - this code is a bit shit sort it out..
|
||||
var test = _provider.GetService(typeof(INode));
|
||||
if (test != null)
|
||||
{
|
||||
var node = (INode)test;
|
||||
var result = await node.Accept(new UpdateFileConfiguration(fileConfiguration));
|
||||
if (result.GetType() == typeof(Rafty.Concensus.ErrorResponse<UpdateFileConfiguration>))
|
||||
{
|
||||
return new BadRequestObjectResult("There was a problem. This error message sucks raise an issue in GitHub.");
|
||||
}
|
||||
|
||||
return new OkObjectResult(result.Command.Configuration);
|
||||
}
|
||||
|
||||
var response = await _setter.Set(fileConfiguration);
|
||||
|
||||
if (response.IsError)
|
||||
{
|
||||
return new BadRequestObjectResult(response.Errors);
|
||||
}
|
||||
|
||||
return new OkObjectResult(fileConfiguration);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
return new BadRequestObjectResult($"{e.Message}:{e.StackTrace}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ namespace Ocelot.Configuration.Repository
|
||||
_polling = true;
|
||||
await Poll();
|
||||
_polling = false;
|
||||
}, null, 0, _config.Delay);
|
||||
}, null, _config.Delay, _config.Delay);
|
||||
}
|
||||
|
||||
private async Task Poll()
|
||||
|
@ -1,96 +1,99 @@
|
||||
namespace Ocelot.Configuration.Repository
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Consul;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Infrastructure.Consul;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.ServiceDiscovery.Configuration;
|
||||
|
||||
public class ConsulFileConfigurationRepository : IFileConfigurationRepository
|
||||
{
|
||||
private readonly IConsulClient _consul;
|
||||
private const string OcelotConfiguration = "InternalConfiguration";
|
||||
private readonly Cache.IOcelotCache<FileConfiguration> _cache;
|
||||
private readonly IOcelotLogger _logger;
|
||||
|
||||
public ConsulFileConfigurationRepository(
|
||||
Cache.IOcelotCache<FileConfiguration> cache,
|
||||
IInternalConfigurationRepository repo,
|
||||
IConsulClientFactory factory,
|
||||
IOcelotLoggerFactory loggerFactory)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger<ConsulFileConfigurationRepository>();
|
||||
_cache = cache;
|
||||
|
||||
var internalConfig = repo.Get();
|
||||
|
||||
var consulHost = "localhost";
|
||||
var consulPort = 8500;
|
||||
string token = null;
|
||||
|
||||
if (!internalConfig.IsError)
|
||||
{
|
||||
consulHost = string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration?.Host) ? consulHost : internalConfig.Data.ServiceProviderConfiguration?.Host;
|
||||
consulPort = internalConfig.Data.ServiceProviderConfiguration?.Port ?? consulPort;
|
||||
token = internalConfig.Data.ServiceProviderConfiguration?.Token;
|
||||
}
|
||||
|
||||
var config = new ConsulRegistryConfiguration(consulHost, consulPort, OcelotConfiguration, token);
|
||||
|
||||
_consul = factory.Get(config);
|
||||
}
|
||||
|
||||
public async Task<Response<FileConfiguration>> Get()
|
||||
{
|
||||
var config = _cache.Get(OcelotConfiguration, OcelotConfiguration);
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
return new OkResponse<FileConfiguration>(config);
|
||||
}
|
||||
|
||||
var queryResult = await _consul.KV.Get(OcelotConfiguration);
|
||||
|
||||
if (queryResult.Response == null)
|
||||
{
|
||||
return new OkResponse<FileConfiguration>(null);
|
||||
}
|
||||
|
||||
var bytes = queryResult.Response.Value;
|
||||
|
||||
var json = Encoding.UTF8.GetString(bytes);
|
||||
|
||||
var consulConfig = JsonConvert.DeserializeObject<FileConfiguration>(json);
|
||||
|
||||
return new OkResponse<FileConfiguration>(consulConfig);
|
||||
}
|
||||
|
||||
public async Task<Response> Set(FileConfiguration ocelotConfiguration)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
var kvPair = new KVPair(OcelotConfiguration)
|
||||
{
|
||||
Value = bytes
|
||||
};
|
||||
|
||||
var result = await _consul.KV.Put(kvPair);
|
||||
|
||||
if (result.Response)
|
||||
{
|
||||
_cache.AddAndDelete(OcelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3), OcelotConfiguration);
|
||||
|
||||
return new OkResponse();
|
||||
}
|
||||
|
||||
return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace Ocelot.Configuration.Repository
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Consul;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Infrastructure.Consul;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.ServiceDiscovery.Configuration;
|
||||
|
||||
public class ConsulFileConfigurationRepository : IFileConfigurationRepository
|
||||
{
|
||||
private readonly IConsulClient _consul;
|
||||
private readonly string _configurationKey;
|
||||
private readonly Cache.IOcelotCache<FileConfiguration> _cache;
|
||||
private readonly IOcelotLogger _logger;
|
||||
|
||||
public ConsulFileConfigurationRepository(
|
||||
Cache.IOcelotCache<FileConfiguration> cache,
|
||||
IInternalConfigurationRepository repo,
|
||||
IConsulClientFactory factory,
|
||||
IOcelotLoggerFactory loggerFactory)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger<ConsulFileConfigurationRepository>();
|
||||
_cache = cache;
|
||||
|
||||
var internalConfig = repo.Get();
|
||||
|
||||
_configurationKey = "InternalConfiguration";
|
||||
var consulHost = "localhost";
|
||||
var consulPort = 8500;
|
||||
string token = null;
|
||||
|
||||
if (!internalConfig.IsError)
|
||||
{
|
||||
consulHost = string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration?.Host) ? consulHost : internalConfig.Data.ServiceProviderConfiguration?.Host;
|
||||
consulPort = internalConfig.Data.ServiceProviderConfiguration?.Port ?? consulPort;
|
||||
token = internalConfig.Data.ServiceProviderConfiguration?.Token;
|
||||
_configurationKey = !string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration?.ConfigurationKey) ?
|
||||
internalConfig.Data.ServiceProviderConfiguration?.ConfigurationKey : _configurationKey;
|
||||
}
|
||||
|
||||
var config = new ConsulRegistryConfiguration(consulHost, consulPort, _configurationKey, token);
|
||||
|
||||
_consul = factory.Get(config);
|
||||
}
|
||||
|
||||
public async Task<Response<FileConfiguration>> Get()
|
||||
{
|
||||
var config = _cache.Get(_configurationKey, _configurationKey);
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
return new OkResponse<FileConfiguration>(config);
|
||||
}
|
||||
|
||||
var queryResult = await _consul.KV.Get(_configurationKey);
|
||||
|
||||
if (queryResult.Response == null)
|
||||
{
|
||||
return new OkResponse<FileConfiguration>(null);
|
||||
}
|
||||
|
||||
var bytes = queryResult.Response.Value;
|
||||
|
||||
var json = Encoding.UTF8.GetString(bytes);
|
||||
|
||||
var consulConfig = JsonConvert.DeserializeObject<FileConfiguration>(json);
|
||||
|
||||
return new OkResponse<FileConfiguration>(consulConfig);
|
||||
}
|
||||
|
||||
public async Task<Response> Set(FileConfiguration ocelotConfiguration)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
var kvPair = new KVPair(_configurationKey)
|
||||
{
|
||||
Value = bytes
|
||||
};
|
||||
|
||||
var result = await _consul.KV.Put(kvPair);
|
||||
|
||||
if (result.Response)
|
||||
{
|
||||
_cache.AddAndDelete(_configurationKey, ocelotConfiguration, TimeSpan.FromSeconds(3), _configurationKey);
|
||||
|
||||
return new OkResponse();
|
||||
}
|
||||
|
||||
return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,9 @@
|
||||
{
|
||||
public class ServiceProviderConfiguration
|
||||
{
|
||||
public ServiceProviderConfiguration(string type, string host, int port, string token)
|
||||
public ServiceProviderConfiguration(string type, string host, int port, string token, string configurationKey)
|
||||
{
|
||||
ConfigurationKey = configurationKey;
|
||||
Host = host;
|
||||
Port = port;
|
||||
Token = token;
|
||||
@ -14,5 +15,6 @@
|
||||
public int Port { get; }
|
||||
public string Type { get; }
|
||||
public string Token { get; }
|
||||
public string ConfigurationKey { get; }
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ namespace Ocelot.Headers
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Configuration.Creator;
|
||||
using Ocelot.Infrastructure;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
|
15
src/Ocelot/Infrastructure/DelayedMessage.cs
Normal file
15
src/Ocelot/Infrastructure/DelayedMessage.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace Ocelot.Infrastructure
|
||||
{
|
||||
internal class DelayedMessage<T>
|
||||
{
|
||||
public DelayedMessage(T message, int delay)
|
||||
{
|
||||
Delay = delay;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public T Message { get; set; }
|
||||
|
||||
public int Delay { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Ocelot.Infrastructure.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Trivial implementations of methods present in .NET Core 2 but not supported on .NET Standard 2.0.
|
||||
/// </summary>
|
||||
internal static class NetCoreSupportExtensions
|
||||
{
|
||||
internal static void AppendJoin<T>(this StringBuilder builder, char separator, IEnumerable<T> values)
|
||||
{
|
||||
builder.Append(string.Join(separator.ToString(), values));
|
||||
}
|
||||
|
||||
internal static string[] Split(this string input, string separator, StringSplitOptions options = StringSplitOptions.None)
|
||||
{
|
||||
return input.Split(new[] { separator }, options);
|
||||
}
|
||||
|
||||
internal static bool StartsWith(this string input, char value)
|
||||
{
|
||||
return input.StartsWith(value.ToString());
|
||||
}
|
||||
|
||||
internal static bool EndsWith(this string input, char value)
|
||||
{
|
||||
return input.EndsWith(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
10
src/Ocelot/Infrastructure/IBus.cs
Normal file
10
src/Ocelot/Infrastructure/IBus.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Ocelot.Infrastructure
|
||||
{
|
||||
public interface IBus<T>
|
||||
{
|
||||
void Subscribe(Action<T> action);
|
||||
void Publish(T message, int delay);
|
||||
}
|
||||
}
|
47
src/Ocelot/Infrastructure/InMemoryBus.cs
Normal file
47
src/Ocelot/Infrastructure/InMemoryBus.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ocelot.Infrastructure
|
||||
{
|
||||
public class InMemoryBus<T> : IBus<T>
|
||||
{
|
||||
private readonly BlockingCollection<DelayedMessage<T>> _queue;
|
||||
private readonly List<Action<T>> _subscriptions;
|
||||
private Thread _processing;
|
||||
|
||||
public InMemoryBus()
|
||||
{
|
||||
_queue = new BlockingCollection<DelayedMessage<T>>();
|
||||
_subscriptions = new List<Action<T>>();
|
||||
_processing = new Thread(async () => await Process());
|
||||
_processing.Start();
|
||||
}
|
||||
|
||||
public void Subscribe(Action<T> action)
|
||||
{
|
||||
_subscriptions.Add(action);
|
||||
}
|
||||
|
||||
public void Publish(T message, int delay)
|
||||
{
|
||||
var delayed = new DelayedMessage<T>(message, delay);
|
||||
_queue.Add(delayed);
|
||||
}
|
||||
|
||||
private async Task Process()
|
||||
{
|
||||
foreach(var delayedMessage in _queue.GetConsumingEnumerable())
|
||||
{
|
||||
await Task.Delay(delayedMessage.Delay);
|
||||
|
||||
foreach (var subscription in _subscriptions)
|
||||
{
|
||||
subscription(delayedMessage.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,61 +3,64 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Infrastructure;
|
||||
using Ocelot.Middleware;
|
||||
using Responses;
|
||||
using Values;
|
||||
|
||||
public class CookieStickySessions : ILoadBalancer, IDisposable
|
||||
public class CookieStickySessions : ILoadBalancer
|
||||
{
|
||||
private readonly int _expiryInMs;
|
||||
private readonly int _keyExpiryInMs;
|
||||
private readonly string _key;
|
||||
private readonly ILoadBalancer _loadBalancer;
|
||||
private readonly ConcurrentDictionary<string, StickySession> _stored;
|
||||
private readonly Timer _timer;
|
||||
private bool _expiring;
|
||||
private readonly IBus<StickySession> _bus;
|
||||
private readonly object _lock = new object();
|
||||
|
||||
public CookieStickySessions(ILoadBalancer loadBalancer, string key, int expiryInMs)
|
||||
public CookieStickySessions(ILoadBalancer loadBalancer, string key, int keyExpiryInMs, IBus<StickySession> bus)
|
||||
{
|
||||
_bus = bus;
|
||||
_key = key;
|
||||
_expiryInMs = expiryInMs;
|
||||
_keyExpiryInMs = keyExpiryInMs;
|
||||
_loadBalancer = loadBalancer;
|
||||
_stored = new ConcurrentDictionary<string, StickySession>();
|
||||
_timer = new Timer(x =>
|
||||
_bus.Subscribe(ss =>
|
||||
{
|
||||
if (_expiring)
|
||||
//todo - get test coverage for this.
|
||||
if (_stored.TryGetValue(ss.Key, out var stickySession))
|
||||
{
|
||||
return;
|
||||
lock (_lock)
|
||||
{
|
||||
if (stickySession.Expiry < DateTime.UtcNow)
|
||||
{
|
||||
_stored.TryRemove(stickySession.Key, out _);
|
||||
_loadBalancer.Release(stickySession.HostAndPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_expiring = true;
|
||||
|
||||
Expire();
|
||||
|
||||
_expiring = false;
|
||||
}, null, 0, 50);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer?.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context)
|
||||
{
|
||||
var value = context.HttpContext.Request.Cookies[_key];
|
||||
var key = context.HttpContext.Request.Cookies[_key];
|
||||
|
||||
if (!string.IsNullOrEmpty(value) && _stored.ContainsKey(value))
|
||||
lock (_lock)
|
||||
{
|
||||
var cached = _stored[value];
|
||||
if (!string.IsNullOrEmpty(key) && _stored.ContainsKey(key))
|
||||
{
|
||||
var cached = _stored[key];
|
||||
|
||||
var updated = new StickySession(cached.HostAndPort, DateTime.UtcNow.AddMilliseconds(_expiryInMs));
|
||||
var updated = new StickySession(cached.HostAndPort, DateTime.UtcNow.AddMilliseconds(_keyExpiryInMs), key);
|
||||
|
||||
_stored[value] = updated;
|
||||
_stored[key] = updated;
|
||||
|
||||
return new OkResponse<ServiceHostAndPort>(updated.HostAndPort);
|
||||
_bus.Publish(updated, _keyExpiryInMs);
|
||||
|
||||
return new OkResponse<ServiceHostAndPort>(updated.HostAndPort);
|
||||
}
|
||||
}
|
||||
|
||||
var next = await _loadBalancer.Lease(context);
|
||||
@ -67,9 +70,14 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
return new ErrorResponse<ServiceHostAndPort>(next.Errors);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(value) && !_stored.ContainsKey(value))
|
||||
lock (_lock)
|
||||
{
|
||||
_stored[value] = new StickySession(next.Data, DateTime.UtcNow.AddMilliseconds(_expiryInMs));
|
||||
if (!string.IsNullOrEmpty(key) && !_stored.ContainsKey(key))
|
||||
{
|
||||
var ss = new StickySession(next.Data, DateTime.UtcNow.AddMilliseconds(_keyExpiryInMs), key);
|
||||
_stored[key] = ss;
|
||||
_bus.Publish(ss, _keyExpiryInMs);
|
||||
}
|
||||
}
|
||||
|
||||
return new OkResponse<ServiceHostAndPort>(next.Data);
|
||||
@ -78,16 +86,5 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
public void Release(ServiceHostAndPort hostAndPort)
|
||||
{
|
||||
}
|
||||
|
||||
private void Expire()
|
||||
{
|
||||
var expired = _stored.Where(x => x.Value.Expiry < DateTime.UtcNow);
|
||||
|
||||
foreach (var expire in expired)
|
||||
{
|
||||
_stored.Remove(expire.Key, out _);
|
||||
_loadBalancer.Release(expire.Value.HostAndPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Infrastructure;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
@ -25,7 +26,8 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName);
|
||||
case nameof(CookieStickySessions):
|
||||
var loadBalancer = new RoundRobin(async () => await serviceProvider.Get());
|
||||
return new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs);
|
||||
var bus = new InMemoryBus<StickySession>();
|
||||
return new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs, bus);
|
||||
default:
|
||||
return new NoLoadBalancer(await serviceProvider.Get());
|
||||
}
|
||||
|
@ -5,14 +5,17 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class StickySession
|
||||
{
|
||||
public StickySession(ServiceHostAndPort hostAndPort, DateTime expiry)
|
||||
public StickySession(ServiceHostAndPort hostAndPort, DateTime expiry, string key)
|
||||
{
|
||||
HostAndPort = hostAndPort;
|
||||
Expiry = expiry;
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public ServiceHostAndPort HostAndPort { get; }
|
||||
|
||||
public DateTime Expiry { get; }
|
||||
|
||||
public string Key {get;}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
@ -25,18 +25,26 @@
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
||||
<PackageReference Include="Butterfly.Client" Version="0.0.8" />
|
||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8">
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentValidation" Version="7.5.2" />
|
||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.4.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" />
|
||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="2.0.3" />
|
||||
<PackageReference Include="Microsoft.Data.SQLite" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.0.1">
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
@ -45,9 +53,9 @@
|
||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.2" />
|
||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.2" />
|
||||
<PackageReference Include="Consul" Version="0.7.2.4" />
|
||||
<PackageReference Include="Polly" Version="5.8.0" />
|
||||
<PackageReference Include="Polly" Version="6.0.1" />
|
||||
<PackageReference Include="Pivotal.Discovery.Client" Version="1.1.0" />
|
||||
<PackageReference Include="IdentityServer4" Version="2.1.3" />
|
||||
<PackageReference Include="Rafty" Version="0.4.2" />
|
||||
<PackageReference Include="IdentityServer4" Version="2.2.0" />
|
||||
<PackageReference Include="Rafty" Version="0.4.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Rafty.FiniteStateMachine;
|
||||
using Rafty.Infrastructure;
|
||||
@ -17,7 +18,7 @@ namespace Ocelot.Raft
|
||||
_id = nodeId.Id.Replace("/","").Replace(":","");
|
||||
}
|
||||
|
||||
public void Handle(LogEntry log)
|
||||
public Task Handle(LogEntry log)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -28,6 +29,8 @@ namespace Ocelot.Raft
|
||||
{
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,128 +1,127 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Middleware;
|
||||
using Rafty.Concensus;
|
||||
using Rafty.FiniteStateMachine;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class HttpPeer : IPeer
|
||||
{
|
||||
private readonly string _hostAndPort;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly JsonSerializerSettings _jsonSerializerSettings;
|
||||
private readonly string _baseSchemeUrlAndPort;
|
||||
private BearerToken _token;
|
||||
private readonly IInternalConfiguration _config;
|
||||
private readonly IIdentityServerConfiguration _identityServerConfiguration;
|
||||
|
||||
public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IInternalConfiguration config, IIdentityServerConfiguration identityServerConfiguration)
|
||||
{
|
||||
_identityServerConfiguration = identityServerConfiguration;
|
||||
_config = config;
|
||||
Id = hostAndPort;
|
||||
_hostAndPort = hostAndPort;
|
||||
_httpClient = httpClient;
|
||||
_jsonSerializerSettings = new JsonSerializerSettings() {
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
_baseSchemeUrlAndPort = finder.Find();
|
||||
}
|
||||
|
||||
public string Id {get; private set;}
|
||||
|
||||
public RequestVoteResponse Request(RequestVote requestVote)
|
||||
{
|
||||
if(_token == null)
|
||||
{
|
||||
SetToken();
|
||||
}
|
||||
|
||||
var json = JsonConvert.SerializeObject(requestVote, _jsonSerializerSettings);
|
||||
var content = new StringContent(json);
|
||||
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||
var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content).GetAwaiter().GetResult();
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<RequestVoteResponse>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RequestVoteResponse(false, requestVote.Term);
|
||||
}
|
||||
}
|
||||
|
||||
public AppendEntriesResponse Request(AppendEntries appendEntries)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(_token == null)
|
||||
{
|
||||
SetToken();
|
||||
}
|
||||
|
||||
var json = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings);
|
||||
var content = new StringContent(json);
|
||||
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||
var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content).GetAwaiter().GetResult();
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<AppendEntriesResponse>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(),_jsonSerializerSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new AppendEntriesResponse(appendEntries.Term, false);
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
return new AppendEntriesResponse(appendEntries.Term, false);
|
||||
}
|
||||
}
|
||||
|
||||
public Response<T> Request<T>(T command)
|
||||
where T : ICommand
|
||||
{
|
||||
if(_token == null)
|
||||
{
|
||||
SetToken();
|
||||
}
|
||||
|
||||
var json = JsonConvert.SerializeObject(command, _jsonSerializerSettings);
|
||||
var content = new StringContent(json);
|
||||
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||
var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content).GetAwaiter().GetResult();
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<OkResponse<T>>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ErrorResponse<T>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), command);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetToken()
|
||||
{
|
||||
var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token";
|
||||
var formData = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("client_id", _identityServerConfiguration.ApiName),
|
||||
new KeyValuePair<string, string>("client_secret", _identityServerConfiguration.ApiSecret),
|
||||
new KeyValuePair<string, string>("scope", _identityServerConfiguration.ApiName),
|
||||
new KeyValuePair<string, string>("grant_type", "client_credentials")
|
||||
};
|
||||
var content = new FormUrlEncodedContent(formData);
|
||||
var response = _httpClient.PostAsync(tokenUrl, content).GetAwaiter().GetResult();
|
||||
var responseContent = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
response.EnsureSuccessStatusCode();
|
||||
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Middleware;
|
||||
using Rafty.Concensus;
|
||||
using Rafty.FiniteStateMachine;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class HttpPeer : IPeer
|
||||
{
|
||||
private readonly string _hostAndPort;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly JsonSerializerSettings _jsonSerializerSettings;
|
||||
private readonly string _baseSchemeUrlAndPort;
|
||||
private BearerToken _token;
|
||||
private readonly IInternalConfiguration _config;
|
||||
private readonly IIdentityServerConfiguration _identityServerConfiguration;
|
||||
|
||||
public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IInternalConfiguration config, IIdentityServerConfiguration identityServerConfiguration)
|
||||
{
|
||||
_identityServerConfiguration = identityServerConfiguration;
|
||||
_config = config;
|
||||
Id = hostAndPort;
|
||||
_hostAndPort = hostAndPort;
|
||||
_httpClient = httpClient;
|
||||
_jsonSerializerSettings = new JsonSerializerSettings() {
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
_baseSchemeUrlAndPort = finder.Find();
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public async Task<RequestVoteResponse> Request(RequestVote requestVote)
|
||||
{
|
||||
if(_token == null)
|
||||
{
|
||||
await SetToken();
|
||||
}
|
||||
|
||||
var json = JsonConvert.SerializeObject(requestVote, _jsonSerializerSettings);
|
||||
var content = new StringContent(json);
|
||||
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||
var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content);
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<RequestVoteResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
||||
}
|
||||
|
||||
return new RequestVoteResponse(false, requestVote.Term);
|
||||
}
|
||||
|
||||
public async Task<AppendEntriesResponse> Request(AppendEntries appendEntries)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(_token == null)
|
||||
{
|
||||
await SetToken();
|
||||
}
|
||||
|
||||
var json = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings);
|
||||
var content = new StringContent(json);
|
||||
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||
var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content);
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<AppendEntriesResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
||||
}
|
||||
|
||||
return new AppendEntriesResponse(appendEntries.Term, false);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
return new AppendEntriesResponse(appendEntries.Term, false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Response<T>> Request<T>(T command)
|
||||
where T : ICommand
|
||||
{
|
||||
Console.WriteLine("SENDING REQUEST....");
|
||||
if(_token == null)
|
||||
{
|
||||
await SetToken();
|
||||
}
|
||||
|
||||
var json = JsonConvert.SerializeObject(command, _jsonSerializerSettings);
|
||||
var content = new StringContent(json);
|
||||
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||
var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content);
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine("REQUEST OK....");
|
||||
var okResponse = JsonConvert.DeserializeObject<OkResponse<ICommand>>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
||||
return new OkResponse<T>((T)okResponse.Command);
|
||||
}
|
||||
|
||||
Console.WriteLine("REQUEST NOT OK....");
|
||||
return new ErrorResponse<T>(await response.Content.ReadAsStringAsync(), command);
|
||||
}
|
||||
|
||||
private async Task SetToken()
|
||||
{
|
||||
var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token";
|
||||
var formData = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("client_id", _identityServerConfiguration.ApiName),
|
||||
new KeyValuePair<string, string>("client_secret", _identityServerConfiguration.ApiSecret),
|
||||
new KeyValuePair<string, string>("scope", _identityServerConfiguration.ApiName),
|
||||
new KeyValuePair<string, string>("grant_type", "client_credentials")
|
||||
};
|
||||
var content = new FormUrlEncodedContent(formData);
|
||||
var response = await _httpClient.PostAsync(tokenUrl, content);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
response.EnsureSuccessStatusCode();
|
||||
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Rafty.FiniteStateMachine;
|
||||
using Rafty.Log;
|
||||
@ -14,12 +15,12 @@ namespace Ocelot.Raft
|
||||
_setter = setter;
|
||||
}
|
||||
|
||||
public void Handle(LogEntry log)
|
||||
public async Task Handle(LogEntry log)
|
||||
{
|
||||
//todo - handle an error
|
||||
//hack it to just cast as at the moment we know this is the only command :P
|
||||
var hack = (UpdateFileConfiguration)log.CommandData;
|
||||
_setter.Set(hack.Configuration).GetAwaiter().GetResult();
|
||||
await _setter.Set(hack.Configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,9 +20,9 @@ namespace Ocelot.Raft
|
||||
public class RaftController : Controller
|
||||
{
|
||||
private readonly INode _node;
|
||||
private IOcelotLogger _logger;
|
||||
private string _baseSchemeUrlAndPort;
|
||||
private JsonSerializerSettings _jsonSerialiserSettings;
|
||||
private readonly IOcelotLogger _logger;
|
||||
private readonly string _baseSchemeUrlAndPort;
|
||||
private readonly JsonSerializerSettings _jsonSerialiserSettings;
|
||||
|
||||
public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IBaseUrlFinder finder)
|
||||
{
|
||||
@ -45,7 +45,7 @@ namespace Ocelot.Raft
|
||||
|
||||
_logger.LogDebug($"{_baseSchemeUrlAndPort}/appendentries called, my state is {_node.State.GetType().FullName}");
|
||||
|
||||
var appendEntriesResponse = _node.Handle(appendEntries);
|
||||
var appendEntriesResponse = await _node.Handle(appendEntries);
|
||||
|
||||
return new OkObjectResult(appendEntriesResponse);
|
||||
}
|
||||
@ -62,7 +62,7 @@ namespace Ocelot.Raft
|
||||
|
||||
_logger.LogDebug($"{_baseSchemeUrlAndPort}/requestvote called, my state is {_node.State.GetType().FullName}");
|
||||
|
||||
var requestVoteResponse = _node.Handle(requestVote);
|
||||
var requestVoteResponse = await _node.Handle(requestVote);
|
||||
|
||||
return new OkObjectResult(requestVoteResponse);
|
||||
}
|
||||
@ -81,7 +81,7 @@ namespace Ocelot.Raft
|
||||
|
||||
_logger.LogDebug($"{_baseSchemeUrlAndPort}/command called, my state is {_node.State.GetType().FullName}");
|
||||
|
||||
var commandResponse = _node.Accept(command);
|
||||
var commandResponse = await _node.Accept(command);
|
||||
|
||||
json = JsonConvert.SerializeObject(commandResponse, _jsonSerialiserSettings);
|
||||
|
||||
@ -91,7 +91,7 @@ namespace Ocelot.Raft
|
||||
catch(Exception e)
|
||||
{
|
||||
_logger.LogError($"THERE WAS A PROBLEM ON NODE {_node.State.CurrentState.Id}", e);
|
||||
throw e;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,11 @@ using Newtonsoft.Json;
|
||||
using System;
|
||||
using Rafty.Infrastructure;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
{
|
||||
//todo - use async await
|
||||
[ExcludeFromCoverage]
|
||||
public class SqlLiteLog : ILog
|
||||
{
|
||||
@ -40,89 +42,80 @@ namespace Ocelot.Raft
|
||||
}
|
||||
}
|
||||
|
||||
public int LastLogIndex
|
||||
public Task<int> LastLogIndex()
|
||||
{
|
||||
get
|
||||
lock(_lock)
|
||||
{
|
||||
lock(_lock)
|
||||
var result = 1;
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
var result = 1;
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
connection.Open();
|
||||
var sql = @"select id from logs order by id desc limit 1";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
connection.Open();
|
||||
var sql = @"select id from logs order by id desc limit 1";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
var index = Convert.ToInt32(command.ExecuteScalar());
|
||||
if(index > result)
|
||||
{
|
||||
var index = Convert.ToInt32(command.ExecuteScalar());
|
||||
if(index > result)
|
||||
{
|
||||
result = index;
|
||||
}
|
||||
result = index;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
public long LastLogTerm
|
||||
public Task<long> LastLogTerm ()
|
||||
{
|
||||
get
|
||||
lock(_lock)
|
||||
{
|
||||
lock(_lock)
|
||||
long result = 0;
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
long result = 0;
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
connection.Open();
|
||||
var sql = @"select data from logs order by id desc limit 1";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
connection.Open();
|
||||
var sql = @"select data from logs order by id desc limit 1";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
var data = Convert.ToString(command.ExecuteScalar());
|
||||
var jsonSerializerSettings = new JsonSerializerSettings() {
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
if(log != null && log.Term > result)
|
||||
{
|
||||
var data = Convert.ToString(command.ExecuteScalar());
|
||||
var jsonSerializerSettings = new JsonSerializerSettings() {
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
if(log != null && log.Term > result)
|
||||
{
|
||||
result = log.Term;
|
||||
}
|
||||
result = log.Term;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
public Task<int> Count ()
|
||||
{
|
||||
get
|
||||
lock(_lock)
|
||||
{
|
||||
lock(_lock)
|
||||
var result = 0;
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
var result = 0;
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
connection.Open();
|
||||
var sql = @"select count(id) from logs";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
connection.Open();
|
||||
var sql = @"select count(id) from logs";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
var index = Convert.ToInt32(command.ExecuteScalar());
|
||||
if(index > result)
|
||||
{
|
||||
var index = Convert.ToInt32(command.ExecuteScalar());
|
||||
if(index > result)
|
||||
{
|
||||
result = index;
|
||||
}
|
||||
result = index;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
public int Apply(LogEntry log)
|
||||
public Task<int> Apply(LogEntry log)
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
@ -145,13 +138,13 @@ namespace Ocelot.Raft
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var result = command.ExecuteScalar();
|
||||
return Convert.ToInt32(result);
|
||||
return Task.FromResult(Convert.ToInt32(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteConflictsFromThisLog(int index, LogEntry logEntry)
|
||||
public Task DeleteConflictsFromThisLog(int index, LogEntry logEntry)
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
@ -174,15 +167,17 @@ namespace Ocelot.Raft
|
||||
var deleteSql = $"delete from logs where id >= {index};";
|
||||
using(var deleteCommand = new SqliteCommand(deleteSql, connection))
|
||||
{
|
||||
var result = deleteCommand.ExecuteNonQuery();
|
||||
var result = deleteCommand.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public LogEntry Get(int index)
|
||||
public Task<LogEntry> Get(int index)
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
@ -199,13 +194,13 @@ namespace Ocelot.Raft
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
return log;
|
||||
return Task.FromResult(log);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public System.Collections.Generic.List<(int index, LogEntry logEntry)> GetFrom(int index)
|
||||
public Task<List<(int index, LogEntry logEntry)>> GetFrom(int index)
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
@ -235,11 +230,11 @@ namespace Ocelot.Raft
|
||||
}
|
||||
}
|
||||
|
||||
return logsToReturn;
|
||||
return Task.FromResult(logsToReturn);
|
||||
}
|
||||
}
|
||||
|
||||
public long GetTermAtIndex(int index)
|
||||
public Task<long> GetTermAtIndex(int index)
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
@ -264,11 +259,11 @@ namespace Ocelot.Raft
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(int indexOfCommand)
|
||||
public Task Remove(int indexOfCommand)
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
@ -284,6 +279,8 @@ namespace Ocelot.Raft
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ namespace Ocelot.Requester
|
||||
|
||||
if(context.DownstreamReRoute.DangerousAcceptAnyServerCertificateValidator)
|
||||
{
|
||||
httpclientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
|
||||
httpclientHandler.ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => true;
|
||||
|
||||
_logger
|
||||
.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {context.DownstreamReRoute.DownstreamPathTemplate}");
|
||||
|
@ -316,8 +316,6 @@ namespace Ocelot.AcceptanceTests
|
||||
{
|
||||
new Scope("api2"),
|
||||
new Scope("api2.readOnly"),
|
||||
new Scope("openid"),
|
||||
new Scope("offline_access")
|
||||
},
|
||||
ApiSecrets = new List<Secret>()
|
||||
{
|
||||
|
@ -34,26 +34,27 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CacheManager.Serialization.Json" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
|
||||
<PackageReference Include="Shouldly" Version="3.0.0" />
|
||||
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
|
||||
<PackageReference Include="Consul" Version="0.7.2.4" />
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
||||
<PackageReference Include="Rafty" Version="0.4.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -76,16 +76,16 @@ namespace Ocelot.Benchmarks
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
// * Summary*
|
||||
// BenchmarkDotNet = v0.10.13, OS = macOS 10.12.6 (16G1212) [Darwin 16.7.0]
|
||||
// Intel Core i5-4278U CPU 2.60GHz(Haswell), 1 CPU, 4 logical cores and 2 physical cores
|
||||
//.NET Core SDK = 2.1.4
|
||||
/* * Summary*
|
||||
BenchmarkDotNet = v0.10.13, OS = macOS 10.12.6 (16G1212) [Darwin 16.7.0]
|
||||
Intel Core i5-4278U CPU 2.60GHz(Haswell), 1 CPU, 4 logical cores and 2 physical cores
|
||||
.NET Core SDK = 2.1.4
|
||||
|
||||
// [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
|
||||
// DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
|
||||
// Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated |
|
||||
// --------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:|
|
||||
// Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB |
|
||||
[Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
|
||||
DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
|
||||
Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated |
|
||||
--------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:|
|
||||
Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB |*/
|
||||
|
||||
private void GivenOcelotIsRunning(string url)
|
||||
{
|
||||
|
@ -19,7 +19,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.10.13" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.10.14" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -25,26 +25,26 @@
|
||||
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="IdentityServer4" Version="2.1.3" />
|
||||
<PackageReference Include="IdentityServer4" Version="2.2.0" />
|
||||
<PackageReference Include="Shouldly" Version="3.0.0" />
|
||||
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
|
||||
<PackageReference Include="Consul" Version="0.7.2.4" />
|
||||
<PackageReference Include="Rafty" Version="0.4.2" />
|
||||
<PackageReference Include="Rafty" Version="0.4.3" />
|
||||
<PackageReference Include="Microsoft.Data.SQLite" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,393 +1,450 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Raft;
|
||||
using Rafty.Concensus;
|
||||
using Rafty.Infrastructure;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using static Rafty.Infrastructure.Wait;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Ocelot.DependencyInjection;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
namespace Ocelot.IntegrationTests
|
||||
{
|
||||
public class RaftTests : IDisposable
|
||||
{
|
||||
private readonly List<IWebHost> _builders;
|
||||
private readonly List<IWebHostBuilder> _webHostBuilders;
|
||||
private readonly List<Thread> _threads;
|
||||
private FilePeers _peers;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly HttpClient _httpClientForAssertions;
|
||||
private BearerToken _token;
|
||||
private HttpResponseMessage _response;
|
||||
private static readonly object _lock = new object();
|
||||
|
||||
public RaftTests()
|
||||
{
|
||||
_httpClientForAssertions = new HttpClient();
|
||||
_httpClient = new HttpClient();
|
||||
var ocelotBaseUrl = "http://localhost:5000";
|
||||
_httpClient.BaseAddress = new Uri(ocelotBaseUrl);
|
||||
_webHostBuilders = new List<IWebHostBuilder>();
|
||||
_builders = new List<IWebHost>();
|
||||
_threads = new List<Thread>();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var builder in _builders)
|
||||
{
|
||||
builder?.Dispose();
|
||||
}
|
||||
|
||||
foreach (var peer in _peers.Peers)
|
||||
{
|
||||
File.Delete(peer.HostAndPort.Replace("/","").Replace(":",""));
|
||||
File.Delete($"{peer.HostAndPort.Replace("/","").Replace(":","")}.db");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Skip = "This tests is flakey at the moment so ignoring will be fixed long term see https://github.com/TomPallister/Ocelot/issues/245")]
|
||||
public void should_persist_command_to_five_servers()
|
||||
{
|
||||
var configuration = new FileConfiguration
|
||||
{
|
||||
GlobalConfiguration = new FileGlobalConfiguration
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
var updatedConfiguration = new FileConfiguration
|
||||
{
|
||||
GlobalConfiguration = new FileGlobalConfiguration
|
||||
{
|
||||
},
|
||||
ReRoutes = new List<FileReRoute>()
|
||||
{
|
||||
new FileReRoute()
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
Port = 80,
|
||||
}
|
||||
},
|
||||
DownstreamScheme = "http",
|
||||
DownstreamPathTemplate = "/geoffrey",
|
||||
UpstreamHttpMethod = new List<string> { "get" },
|
||||
UpstreamPathTemplate = "/"
|
||||
},
|
||||
new FileReRoute()
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "123.123.123",
|
||||
Port = 443,
|
||||
}
|
||||
},
|
||||
DownstreamScheme = "https",
|
||||
DownstreamPathTemplate = "/blooper/{productId}",
|
||||
UpstreamHttpMethod = new List<string> { "post" },
|
||||
UpstreamPathTemplate = "/test"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var command = new UpdateFileConfiguration(updatedConfiguration);
|
||||
GivenThereIsAConfiguration(configuration);
|
||||
GivenFiveServersAreRunning();
|
||||
GivenALeaderIsElected();
|
||||
GivenIHaveAnOcelotToken("/administration");
|
||||
WhenISendACommandIntoTheCluster(command);
|
||||
ThenTheCommandIsReplicatedToAllStateMachines(command);
|
||||
}
|
||||
|
||||
[Fact(Skip = "This tests is flakey at the moment so ignoring will be fixed long term see https://github.com/TomPallister/Ocelot/issues/245")]
|
||||
public void should_persist_command_to_five_servers_when_using_administration_api()
|
||||
{
|
||||
var configuration = new FileConfiguration
|
||||
{
|
||||
};
|
||||
|
||||
var updatedConfiguration = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>()
|
||||
{
|
||||
new FileReRoute()
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
Port = 80,
|
||||
}
|
||||
},
|
||||
DownstreamScheme = "http",
|
||||
DownstreamPathTemplate = "/geoffrey",
|
||||
UpstreamHttpMethod = new List<string> { "get" },
|
||||
UpstreamPathTemplate = "/"
|
||||
},
|
||||
new FileReRoute()
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "123.123.123",
|
||||
Port = 443,
|
||||
}
|
||||
},
|
||||
DownstreamScheme = "https",
|
||||
DownstreamPathTemplate = "/blooper/{productId}",
|
||||
UpstreamHttpMethod = new List<string> { "post" },
|
||||
UpstreamPathTemplate = "/test"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var command = new UpdateFileConfiguration(updatedConfiguration);
|
||||
GivenThereIsAConfiguration(configuration);
|
||||
GivenFiveServersAreRunning();
|
||||
GivenALeaderIsElected();
|
||||
GivenIHaveAnOcelotToken("/administration");
|
||||
GivenIHaveAddedATokenToMyRequest();
|
||||
WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration);
|
||||
ThenTheCommandIsReplicatedToAllStateMachines(command);
|
||||
}
|
||||
|
||||
private void WhenISendACommandIntoTheCluster(UpdateFileConfiguration command)
|
||||
{
|
||||
var p = _peers.Peers.First();
|
||||
var json = JsonConvert.SerializeObject(command,new JsonSerializerSettings() {
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
});
|
||||
var httpContent = new StringContent(json);
|
||||
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||
using(var httpClient = new HttpClient())
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
||||
var response = httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent).GetAwaiter().GetResult();
|
||||
response.EnsureSuccessStatusCode();
|
||||
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
var result = JsonConvert.DeserializeObject<OkResponse<UpdateFileConfiguration>>(content);
|
||||
result.Command.Configuration.ReRoutes.Count.ShouldBe(2);
|
||||
}
|
||||
|
||||
//dirty sleep to make sure command replicated...
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
while(stopwatch.ElapsedMilliseconds < 10000)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expecteds)
|
||||
{
|
||||
//dirty sleep to give a chance to replicate...
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
while(stopwatch.ElapsedMilliseconds < 2000)
|
||||
{
|
||||
}
|
||||
|
||||
bool CommandCalledOnAllStateMachines()
|
||||
{
|
||||
try
|
||||
{
|
||||
var passed = 0;
|
||||
foreach (var peer in _peers.Peers)
|
||||
{
|
||||
var path = $"{peer.HostAndPort.Replace("/","").Replace(":","")}.db";
|
||||
using(var connection = new SqliteConnection($"Data Source={path};"))
|
||||
{
|
||||
connection.Open();
|
||||
var sql = @"select count(id) from logs";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var index = Convert.ToInt32(command.ExecuteScalar());
|
||||
index.ShouldBe(1);
|
||||
}
|
||||
}
|
||||
|
||||
_httpClientForAssertions.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
||||
var result = _httpClientForAssertions.GetAsync($"{peer.HostAndPort}/administration/configuration").Result;
|
||||
var json = result.Content.ReadAsStringAsync().Result;
|
||||
var response = JsonConvert.DeserializeObject<FileConfiguration>(json, new JsonSerializerSettings{TypeNameHandling = TypeNameHandling.All});
|
||||
response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.Configuration.GlobalConfiguration.RequestIdKey);
|
||||
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Host);
|
||||
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Port);
|
||||
|
||||
for (var i = 0; i < response.ReRoutes.Count; i++)
|
||||
{
|
||||
for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++)
|
||||
{
|
||||
var res = response.ReRoutes[i].DownstreamHostAndPorts[j];
|
||||
var expected = expecteds.Configuration.ReRoutes[i].DownstreamHostAndPorts[j];
|
||||
res.Host.ShouldBe(expected.Host);
|
||||
res.Port.ShouldBe(expected.Port);
|
||||
}
|
||||
|
||||
response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.Configuration.ReRoutes[i].DownstreamPathTemplate);
|
||||
response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.Configuration.ReRoutes[i].DownstreamScheme);
|
||||
response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.Configuration.ReRoutes[i].UpstreamPathTemplate);
|
||||
response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.Configuration.ReRoutes[i].UpstreamHttpMethod);
|
||||
}
|
||||
|
||||
passed++;
|
||||
}
|
||||
|
||||
return passed == 5;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var commandOnAllStateMachines = WaitFor(20000).Until(() => CommandCalledOnAllStateMachines());
|
||||
commandOnAllStateMachines.ShouldBeTrue();
|
||||
}
|
||||
|
||||
private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(updatedConfiguration);
|
||||
var content = new StringContent(json);
|
||||
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||
_response = _httpClient.PostAsync(url, content).Result;
|
||||
}
|
||||
|
||||
private void GivenIHaveAddedATokenToMyRequest()
|
||||
{
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
||||
}
|
||||
|
||||
private void GivenIHaveAnOcelotToken(string adminPath)
|
||||
{
|
||||
var tokenUrl = $"{adminPath}/connect/token";
|
||||
var formData = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("client_id", "admin"),
|
||||
new KeyValuePair<string, string>("client_secret", "secret"),
|
||||
new KeyValuePair<string, string>("scope", "admin"),
|
||||
new KeyValuePair<string, string>("grant_type", "client_credentials")
|
||||
};
|
||||
var content = new FormUrlEncodedContent(formData);
|
||||
|
||||
var response = _httpClient.PostAsync(tokenUrl, content).Result;
|
||||
var responseContent = response.Content.ReadAsStringAsync().Result;
|
||||
response.EnsureSuccessStatusCode();
|
||||
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
|
||||
var configPath = $"{adminPath}/.well-known/openid-configuration";
|
||||
response = _httpClient.GetAsync(configPath).Result;
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
|
||||
{
|
||||
var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json";
|
||||
|
||||
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
|
||||
|
||||
if (File.Exists(configurationPath))
|
||||
{
|
||||
File.Delete(configurationPath);
|
||||
}
|
||||
|
||||
File.WriteAllText(configurationPath, jsonConfiguration);
|
||||
|
||||
var text = File.ReadAllText(configurationPath);
|
||||
|
||||
configurationPath = $"{AppContext.BaseDirectory}/ocelot.json";
|
||||
|
||||
if (File.Exists(configurationPath))
|
||||
{
|
||||
File.Delete(configurationPath);
|
||||
}
|
||||
|
||||
File.WriteAllText(configurationPath, jsonConfiguration);
|
||||
|
||||
text = File.ReadAllText(configurationPath);
|
||||
}
|
||||
|
||||
private void GivenAServerIsRunning(string url)
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
IWebHostBuilder webHostBuilder = new WebHostBuilder();
|
||||
webHostBuilder.UseUrls(url)
|
||||
.UseKestrel()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||
{
|
||||
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
|
||||
var env = hostingContext.HostingEnvironment;
|
||||
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
||||
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
|
||||
config.AddJsonFile("ocelot.json");
|
||||
config.AddJsonFile("peers.json", optional: true, reloadOnChange: true);
|
||||
#pragma warning disable CS0618
|
||||
config.AddOcelotBaseUrl(url);
|
||||
#pragma warning restore CS0618
|
||||
config.AddEnvironmentVariables();
|
||||
})
|
||||
.ConfigureServices(x =>
|
||||
{
|
||||
x.AddSingleton(new NodeId(url));
|
||||
x
|
||||
.AddOcelot()
|
||||
.AddAdministration("/administration", "secret")
|
||||
.AddRafty();
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseOcelot().Wait();
|
||||
});
|
||||
|
||||
var builder = webHostBuilder.Build();
|
||||
builder.Start();
|
||||
|
||||
_webHostBuilders.Add(webHostBuilder);
|
||||
_builders.Add(builder);
|
||||
}
|
||||
}
|
||||
|
||||
private void GivenFiveServersAreRunning()
|
||||
{
|
||||
var bytes = File.ReadAllText("peers.json");
|
||||
_peers = JsonConvert.DeserializeObject<FilePeers>(bytes);
|
||||
|
||||
foreach (var peer in _peers.Peers)
|
||||
{
|
||||
var thread = new Thread(() => GivenAServerIsRunning(peer.HostAndPort));
|
||||
thread.Start();
|
||||
_threads.Add(thread);
|
||||
}
|
||||
}
|
||||
|
||||
private void GivenALeaderIsElected()
|
||||
{
|
||||
//dirty sleep to make sure we have a leader
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
while(stopwatch.ElapsedMilliseconds < 20000)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Raft;
|
||||
using Rafty.Concensus;
|
||||
using Rafty.Infrastructure;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using static Rafty.Infrastructure.Wait;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Ocelot.DependencyInjection;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
namespace Ocelot.IntegrationTests
|
||||
{
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public class RaftTests : IDisposable
|
||||
{
|
||||
private readonly List<IWebHost> _builders;
|
||||
private readonly List<IWebHostBuilder> _webHostBuilders;
|
||||
private readonly List<Thread> _threads;
|
||||
private FilePeers _peers;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly HttpClient _httpClientForAssertions;
|
||||
private BearerToken _token;
|
||||
private HttpResponseMessage _response;
|
||||
private static readonly object _lock = new object();
|
||||
private ITestOutputHelper _output;
|
||||
|
||||
public RaftTests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
_httpClientForAssertions = new HttpClient();
|
||||
_httpClient = new HttpClient();
|
||||
var ocelotBaseUrl = "http://localhost:5000";
|
||||
_httpClient.BaseAddress = new Uri(ocelotBaseUrl);
|
||||
_webHostBuilders = new List<IWebHostBuilder>();
|
||||
_builders = new List<IWebHost>();
|
||||
_threads = new List<Thread>();
|
||||
}
|
||||
|
||||
[Fact(Skip = "still broken waiting for work in rafty")]
|
||||
public void should_persist_command_to_five_servers()
|
||||
{
|
||||
var configuration = new FileConfiguration
|
||||
{
|
||||
GlobalConfiguration = new FileGlobalConfiguration
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
var updatedConfiguration = new FileConfiguration
|
||||
{
|
||||
GlobalConfiguration = new FileGlobalConfiguration
|
||||
{
|
||||
},
|
||||
ReRoutes = new List<FileReRoute>()
|
||||
{
|
||||
new FileReRoute()
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
Port = 80,
|
||||
}
|
||||
},
|
||||
DownstreamScheme = "http",
|
||||
DownstreamPathTemplate = "/geoffrey",
|
||||
UpstreamHttpMethod = new List<string> { "get" },
|
||||
UpstreamPathTemplate = "/"
|
||||
},
|
||||
new FileReRoute()
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "123.123.123",
|
||||
Port = 443,
|
||||
}
|
||||
},
|
||||
DownstreamScheme = "https",
|
||||
DownstreamPathTemplate = "/blooper/{productId}",
|
||||
UpstreamHttpMethod = new List<string> { "post" },
|
||||
UpstreamPathTemplate = "/test"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var command = new UpdateFileConfiguration(updatedConfiguration);
|
||||
GivenThereIsAConfiguration(configuration);
|
||||
GivenFiveServersAreRunning();
|
||||
GivenIHaveAnOcelotToken("/administration");
|
||||
WhenISendACommandIntoTheCluster(command);
|
||||
Thread.Sleep(5000);
|
||||
ThenTheCommandIsReplicatedToAllStateMachines(command);
|
||||
}
|
||||
|
||||
[Fact(Skip = "still broken waiting for work in rafty")]
|
||||
public void should_persist_command_to_five_servers_when_using_administration_api()
|
||||
{
|
||||
var configuration = new FileConfiguration
|
||||
{
|
||||
};
|
||||
|
||||
var updatedConfiguration = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>()
|
||||
{
|
||||
new FileReRoute()
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
Port = 80,
|
||||
}
|
||||
},
|
||||
DownstreamScheme = "http",
|
||||
DownstreamPathTemplate = "/geoffrey",
|
||||
UpstreamHttpMethod = new List<string> { "get" },
|
||||
UpstreamPathTemplate = "/"
|
||||
},
|
||||
new FileReRoute()
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "123.123.123",
|
||||
Port = 443,
|
||||
}
|
||||
},
|
||||
DownstreamScheme = "https",
|
||||
DownstreamPathTemplate = "/blooper/{productId}",
|
||||
UpstreamHttpMethod = new List<string> { "post" },
|
||||
UpstreamPathTemplate = "/test"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var command = new UpdateFileConfiguration(updatedConfiguration);
|
||||
GivenThereIsAConfiguration(configuration);
|
||||
GivenFiveServersAreRunning();
|
||||
GivenIHaveAnOcelotToken("/administration");
|
||||
GivenIHaveAddedATokenToMyRequest();
|
||||
WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration);
|
||||
ThenTheCommandIsReplicatedToAllStateMachines(command);
|
||||
}
|
||||
|
||||
private void WhenISendACommandIntoTheCluster(UpdateFileConfiguration command)
|
||||
{
|
||||
bool SendCommand()
|
||||
{
|
||||
try
|
||||
{
|
||||
var p = _peers.Peers.First();
|
||||
var json = JsonConvert.SerializeObject(command, new JsonSerializerSettings()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
});
|
||||
var httpContent = new StringContent(json);
|
||||
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||
using (var httpClient = new HttpClient())
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
||||
var response = httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent).GetAwaiter().GetResult();
|
||||
response.EnsureSuccessStatusCode();
|
||||
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
|
||||
var errorResult = JsonConvert.DeserializeObject<ErrorResponse<UpdateFileConfiguration>>(content);
|
||||
|
||||
if (!string.IsNullOrEmpty(errorResult.Error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var okResult = JsonConvert.DeserializeObject<OkResponse<UpdateFileConfiguration>>(content);
|
||||
|
||||
if (okResult.Command.Configuration.ReRoutes.Count == 2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var commandSent = WaitFor(20000).Until(() => SendCommand());
|
||||
commandSent.ShouldBeTrue();
|
||||
}
|
||||
|
||||
private void ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expecteds)
|
||||
{
|
||||
bool CommandCalledOnAllStateMachines()
|
||||
{
|
||||
try
|
||||
{
|
||||
var passed = 0;
|
||||
foreach (var peer in _peers.Peers)
|
||||
{
|
||||
var path = $"{peer.HostAndPort.Replace("/","").Replace(":","")}.db";
|
||||
using(var connection = new SqliteConnection($"Data Source={path};"))
|
||||
{
|
||||
connection.Open();
|
||||
var sql = @"select count(id) from logs";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var index = Convert.ToInt32(command.ExecuteScalar());
|
||||
index.ShouldBe(1);
|
||||
}
|
||||
}
|
||||
|
||||
_httpClientForAssertions.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
||||
var result = _httpClientForAssertions.GetAsync($"{peer.HostAndPort}/administration/configuration").Result;
|
||||
var json = result.Content.ReadAsStringAsync().Result;
|
||||
var response = JsonConvert.DeserializeObject<FileConfiguration>(json, new JsonSerializerSettings{TypeNameHandling = TypeNameHandling.All});
|
||||
response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.Configuration.GlobalConfiguration.RequestIdKey);
|
||||
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Host);
|
||||
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Port);
|
||||
|
||||
for (var i = 0; i < response.ReRoutes.Count; i++)
|
||||
{
|
||||
for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++)
|
||||
{
|
||||
var res = response.ReRoutes[i].DownstreamHostAndPorts[j];
|
||||
var expected = expecteds.Configuration.ReRoutes[i].DownstreamHostAndPorts[j];
|
||||
res.Host.ShouldBe(expected.Host);
|
||||
res.Port.ShouldBe(expected.Port);
|
||||
}
|
||||
|
||||
response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.Configuration.ReRoutes[i].DownstreamPathTemplate);
|
||||
response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.Configuration.ReRoutes[i].DownstreamScheme);
|
||||
response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.Configuration.ReRoutes[i].UpstreamPathTemplate);
|
||||
response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.Configuration.ReRoutes[i].UpstreamHttpMethod);
|
||||
}
|
||||
|
||||
passed++;
|
||||
}
|
||||
|
||||
return passed == 5;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//_output.WriteLine($"{e.Message}, {e.StackTrace}");
|
||||
Console.WriteLine(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var commandOnAllStateMachines = WaitFor(20000).Until(() => CommandCalledOnAllStateMachines());
|
||||
commandOnAllStateMachines.ShouldBeTrue();
|
||||
}
|
||||
|
||||
private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration)
|
||||
{
|
||||
bool SendCommand()
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(updatedConfiguration);
|
||||
var content = new StringContent(json);
|
||||
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||
_response = _httpClient.PostAsync(url, content).Result;
|
||||
var responseContent = _response.Content.ReadAsStringAsync().Result;
|
||||
|
||||
if(responseContent == "There was a problem. This error message sucks raise an issue in GitHub.")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(string.IsNullOrEmpty(responseContent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _response.IsSuccessStatusCode;
|
||||
}
|
||||
|
||||
var commandSent = WaitFor(20000).Until(() => SendCommand());
|
||||
commandSent.ShouldBeTrue();
|
||||
}
|
||||
|
||||
private void GivenIHaveAddedATokenToMyRequest()
|
||||
{
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
||||
}
|
||||
|
||||
private void GivenIHaveAnOcelotToken(string adminPath)
|
||||
{
|
||||
bool AddToken()
|
||||
{
|
||||
try
|
||||
{
|
||||
var tokenUrl = $"{adminPath}/connect/token";
|
||||
var formData = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("client_id", "admin"),
|
||||
new KeyValuePair<string, string>("client_secret", "secret"),
|
||||
new KeyValuePair<string, string>("scope", "admin"),
|
||||
new KeyValuePair<string, string>("grant_type", "client_credentials")
|
||||
};
|
||||
var content = new FormUrlEncodedContent(formData);
|
||||
|
||||
var response = _httpClient.PostAsync(tokenUrl, content).Result;
|
||||
var responseContent = response.Content.ReadAsStringAsync().Result;
|
||||
if(!response.IsSuccessStatusCode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
|
||||
var configPath = $"{adminPath}/.well-known/openid-configuration";
|
||||
response = _httpClient.GetAsync(configPath).Result;
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var addToken = WaitFor(20000).Until(() => AddToken());
|
||||
addToken.ShouldBeTrue();
|
||||
}
|
||||
|
||||
private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
|
||||
{
|
||||
var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json";
|
||||
|
||||
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
|
||||
|
||||
if (File.Exists(configurationPath))
|
||||
{
|
||||
File.Delete(configurationPath);
|
||||
}
|
||||
|
||||
File.WriteAllText(configurationPath, jsonConfiguration);
|
||||
|
||||
var text = File.ReadAllText(configurationPath);
|
||||
|
||||
configurationPath = $"{AppContext.BaseDirectory}/ocelot.json";
|
||||
|
||||
if (File.Exists(configurationPath))
|
||||
{
|
||||
File.Delete(configurationPath);
|
||||
}
|
||||
|
||||
File.WriteAllText(configurationPath, jsonConfiguration);
|
||||
|
||||
text = File.ReadAllText(configurationPath);
|
||||
}
|
||||
|
||||
private void GivenAServerIsRunning(string url)
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
IWebHostBuilder webHostBuilder = new WebHostBuilder();
|
||||
webHostBuilder.UseUrls(url)
|
||||
.UseKestrel()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||
{
|
||||
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
|
||||
var env = hostingContext.HostingEnvironment;
|
||||
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
||||
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
|
||||
config.AddJsonFile("ocelot.json");
|
||||
config.AddJsonFile("peers.json", optional: true, reloadOnChange: true);
|
||||
#pragma warning disable CS0618
|
||||
config.AddOcelotBaseUrl(url);
|
||||
#pragma warning restore CS0618
|
||||
config.AddEnvironmentVariables();
|
||||
})
|
||||
.ConfigureServices(x =>
|
||||
{
|
||||
x.AddSingleton(new NodeId(url));
|
||||
x
|
||||
.AddOcelot()
|
||||
.AddAdministration("/administration", "secret")
|
||||
.AddRafty();
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseOcelot().Wait();
|
||||
});
|
||||
|
||||
var builder = webHostBuilder.Build();
|
||||
builder.Start();
|
||||
|
||||
_webHostBuilders.Add(webHostBuilder);
|
||||
_builders.Add(builder);
|
||||
}
|
||||
}
|
||||
|
||||
private void GivenFiveServersAreRunning()
|
||||
{
|
||||
var bytes = File.ReadAllText("peers.json");
|
||||
_peers = JsonConvert.DeserializeObject<FilePeers>(bytes);
|
||||
|
||||
foreach (var peer in _peers.Peers)
|
||||
{
|
||||
File.Delete(peer.HostAndPort.Replace("/", "").Replace(":", ""));
|
||||
File.Delete($"{peer.HostAndPort.Replace("/", "").Replace(":", "")}.db");
|
||||
var thread = new Thread(() => GivenAServerIsRunning(peer.HostAndPort));
|
||||
thread.Start();
|
||||
_threads.Add(thread);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var builder in _builders)
|
||||
{
|
||||
builder?.Dispose();
|
||||
}
|
||||
|
||||
foreach (var peer in _peers.Peers)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(peer.HostAndPort.Replace("/", "").Replace(":", ""));
|
||||
File.Delete($"{peer.HostAndPort.Replace("/", "").Replace(":", "")}.db");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,16 +24,16 @@
|
||||
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
|
||||
<PackageReference Include="Consul" Version="0.7.2.4" />
|
||||
<PackageReference Include="Polly" Version="5.8.0" />
|
||||
<PackageReference Include="Polly" Version="6.0.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -1,158 +1,174 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Moq;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.UnitTests.Responder;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
using Shouldly;
|
||||
using static Ocelot.Infrastructure.Wait;
|
||||
|
||||
namespace Ocelot.UnitTests.Configuration
|
||||
{
|
||||
public class ConsulFileConfigurationPollerTests : IDisposable
|
||||
{
|
||||
private ConsulFileConfigurationPoller _poller;
|
||||
private Mock<IOcelotLoggerFactory> _factory;
|
||||
private Mock<IFileConfigurationRepository> _repo;
|
||||
private Mock<IFileConfigurationSetter> _setter;
|
||||
private FileConfiguration _fileConfig;
|
||||
private Mock<IConsulPollerConfiguration> _config;
|
||||
|
||||
public ConsulFileConfigurationPollerTests()
|
||||
{
|
||||
var logger = new Mock<IOcelotLogger>();
|
||||
_factory = new Mock<IOcelotLoggerFactory>();
|
||||
_factory.Setup(x => x.CreateLogger<ConsulFileConfigurationPoller>()).Returns(logger.Object);
|
||||
_repo = new Mock<IFileConfigurationRepository>();
|
||||
_setter = new Mock<IFileConfigurationSetter>();
|
||||
_fileConfig = new FileConfiguration();
|
||||
_config = new Mock<IConsulPollerConfiguration>();
|
||||
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(_fileConfig));
|
||||
_config.Setup(x => x.Delay).Returns(100);
|
||||
_poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object, _config.Object);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_poller.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_start()
|
||||
{
|
||||
this.Given(x => ThenTheSetterIsCalled(_fileConfig, 1))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_call_setter_when_gets_new_config()
|
||||
{
|
||||
var newConfig = new FileConfiguration {
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "test"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 0))
|
||||
.Then(x => ThenTheSetterIsCalled(newConfig, 1))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_not_poll_if_already_polling()
|
||||
{
|
||||
var newConfig = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "test"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 10))
|
||||
.Then(x => ThenTheSetterIsCalled(newConfig, 1))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_do_nothing_if_call_to_consul_fails()
|
||||
{
|
||||
var newConfig = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "test"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.Given(x => WhenConsulErrors())
|
||||
.Then(x => ThenTheSetterIsCalled(newConfig, 0))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void WhenConsulErrors()
|
||||
{
|
||||
_repo
|
||||
.Setup(x => x.Get())
|
||||
.ReturnsAsync(new ErrorResponse<FileConfiguration>(new AnyError()));
|
||||
}
|
||||
|
||||
private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig, int delay)
|
||||
{
|
||||
_repo
|
||||
.Setup(x => x.Get())
|
||||
.Callback(() => Thread.Sleep(delay))
|
||||
.ReturnsAsync(new OkResponse<FileConfiguration>(newConfig));
|
||||
}
|
||||
|
||||
private void ThenTheSetterIsCalled(FileConfiguration fileConfig, int times)
|
||||
{
|
||||
var result = WaitFor(2000).Until(() => {
|
||||
try
|
||||
{
|
||||
_setter.Verify(x => x.Set(fileConfig), Times.Exactly(times));
|
||||
return true;
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Moq;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.UnitTests.Responder;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
using Shouldly;
|
||||
using static Ocelot.Infrastructure.Wait;
|
||||
|
||||
namespace Ocelot.UnitTests.Configuration
|
||||
{
|
||||
public class ConsulFileConfigurationPollerTests : IDisposable
|
||||
{
|
||||
private readonly ConsulFileConfigurationPoller _poller;
|
||||
private Mock<IOcelotLoggerFactory> _factory;
|
||||
private readonly Mock<IFileConfigurationRepository> _repo;
|
||||
private readonly Mock<IFileConfigurationSetter> _setter;
|
||||
private readonly FileConfiguration _fileConfig;
|
||||
private Mock<IConsulPollerConfiguration> _config;
|
||||
|
||||
public ConsulFileConfigurationPollerTests()
|
||||
{
|
||||
var logger = new Mock<IOcelotLogger>();
|
||||
_factory = new Mock<IOcelotLoggerFactory>();
|
||||
_factory.Setup(x => x.CreateLogger<ConsulFileConfigurationPoller>()).Returns(logger.Object);
|
||||
_repo = new Mock<IFileConfigurationRepository>();
|
||||
_setter = new Mock<IFileConfigurationSetter>();
|
||||
_fileConfig = new FileConfiguration();
|
||||
_config = new Mock<IConsulPollerConfiguration>();
|
||||
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(_fileConfig));
|
||||
_config.Setup(x => x.Delay).Returns(100);
|
||||
_poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object, _config.Object);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_poller.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_start()
|
||||
{
|
||||
this.Given(x => ThenTheSetterIsCalled(_fileConfig, 1))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_call_setter_when_gets_new_config()
|
||||
{
|
||||
var newConfig = new FileConfiguration {
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "test"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 0))
|
||||
.Then(x => ThenTheSetterIsCalledAtLeast(newConfig, 1))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_not_poll_if_already_polling()
|
||||
{
|
||||
var newConfig = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "test"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 10))
|
||||
.Then(x => ThenTheSetterIsCalled(newConfig, 1))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_do_nothing_if_call_to_consul_fails()
|
||||
{
|
||||
var newConfig = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "test"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.Given(x => WhenConsulErrors())
|
||||
.Then(x => ThenTheSetterIsCalled(newConfig, 0))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void WhenConsulErrors()
|
||||
{
|
||||
_repo
|
||||
.Setup(x => x.Get())
|
||||
.ReturnsAsync(new ErrorResponse<FileConfiguration>(new AnyError()));
|
||||
}
|
||||
|
||||
private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig, int delay)
|
||||
{
|
||||
_repo
|
||||
.Setup(x => x.Get())
|
||||
.Callback(() => Thread.Sleep(delay))
|
||||
.ReturnsAsync(new OkResponse<FileConfiguration>(newConfig));
|
||||
}
|
||||
|
||||
private void ThenTheSetterIsCalled(FileConfiguration fileConfig, int times)
|
||||
{
|
||||
var result = WaitFor(2000).Until(() => {
|
||||
try
|
||||
{
|
||||
_setter.Verify(x => x.Set(fileConfig), Times.Exactly(times));
|
||||
return true;
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
|
||||
private void ThenTheSetterIsCalledAtLeast(FileConfiguration fileConfig, int times)
|
||||
{
|
||||
var result = WaitFor(2000).Until(() => {
|
||||
try
|
||||
{
|
||||
_setter.Verify(x => x.Set(fileConfig), Times.AtLeast(times));
|
||||
return true;
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,219 +1,260 @@
|
||||
namespace Ocelot.UnitTests.Configuration
|
||||
{
|
||||
using Xunit;
|
||||
using TestStack.BDDfy;
|
||||
using Shouldly;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Moq;
|
||||
using Ocelot.Infrastructure.Consul;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Cache;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Responses;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.ServiceDiscovery.Configuration;
|
||||
using Consul;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Linq;
|
||||
|
||||
public class ConsulFileConfigurationRepositoryTests
|
||||
{
|
||||
private ConsulFileConfigurationRepository _repo;
|
||||
private Mock<IOcelotCache<FileConfiguration>> _cache;
|
||||
private Mock<IInternalConfigurationRepository> _internalRepo;
|
||||
private Mock<IConsulClientFactory> _factory;
|
||||
private Mock<IOcelotLoggerFactory> _loggerFactory;
|
||||
private Mock<IConsulClient> _client;
|
||||
private Mock<IKVEndpoint> _kvEndpoint;
|
||||
private FileConfiguration _fileConfiguration;
|
||||
private Response _setResult;
|
||||
private Response<FileConfiguration> _getResult;
|
||||
|
||||
public ConsulFileConfigurationRepositoryTests()
|
||||
{
|
||||
_cache = new Mock<IOcelotCache<FileConfiguration>>();
|
||||
_internalRepo = new Mock<IInternalConfigurationRepository>();
|
||||
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
||||
|
||||
_factory = new Mock<IConsulClientFactory>();
|
||||
_client = new Mock<IConsulClient>();
|
||||
_kvEndpoint = new Mock<IKVEndpoint>();
|
||||
|
||||
_client
|
||||
.Setup(x => x.KV)
|
||||
.Returns(_kvEndpoint.Object);
|
||||
_factory
|
||||
.Setup(x => x.Get(It.IsAny<ConsulRegistryConfiguration>()))
|
||||
.Returns(_client.Object);
|
||||
|
||||
_internalRepo
|
||||
.Setup(x => x.Get())
|
||||
.Returns(new OkResponse<IInternalConfiguration>(new InternalConfiguration(new List<ReRoute>(), "", new ServiceProviderConfigurationBuilder().Build(), "")));
|
||||
|
||||
_repo = new ConsulFileConfigurationRepository(_cache.Object, _internalRepo.Object, _factory.Object, _loggerFactory.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_set_config()
|
||||
{
|
||||
var config = FakeFileConfiguration();
|
||||
|
||||
this.Given(_ => GivenIHaveAConfiguration(config))
|
||||
.And(_ => GivenWritingToConsulSucceeds())
|
||||
.When(_ => WhenISetTheConfiguration())
|
||||
.Then(_ => ThenTheConfigurationIsStoredAs(config))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_get_config()
|
||||
{
|
||||
var config = FakeFileConfiguration();
|
||||
|
||||
this.Given(_ => GivenIHaveAConfiguration(config))
|
||||
.And(_ => GivenFetchFromConsulSucceeds())
|
||||
.When(_ => WhenIGetTheConfiguration())
|
||||
.Then(_ => ThenTheConfigurationIs(config))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_get_null_config()
|
||||
{
|
||||
this.Given(_ => GivenFetchFromConsulReturnsNull())
|
||||
.When(_ => WhenIGetTheConfiguration())
|
||||
.Then(_ => ThenTheConfigurationIsNull())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_get_config_from_cache()
|
||||
{
|
||||
var config = FakeFileConfiguration();
|
||||
|
||||
this.Given(_ => GivenIHaveAConfiguration(config))
|
||||
.And(_ => GivenFetchFromCacheSucceeds())
|
||||
.When(_ => WhenIGetTheConfiguration())
|
||||
.Then(_ => ThenTheConfigurationIs(config))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void ThenTheConfigurationIsNull()
|
||||
{
|
||||
_getResult.Data.ShouldBeNull();
|
||||
}
|
||||
|
||||
private void ThenTheConfigurationIs(FileConfiguration config)
|
||||
{
|
||||
var expected = JsonConvert.SerializeObject(config, Formatting.Indented);
|
||||
var result = JsonConvert.SerializeObject(_getResult.Data, Formatting.Indented);
|
||||
result.ShouldBe(expected);
|
||||
}
|
||||
|
||||
private async Task WhenIGetTheConfiguration()
|
||||
{
|
||||
_getResult = await _repo.Get();
|
||||
}
|
||||
|
||||
private void GivenWritingToConsulSucceeds()
|
||||
{
|
||||
var response = new WriteResult<bool>();
|
||||
response.Response = true;
|
||||
|
||||
_kvEndpoint
|
||||
.Setup(x => x.Put(It.IsAny<KVPair>(), It.IsAny<CancellationToken>())).ReturnsAsync(response);
|
||||
}
|
||||
|
||||
private void GivenFetchFromCacheSucceeds()
|
||||
{
|
||||
_cache.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())).Returns(_fileConfiguration);
|
||||
}
|
||||
|
||||
private void GivenFetchFromConsulReturnsNull()
|
||||
{
|
||||
QueryResult<KVPair> result = new QueryResult<KVPair>();
|
||||
|
||||
_kvEndpoint
|
||||
.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(result);
|
||||
}
|
||||
|
||||
private void GivenFetchFromConsulSucceeds()
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(_fileConfiguration, Formatting.Indented);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
var kvp = new KVPair("OcelotConfiguration");
|
||||
kvp.Value = bytes;
|
||||
|
||||
var query = new QueryResult<KVPair>();
|
||||
query.Response = kvp;
|
||||
|
||||
_kvEndpoint
|
||||
.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(query);
|
||||
}
|
||||
|
||||
private void ThenTheConfigurationIsStoredAs(FileConfiguration config)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(config, Formatting.Indented);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
_kvEndpoint
|
||||
.Verify(x => x.Put(It.Is<KVPair>(k => k.Value.SequenceEqual(bytes)), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
private async Task WhenISetTheConfiguration()
|
||||
{
|
||||
_setResult = await _repo.Set(_fileConfiguration);
|
||||
}
|
||||
|
||||
private void GivenIHaveAConfiguration(FileConfiguration config)
|
||||
{
|
||||
_fileConfiguration = config;
|
||||
}
|
||||
|
||||
private FileConfiguration FakeFileConfiguration()
|
||||
{
|
||||
var reRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "123.12.12.12",
|
||||
Port = 80,
|
||||
}
|
||||
},
|
||||
DownstreamScheme = "https",
|
||||
DownstreamPathTemplate = "/asdfs/test/{test}"
|
||||
}
|
||||
};
|
||||
|
||||
var globalConfiguration = new FileGlobalConfiguration
|
||||
{
|
||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
|
||||
{
|
||||
Port = 198,
|
||||
Host = "blah"
|
||||
}
|
||||
};
|
||||
|
||||
return new FileConfiguration
|
||||
{
|
||||
GlobalConfiguration = globalConfiguration,
|
||||
ReRoutes = reRoutes
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace Ocelot.UnitTests.Configuration
|
||||
{
|
||||
using Xunit;
|
||||
using TestStack.BDDfy;
|
||||
using Shouldly;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Moq;
|
||||
using Ocelot.Infrastructure.Consul;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Cache;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Responses;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.ServiceDiscovery.Configuration;
|
||||
using Consul;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Linq;
|
||||
|
||||
public class ConsulFileConfigurationRepositoryTests
|
||||
{
|
||||
private ConsulFileConfigurationRepository _repo;
|
||||
private Mock<IOcelotCache<FileConfiguration>> _cache;
|
||||
private Mock<IInternalConfigurationRepository> _internalRepo;
|
||||
private Mock<IConsulClientFactory> _factory;
|
||||
private Mock<IOcelotLoggerFactory> _loggerFactory;
|
||||
private Mock<IConsulClient> _client;
|
||||
private Mock<IKVEndpoint> _kvEndpoint;
|
||||
private FileConfiguration _fileConfiguration;
|
||||
private Response _setResult;
|
||||
private Response<FileConfiguration> _getResult;
|
||||
|
||||
public ConsulFileConfigurationRepositoryTests()
|
||||
{
|
||||
_cache = new Mock<IOcelotCache<FileConfiguration>>();
|
||||
_internalRepo = new Mock<IInternalConfigurationRepository>();
|
||||
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
||||
|
||||
_factory = new Mock<IConsulClientFactory>();
|
||||
_client = new Mock<IConsulClient>();
|
||||
_kvEndpoint = new Mock<IKVEndpoint>();
|
||||
|
||||
_client
|
||||
.Setup(x => x.KV)
|
||||
.Returns(_kvEndpoint.Object);
|
||||
|
||||
_factory
|
||||
.Setup(x => x.Get(It.IsAny<ConsulRegistryConfiguration>()))
|
||||
.Returns(_client.Object);
|
||||
|
||||
_internalRepo
|
||||
.Setup(x => x.Get())
|
||||
.Returns(new OkResponse<IInternalConfiguration>(new InternalConfiguration(new List<ReRoute>(), "", new ServiceProviderConfigurationBuilder().Build(), "")));
|
||||
|
||||
_repo = new ConsulFileConfigurationRepository(_cache.Object, _internalRepo.Object, _factory.Object, _loggerFactory.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_set_config()
|
||||
{
|
||||
var config = FakeFileConfiguration();
|
||||
|
||||
this.Given(_ => GivenIHaveAConfiguration(config))
|
||||
.And(_ => GivenWritingToConsulSucceeds())
|
||||
.When(_ => WhenISetTheConfiguration())
|
||||
.Then(_ => ThenTheConfigurationIsStoredAs(config))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_get_config()
|
||||
{
|
||||
var config = FakeFileConfiguration();
|
||||
|
||||
this.Given(_ => GivenIHaveAConfiguration(config))
|
||||
.And(_ => GivenFetchFromConsulSucceeds())
|
||||
.When(_ => WhenIGetTheConfiguration())
|
||||
.Then(_ => ThenTheConfigurationIs(config))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_get_null_config()
|
||||
{
|
||||
this.Given(_ => GivenFetchFromConsulReturnsNull())
|
||||
.When(_ => WhenIGetTheConfiguration())
|
||||
.Then(_ => ThenTheConfigurationIsNull())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_get_config_from_cache()
|
||||
{
|
||||
var config = FakeFileConfiguration();
|
||||
|
||||
this.Given(_ => GivenIHaveAConfiguration(config))
|
||||
.And(_ => GivenFetchFromCacheSucceeds())
|
||||
.When(_ => WhenIGetTheConfiguration())
|
||||
.Then(_ => ThenTheConfigurationIs(config))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_set_config_key()
|
||||
{
|
||||
var config = FakeFileConfiguration();
|
||||
|
||||
this.Given(_ => GivenIHaveAConfiguration(config))
|
||||
.And(_ => GivenTheConfigKeyComesFromFileConfig("Tom"))
|
||||
.And(_ => GivenFetchFromConsulSucceeds())
|
||||
.When(_ => WhenIGetTheConfiguration())
|
||||
.And(_ => ThenTheConfigKeyIs("Tom"))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_set_default_config_key()
|
||||
{
|
||||
var config = FakeFileConfiguration();
|
||||
|
||||
this.Given(_ => GivenIHaveAConfiguration(config))
|
||||
.And(_ => GivenFetchFromConsulSucceeds())
|
||||
.When(_ => WhenIGetTheConfiguration())
|
||||
.And(_ => ThenTheConfigKeyIs("InternalConfiguration"))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void ThenTheConfigKeyIs(string expected)
|
||||
{
|
||||
_kvEndpoint
|
||||
.Verify(x => x.Get(expected, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
private void GivenTheConfigKeyComesFromFileConfig(string key)
|
||||
{
|
||||
_internalRepo
|
||||
.Setup(x => x.Get())
|
||||
.Returns(new OkResponse<IInternalConfiguration>(new InternalConfiguration(new List<ReRoute>(), "", new ServiceProviderConfigurationBuilder().WithConfigurationKey(key).Build(), "")));
|
||||
|
||||
_repo = new ConsulFileConfigurationRepository(_cache.Object, _internalRepo.Object, _factory.Object, _loggerFactory.Object);
|
||||
}
|
||||
|
||||
private void ThenTheConfigurationIsNull()
|
||||
{
|
||||
_getResult.Data.ShouldBeNull();
|
||||
}
|
||||
|
||||
private void ThenTheConfigurationIs(FileConfiguration config)
|
||||
{
|
||||
var expected = JsonConvert.SerializeObject(config, Formatting.Indented);
|
||||
var result = JsonConvert.SerializeObject(_getResult.Data, Formatting.Indented);
|
||||
result.ShouldBe(expected);
|
||||
}
|
||||
|
||||
private async Task WhenIGetTheConfiguration()
|
||||
{
|
||||
_getResult = await _repo.Get();
|
||||
}
|
||||
|
||||
private void GivenWritingToConsulSucceeds()
|
||||
{
|
||||
var response = new WriteResult<bool>();
|
||||
response.Response = true;
|
||||
|
||||
_kvEndpoint
|
||||
.Setup(x => x.Put(It.IsAny<KVPair>(), It.IsAny<CancellationToken>())).ReturnsAsync(response);
|
||||
}
|
||||
|
||||
private void GivenFetchFromCacheSucceeds()
|
||||
{
|
||||
_cache.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())).Returns(_fileConfiguration);
|
||||
}
|
||||
|
||||
private void GivenFetchFromConsulReturnsNull()
|
||||
{
|
||||
QueryResult<KVPair> result = new QueryResult<KVPair>();
|
||||
|
||||
_kvEndpoint
|
||||
.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(result);
|
||||
}
|
||||
|
||||
private void GivenFetchFromConsulSucceeds()
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(_fileConfiguration, Formatting.Indented);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
var kvp = new KVPair("OcelotConfiguration");
|
||||
kvp.Value = bytes;
|
||||
|
||||
var query = new QueryResult<KVPair>();
|
||||
query.Response = kvp;
|
||||
|
||||
_kvEndpoint
|
||||
.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(query);
|
||||
}
|
||||
|
||||
private void ThenTheConfigurationIsStoredAs(FileConfiguration config)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(config, Formatting.Indented);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
_kvEndpoint
|
||||
.Verify(x => x.Put(It.Is<KVPair>(k => k.Value.SequenceEqual(bytes)), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
private async Task WhenISetTheConfiguration()
|
||||
{
|
||||
_setResult = await _repo.Set(_fileConfiguration);
|
||||
}
|
||||
|
||||
private void GivenIHaveAConfiguration(FileConfiguration config)
|
||||
{
|
||||
_fileConfiguration = config;
|
||||
}
|
||||
|
||||
private FileConfiguration FakeFileConfiguration()
|
||||
{
|
||||
var reRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "123.12.12.12",
|
||||
Port = 80,
|
||||
}
|
||||
},
|
||||
DownstreamScheme = "https",
|
||||
DownstreamPathTemplate = "/asdfs/test/{test}"
|
||||
}
|
||||
};
|
||||
|
||||
var globalConfiguration = new FileGlobalConfiguration
|
||||
{
|
||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
|
||||
{
|
||||
Port = 198,
|
||||
Host = "blah"
|
||||
}
|
||||
};
|
||||
|
||||
return new FileConfiguration
|
||||
{
|
||||
GlobalConfiguration = globalConfiguration,
|
||||
ReRoutes = reRoutes
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,67 +1,70 @@
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.Configuration.Creator;
|
||||
using Ocelot.Configuration.File;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.Configuration
|
||||
{
|
||||
public class ServiceProviderCreatorTests
|
||||
{
|
||||
private readonly ServiceProviderConfigurationCreator _creator;
|
||||
private FileGlobalConfiguration _globalConfig;
|
||||
private ServiceProviderConfiguration _result;
|
||||
|
||||
public ServiceProviderCreatorTests()
|
||||
{
|
||||
_creator = new ServiceProviderConfigurationCreator();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_create_service_provider_config()
|
||||
{
|
||||
var globalConfig = new FileGlobalConfiguration
|
||||
{
|
||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
Port = 1234,
|
||||
Type = "ServiceFabric",
|
||||
Token = "testtoken"
|
||||
}
|
||||
};
|
||||
|
||||
var expected = new ServiceProviderConfigurationBuilder()
|
||||
.WithHost("127.0.0.1")
|
||||
.WithPort(1234)
|
||||
.WithType("ServiceFabric")
|
||||
.WithToken("testtoken")
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenTheFollowingGlobalConfig(globalConfig))
|
||||
.When(x => x.WhenICreate())
|
||||
.Then(x => x.ThenTheConfigIs(expected))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenTheFollowingGlobalConfig(FileGlobalConfiguration fileGlobalConfig)
|
||||
{
|
||||
_globalConfig = fileGlobalConfig;
|
||||
}
|
||||
|
||||
private void WhenICreate()
|
||||
{
|
||||
_result = _creator.Create(_globalConfig);
|
||||
}
|
||||
|
||||
private void ThenTheConfigIs(ServiceProviderConfiguration expected)
|
||||
{
|
||||
_result.Host.ShouldBe(expected.Host);
|
||||
_result.Port.ShouldBe(expected.Port);
|
||||
_result.Token.ShouldBe(expected.Token);
|
||||
_result.Type.ShouldBe(expected.Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.Configuration.Creator;
|
||||
using Ocelot.Configuration.File;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.Configuration
|
||||
{
|
||||
public class ServiceProviderCreatorTests
|
||||
{
|
||||
private readonly ServiceProviderConfigurationCreator _creator;
|
||||
private FileGlobalConfiguration _globalConfig;
|
||||
private ServiceProviderConfiguration _result;
|
||||
|
||||
public ServiceProviderCreatorTests()
|
||||
{
|
||||
_creator = new ServiceProviderConfigurationCreator();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_create_service_provider_config()
|
||||
{
|
||||
var globalConfig = new FileGlobalConfiguration
|
||||
{
|
||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
Port = 1234,
|
||||
Type = "ServiceFabric",
|
||||
Token = "testtoken",
|
||||
ConfigurationKey = "woo"
|
||||
}
|
||||
};
|
||||
|
||||
var expected = new ServiceProviderConfigurationBuilder()
|
||||
.WithHost("127.0.0.1")
|
||||
.WithPort(1234)
|
||||
.WithType("ServiceFabric")
|
||||
.WithToken("testtoken")
|
||||
.WithConfigurationKey("woo")
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenTheFollowingGlobalConfig(globalConfig))
|
||||
.When(x => x.WhenICreate())
|
||||
.Then(x => x.ThenTheConfigIs(expected))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenTheFollowingGlobalConfig(FileGlobalConfiguration fileGlobalConfig)
|
||||
{
|
||||
_globalConfig = fileGlobalConfig;
|
||||
}
|
||||
|
||||
private void WhenICreate()
|
||||
{
|
||||
_result = _creator.Create(_globalConfig);
|
||||
}
|
||||
|
||||
private void ThenTheConfigIs(ServiceProviderConfiguration expected)
|
||||
{
|
||||
_result.Host.ShouldBe(expected.Host);
|
||||
_result.Port.ShouldBe(expected.Port);
|
||||
_result.Token.ShouldBe(expected.Token);
|
||||
_result.Type.ShouldBe(expected.Type);
|
||||
_result.ConfigurationKey.ShouldBe(expected.ConfigurationKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,14 +126,14 @@ namespace Ocelot.UnitTests.Controllers
|
||||
{
|
||||
_node
|
||||
.Setup(x => x.Accept(It.IsAny<UpdateFileConfiguration>()))
|
||||
.Returns(new Rafty.Concensus.OkResponse<UpdateFileConfiguration>(new UpdateFileConfiguration(new FileConfiguration())));
|
||||
.ReturnsAsync(new Rafty.Concensus.OkResponse<UpdateFileConfiguration>(new UpdateFileConfiguration(new FileConfiguration())));
|
||||
}
|
||||
|
||||
private void GivenTheNodeReturnsError()
|
||||
{
|
||||
_node
|
||||
.Setup(x => x.Accept(It.IsAny<UpdateFileConfiguration>()))
|
||||
.Returns(new Rafty.Concensus.ErrorResponse<UpdateFileConfiguration>("error", new UpdateFileConfiguration(new FileConfiguration())));
|
||||
.ReturnsAsync(new Rafty.Concensus.ErrorResponse<UpdateFileConfiguration>("error", new UpdateFileConfiguration(new FileConfiguration())));
|
||||
}
|
||||
|
||||
private void GivenTheConfigSetterReturns(Response response)
|
||||
|
40
test/Ocelot.UnitTests/Infrastructure/InMemoryBusTests.cs
Normal file
40
test/Ocelot.UnitTests/Infrastructure/InMemoryBusTests.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Infrastructure;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.Infrastructure
|
||||
{
|
||||
public class InMemoryBusTests
|
||||
{
|
||||
private readonly InMemoryBus<object> _bus;
|
||||
|
||||
public InMemoryBusTests()
|
||||
{
|
||||
_bus = new InMemoryBus<object>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task should_publish_with_delay()
|
||||
{
|
||||
var called = false;
|
||||
_bus.Subscribe(x => {
|
||||
called = true;
|
||||
});
|
||||
_bus.Publish(new object(), 1);
|
||||
await Task.Delay(10);
|
||||
called.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_not_be_publish_yet_as_no_delay_in_caller()
|
||||
{
|
||||
var called = false;
|
||||
_bus.Subscribe(x => {
|
||||
called = true;
|
||||
});
|
||||
_bus.Publish(new object(), 1);
|
||||
called.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Responses;
|
||||
@ -10,28 +11,43 @@ namespace Ocelot.UnitTests.LoadBalancer
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using System.Threading;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.UnitTests.Responder;
|
||||
using TestStack.BDDfy;
|
||||
using Ocelot.Infrastructure;
|
||||
|
||||
public class CookieStickySessionsTests
|
||||
{
|
||||
private readonly CookieStickySessions _stickySessions;
|
||||
private readonly Mock<ILoadBalancer> _loadBalancer;
|
||||
private readonly int _defaultExpiryInMs;
|
||||
private DownstreamContext _downstreamContext;
|
||||
private Response<ServiceHostAndPort> _result;
|
||||
private Response<ServiceHostAndPort> _firstHostAndPort;
|
||||
private Response<ServiceHostAndPort> _secondHostAndPort;
|
||||
private readonly FakeBus<StickySession> _bus;
|
||||
|
||||
public CookieStickySessionsTests()
|
||||
{
|
||||
_bus = new FakeBus<StickySession>();
|
||||
_loadBalancer = new Mock<ILoadBalancer>();
|
||||
const int defaultExpiryInMs = 100;
|
||||
_stickySessions = new CookieStickySessions(_loadBalancer.Object, "sessionid", defaultExpiryInMs);
|
||||
_defaultExpiryInMs = 0;
|
||||
_stickySessions = new CookieStickySessions(_loadBalancer.Object, "sessionid", _defaultExpiryInMs, _bus);
|
||||
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_expire_sticky_session()
|
||||
{
|
||||
this.Given(_ => GivenTheLoadBalancerReturns())
|
||||
.And(_ => GivenTheDownstreamRequestHasSessionId("321"))
|
||||
.And(_ => GivenIHackAMessageInWithAPastExpiry())
|
||||
.And(_ => WhenILease())
|
||||
.When(_ => WhenTheMessagesAreProcessed())
|
||||
.Then(_ => ThenTheLoadBalancerIsCalled())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_host_and_port()
|
||||
{
|
||||
@ -48,6 +64,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
||||
.And(_ => GivenTheDownstreamRequestHasSessionId("321"))
|
||||
.When(_ => WhenILeaseTwiceInARow())
|
||||
.Then(_ => ThenTheFirstAndSecondResponseAreTheSame())
|
||||
.And(_ => ThenTheStickySessionWillTimeout())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
@ -69,92 +86,26 @@ namespace Ocelot.UnitTests.LoadBalancer
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_expire_sticky_session()
|
||||
{
|
||||
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
|
||||
.When(_ => WhenTheStickySessionExpires())
|
||||
.Then(_ => ThenANewHostAndPortIsReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_refresh_sticky_session()
|
||||
{
|
||||
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
|
||||
.When(_ => WhenIMakeRequestsToKeepRefreshingTheSession())
|
||||
.Then(_ => ThenTheSessionIsRefreshed())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_dispose()
|
||||
{
|
||||
_stickySessions.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_release()
|
||||
{
|
||||
_stickySessions.Release(new ServiceHostAndPort("", 0));
|
||||
}
|
||||
|
||||
private async Task ThenTheSessionIsRefreshed()
|
||||
private void ThenTheLoadBalancerIsCalled()
|
||||
{
|
||||
var postExpireHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||
postExpireHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||
postExpireHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||
|
||||
_loadBalancer
|
||||
.Verify(x => x.Lease(It.IsAny<DownstreamContext>()), Times.Once);
|
||||
_loadBalancer.Verify(x => x.Release(It.IsAny<ServiceHostAndPort>()), Times.Once);
|
||||
}
|
||||
|
||||
private async Task WhenIMakeRequestsToKeepRefreshingTheSession()
|
||||
private void WhenTheMessagesAreProcessed()
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
var cookies = new FakeCookies();
|
||||
cookies.AddCookie("sessionid", "321");
|
||||
context.Request.Cookies = cookies;
|
||||
_downstreamContext = new DownstreamContext(context);
|
||||
|
||||
var firstHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||
firstHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||
firstHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||
|
||||
Thread.Sleep(80);
|
||||
|
||||
var secondHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||
secondHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||
secondHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||
|
||||
Thread.Sleep(80);
|
||||
_bus.Process();
|
||||
}
|
||||
|
||||
private async Task ThenANewHostAndPortIsReturned()
|
||||
private void GivenIHackAMessageInWithAPastExpiry()
|
||||
{
|
||||
var postExpireHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||
postExpireHostAndPort.Data.DownstreamHost.ShouldBe("two");
|
||||
postExpireHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||
}
|
||||
|
||||
private async Task WhenTheStickySessionExpires()
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
var cookies = new FakeCookies();
|
||||
cookies.AddCookie("sessionid", "321");
|
||||
context.Request.Cookies = cookies;
|
||||
_downstreamContext = new DownstreamContext(context);
|
||||
|
||||
var firstHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||
var secondHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||
|
||||
firstHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||
firstHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||
|
||||
secondHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||
secondHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||
|
||||
Thread.Sleep(150);
|
||||
var hostAndPort = new ServiceHostAndPort("999", 999);
|
||||
_bus.Publish(new StickySession(hostAndPort, DateTime.UtcNow.AddDays(-1), "321"), 0);
|
||||
}
|
||||
|
||||
private void ThenAnErrorIsReturned()
|
||||
@ -236,9 +187,14 @@ namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
_result.Data.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
private void ThenTheStickySessionWillTimeout()
|
||||
{
|
||||
_bus.Messages.Count.ShouldBe(2);
|
||||
}
|
||||
}
|
||||
|
||||
class FakeCookies : IRequestCookieCollection
|
||||
|
||||
internal class FakeCookies : IRequestCookieCollection
|
||||
{
|
||||
private readonly Dictionary<string, string> _cookies = new Dictionary<string, string>();
|
||||
|
||||
@ -273,4 +229,37 @@ namespace Ocelot.UnitTests.LoadBalancer
|
||||
return _cookies.GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
internal class FakeBus<T> : IBus<T>
|
||||
{
|
||||
public FakeBus()
|
||||
{
|
||||
Messages = new List<T>();
|
||||
Subscriptions = new List<Action<T>>();
|
||||
}
|
||||
|
||||
public List<T> Messages { get; }
|
||||
public List<Action<T>> Subscriptions { get; }
|
||||
|
||||
public void Subscribe(Action<T> action)
|
||||
{
|
||||
Subscriptions.Add(action);
|
||||
}
|
||||
|
||||
public void Publish(T message, int delay)
|
||||
{
|
||||
Messages.Add(message);
|
||||
}
|
||||
|
||||
public void Process()
|
||||
{
|
||||
foreach (var message in Messages)
|
||||
{
|
||||
foreach (var subscription in Subscriptions)
|
||||
{
|
||||
subscription(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
_factory = new Mock<ILoadBalancerFactory>();
|
||||
_loadBalancerHouse = new LoadBalancerHouse(_factory.Object);
|
||||
_serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123, string.Empty);
|
||||
_serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123, string.Empty, "configKey");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -29,30 +29,30 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
|
||||
<PackageReference Include="Moq" Version="4.8.2" />
|
||||
<PackageReference Include="Shouldly" Version="3.0.0" />
|
||||
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
||||
<PackageReference Include="Rafty" Version="0.4.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="WebSockets\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
Loading…
x
Reference in New Issue
Block a user