Feature/more dynamic routes (#508)

* Made the file config poller use IHostedService, bit more generic, not just need to provide the correct implementations of the repo services and it will poll anything..this means we can open up redis for #458

* removed comments

* #458 allow users to set rate limits per service for dynamic re routes

* #458 added docs for rate limting on dynamic reroutes
This commit is contained in:
Tom Pallister 2018-07-29 20:32:45 +01:00 committed by GitHub
parent 0f2cf2d188
commit b0a20d13b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 652 additions and 350 deletions

View File

@ -125,20 +125,14 @@ is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them
Dynamic Routing Dynamic Routing
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/issue/340>`_. The idea is to enable dynamic routing when using This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/issue/340>`_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider.
a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segmentof the upstream path to lookup the
downstream service with the service discovery provider.
An example of this would be calling ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of An example of this would be calling ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of
the path which is product and use it as a key to look up the service in consul. If consul returns a service Ocelot will request it on whatever host and the path which is product and use it as a key to look up the service in consul. If consul returns a service Ocelot will request it on whatever host and port comes back from consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. Ocelot will apprend any query string to the downstream url as normal.
port comes back from consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products.
Ocelot will apprend any query string to the downstream url as normal.
In order to enable dynamic routing you need to have 0 ReRoutes in your config. At the moment you cannot mix dynamic and configuration ReRoutes. In addition to this you In order to enable dynamic routing you need to have 0 ReRoutes in your config. At the moment you cannot mix dynamic and configuration ReRoutes. In addition to this you need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme.
need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme.
In addition to that you can set RateLimitOptions, QoSOptions, LoadBalancerOptions and HttpHandlerOptions, DownstreamScheme (You might want to call Ocelot on https but In addition to that you can set RateLimitOptions, QoSOptions, LoadBalancerOptions and HttpHandlerOptions, DownstreamScheme (You might want to call Ocelot on https but talk to private services over http) that will be applied to all of the dynamic ReRoutes.
talk to private services over http) that will be applied to all of the dynamic ReRoutes.
The config might look something like The config might look something like
@ -183,4 +177,40 @@ The config might look something like
} }
} }
Ocelot also allows you to set DynamicReRoutes which lets you set rate limiting rules per downstream service. This is useful if you have for example a product and search service and you want to rate limit one more than the other. An example of this would be as follows.
.. code-block:: json
{
"DynamicReRoutes": [
{
"ServiceName": "product",
"RateLimitRule": {
"ClientWhitelist": [],
"EnableRateLimiting": true,
"Period": "1s",
"PeriodTimespan": 1000.0,
"Limit": 3
}
}
],
"GlobalConfiguration": {
"RequestIdKey": null,
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 8523,
},
"RateLimitOptions": {
"ClientIdHeader": "ClientId",
"QuotaExceededMessage": "",
"RateLimitCounterPrefix": "",
"DisableRateLimitHeaders": false,
"HttpStatusCode": 428
}
"DownstreamScheme": "http",
}
}
This configuration means that if you have a request come into Ocelot on /product/* then dynamic routing will kick in and ocelot will use the rate limiting set against the product service in the DynamicReRoutes section.
Please take a look through all of the docs to understand these options. Please take a look through all of the docs to understand these options.

View File

@ -103,6 +103,12 @@ namespace Ocelot.Configuration.Creator
reRoutes.Add(ocelotReRoute); reRoutes.Add(ocelotReRoute);
} }
foreach(var fileDynamicReRoute in fileConfiguration.DynamicReRoutes)
{
var reRoute = SetUpDynamicReRoute(fileDynamicReRoute, fileConfiguration.GlobalConfiguration);
reRoutes.Add(reRoute);
}
var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration); var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
var lbOptions = CreateLoadBalancerOptions(fileConfiguration.GlobalConfiguration.LoadBalancerOptions); var lbOptions = CreateLoadBalancerOptions(fileConfiguration.GlobalConfiguration.LoadBalancerOptions);
@ -124,7 +130,24 @@ namespace Ocelot.Configuration.Creator
return new OkResponse<IInternalConfiguration>(config); return new OkResponse<IInternalConfiguration>(config);
} }
public ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) private ReRoute SetUpDynamicReRoute(FileDynamicReRoute fileDynamicReRoute, FileGlobalConfiguration globalConfiguration)
{
var rateLimitOption = _rateLimitOptionsCreator.Create(fileDynamicReRoute.RateLimitRule, globalConfiguration);
var downstreamReRoute = new DownstreamReRouteBuilder()
.WithEnableRateLimiting(true)
.WithRateLimitOptions(rateLimitOption)
.WithServiceName(fileDynamicReRoute.ServiceName)
.Build();
var reRoute = new ReRouteBuilder()
.WithDownstreamReRoute(downstreamReRoute)
.Build();
return reRoute;
}
private ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration)
{ {
var applicableReRoutes = reRoutes var applicableReRoutes = reRoutes
.SelectMany(x => x.DownstreamReRoute) .SelectMany(x => x.DownstreamReRoute)
@ -186,7 +209,7 @@ namespace Ocelot.Configuration.Creator
var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod.ToArray()); var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod.ToArray());
var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute.RateLimitOptions, globalConfiguration);
var region = _regionCreator.Create(fileReRoute); var region = _regionCreator.Create(fileReRoute);

View File

@ -4,6 +4,6 @@ namespace Ocelot.Configuration.Creator
{ {
public interface IRateLimitOptionsCreator public interface IRateLimitOptionsCreator
{ {
RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting); RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration);
} }
} }

View File

@ -6,23 +6,23 @@ namespace Ocelot.Configuration.Creator
{ {
public class RateLimitOptionsCreator : IRateLimitOptionsCreator public class RateLimitOptionsCreator : IRateLimitOptionsCreator
{ {
public RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting) public RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration)
{ {
RateLimitOptions rateLimitOption = null; RateLimitOptions rateLimitOption = null;
if (enableRateLimiting) if (fileRateLimitRule != null && fileRateLimitRule.EnableRateLimiting)
{ {
rateLimitOption = new RateLimitOptionsBuilder() rateLimitOption = new RateLimitOptionsBuilder()
.WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader) .WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader)
.WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) .WithClientWhiteList(fileRateLimitRule.ClientWhitelist)
.WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders) .WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders)
.WithEnableRateLimiting(fileReRoute.RateLimitOptions.EnableRateLimiting) .WithEnableRateLimiting(fileRateLimitRule.EnableRateLimiting)
.WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode) .WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode)
.WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage) .WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage)
.WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix) .WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix)
.WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, .WithRateLimitRule(new RateLimitRule(fileRateLimitRule.Period,
fileReRoute.RateLimitOptions.PeriodTimespan, fileRateLimitRule.PeriodTimespan,
fileReRoute.RateLimitOptions.Limit)) fileRateLimitRule.Limit))
.Build(); .Build();
} }

View File

@ -9,9 +9,11 @@ namespace Ocelot.Configuration.File
ReRoutes = new List<FileReRoute>(); ReRoutes = new List<FileReRoute>();
GlobalConfiguration = new FileGlobalConfiguration(); GlobalConfiguration = new FileGlobalConfiguration();
Aggregates = new List<FileAggregateReRoute>(); Aggregates = new List<FileAggregateReRoute>();
DynamicReRoutes = new List<FileDynamicReRoute>();
} }
public List<FileReRoute> ReRoutes { get; set; } public List<FileReRoute> ReRoutes { get; set; }
public List<FileDynamicReRoute> DynamicReRoutes { get; set; }
// Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates // Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates
public List<FileAggregateReRoute> Aggregates { get;set; } public List<FileAggregateReRoute> Aggregates { get;set; }

View File

@ -0,0 +1,8 @@
namespace Ocelot.Configuration.File
{
public class FileDynamicReRoute
{
public string ServiceName { get; set; }
public FileRateLimitRule RateLimitRule { get; set; }
}
}

View File

@ -2,6 +2,7 @@
{ {
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Configuration; using Configuration;
using Configuration.Builder; using Configuration.Builder;
using Configuration.Creator; using Configuration.Creator;
@ -43,7 +44,7 @@
var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod }); var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod });
var downstreamReRoute = new DownstreamReRouteBuilder() var downstreamReRouteBuilder = new DownstreamReRouteBuilder()
.WithServiceName(serviceName) .WithServiceName(serviceName)
.WithLoadBalancerKey(loadBalancerKey) .WithLoadBalancerKey(loadBalancerKey)
.WithDownstreamPathTemplate(downstreamPath) .WithDownstreamPathTemplate(downstreamPath)
@ -51,8 +52,22 @@
.WithHttpHandlerOptions(configuration.HttpHandlerOptions) .WithHttpHandlerOptions(configuration.HttpHandlerOptions)
.WithQosOptions(qosOptions) .WithQosOptions(qosOptions)
.WithDownstreamScheme(configuration.DownstreamScheme) .WithDownstreamScheme(configuration.DownstreamScheme)
.WithLoadBalancerOptions(configuration.LoadBalancerOptions) .WithLoadBalancerOptions(configuration.LoadBalancerOptions);
.Build();
var rateLimitOptions = configuration.ReRoutes != null
? configuration.ReRoutes
.SelectMany(x => x.DownstreamReRoute)
.FirstOrDefault(x => x.ServiceName == serviceName)
: null;
if(rateLimitOptions != null)
{
downstreamReRouteBuilder
.WithRateLimitOptions(rateLimitOptions.RateLimitOptions)
.WithEnableRateLimiting(true);
}
var downstreamReRoute = downstreamReRouteBuilder.Build();
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithDownstreamReRoute(downstreamReRoute) .WithDownstreamReRoute(downstreamReRoute)

View File

@ -20,7 +20,9 @@
public IDownstreamRouteProvider Get(IInternalConfiguration config) public IDownstreamRouteProvider Get(IInternalConfiguration config)
{ {
if(!config.ReRoutes.Any() && IsServiceDiscovery(config.ServiceProviderConfiguration)) //todo - this is a bit hacky we are saying there are no reRoutes or there are reRoutes but none of them have
//an upstream path template which means they are dyanmic and service discovery is on...
if((!config.ReRoutes.Any() || config.ReRoutes.All(x => string.IsNullOrEmpty(x.UpstreamPathTemplate.Value))) && IsServiceDiscovery(config.ServiceProviderConfiguration))
{ {
_logger.LogInformation($"Selected {nameof(DownstreamRouteCreator)} as DownstreamRouteProvider for this request"); _logger.LogInformation($"Selected {nameof(DownstreamRouteCreator)} as DownstreamRouteProvider for this request");
return _providers[nameof(DownstreamRouteCreator)]; return _providers[nameof(DownstreamRouteCreator)];

View File

@ -52,6 +52,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
} }
var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value)); var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value));
Logger.LogDebug($"downstream templates are {downstreamPathTemplates}"); Logger.LogDebug($"downstream templates are {downstreamPathTemplates}");
context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues; context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues;

View File

@ -42,7 +42,6 @@
UpstreamPathTemplate = "/api/ClientRateLimit", UpstreamPathTemplate = "/api/ClientRateLimit",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
RequestIdKey = _steps.RequestIdKey, RequestIdKey = _steps.RequestIdKey,
RateLimitOptions = new FileRateLimitRule() RateLimitOptions = new FileRateLimitRule()
{ {
EnableRateLimiting = true, EnableRateLimiting = true,

View File

@ -1,9 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Text; using System.Text;
using Consul;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -22,9 +23,11 @@ namespace Ocelot.AcceptanceTests
private readonly Steps _steps; private readonly Steps _steps;
private IWebHost _fakeConsulBuilder; private IWebHost _fakeConsulBuilder;
private FileConfiguration _config; private FileConfiguration _config;
private List<ServiceEntry> _consulServices;
public ConfigurationInConsulTests() public ConfigurationInConsulTests()
{ {
_consulServices = new List<ServiceEntry>();
_steps = new Steps(); _steps = new Steps();
} }
@ -63,7 +66,7 @@ namespace Ocelot.AcceptanceTests
var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; var fakeConsulServiceDiscoveryUrl = "http://localhost:9500";
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, ""))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
@ -108,7 +111,7 @@ namespace Ocelot.AcceptanceTests
var fakeConsulServiceDiscoveryUrl = "http://localhost:9502"; var fakeConsulServiceDiscoveryUrl = "http://localhost:9502";
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, ""))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache()) .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache())
@ -168,7 +171,7 @@ namespace Ocelot.AcceptanceTests
}; };
this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) this.Given(x => GivenTheConsulConfigurationIs(consulConfig))
.And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, ""))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura")) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
@ -257,7 +260,7 @@ namespace Ocelot.AcceptanceTests
}; };
this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) this.Given(x => GivenTheConsulConfigurationIs(consulConfig))
.And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, ""))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51780", "/status", 200, "Hello from Laura")) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51780", "/status", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
@ -269,6 +272,89 @@ namespace Ocelot.AcceptanceTests
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_handle_request_to_consul_for_downstream_service_and_make_request_no_re_routes_and_rate_limit()
{
const int consulPort = 8523;
const string serviceName = "web";
const int downstreamServicePort = 8187;
var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}";
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = "localhost",
Port = downstreamServicePort,
ID = "web_90_0_2_224_8080",
Tags = new[] {"version-v1"}
},
};
var consulConfig = new FileConfiguration
{
DynamicReRoutes = new List<FileDynamicReRoute>
{
new FileDynamicReRoute
{
ServiceName = serviceName,
RateLimitRule = new FileRateLimitRule()
{
EnableRateLimiting = true,
ClientWhitelist = new List<string>(),
Limit = 3,
Period = "1s",
PeriodTimespan = 1000
}
}
},
GlobalConfiguration = new FileGlobalConfiguration
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Host = "localhost",
Port = consulPort
},
RateLimitOptions = new FileRateLimitOptions()
{
ClientIdHeader = "ClientId",
DisableRateLimitHeaders = false,
QuotaExceededMessage = "",
RateLimitCounterPrefix = "",
HttpStatusCode = 428
},
DownstreamScheme = "http",
}
};
var configuration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Host = "localhost",
Port = consulPort
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/something", 200, "Hello from Laura"))
.And(x => GivenTheConsulConfigurationIs(consulConfig))
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something",1))
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something", 2))
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something",1))
.Then(x => _steps.ThenTheStatusCodeShouldBe(428))
.BDDfy();
}
private void ThenTheConfigIsUpdatedInOcelot() private void ThenTheConfigIsUpdatedInOcelot()
{ {
var result = WaitFor(20000).Until(() => { var result = WaitFor(20000).Until(() => {
@ -292,7 +378,15 @@ namespace Ocelot.AcceptanceTests
_config = config; _config = config;
} }
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries)
{
foreach(var serviceEntry in serviceEntries)
{
_consulServices.Add(serviceEntry);
}
}
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName)
{ {
_fakeConsulBuilder = new WebHostBuilder() _fakeConsulBuilder = new WebHostBuilder()
.UseUrls(url) .UseUrls(url)
@ -336,6 +430,10 @@ namespace Ocelot.AcceptanceTests
throw; throw;
} }
} }
else if (context.Request.Path.Value == $"/v1/health/service/{serviceName}")
{
await context.Response.WriteJsonAsync(_consulServices);
}
}); });
}) })
.Build(); .Build();

View File

@ -17,6 +17,7 @@
using Ocelot.Errors; using Ocelot.Errors;
using Ocelot.UnitTests.TestData; using Ocelot.UnitTests.TestData;
using Ocelot.Values; using Ocelot.Values;
using System;
public class FileInternalConfigurationCreatorTests public class FileInternalConfigurationCreatorTests
{ {
@ -821,6 +822,54 @@
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_set_up_dynamic_re_routes()
{
var reRouteOptions = new ReRouteOptionsBuilder()
.Build();
var downstreamReRoute = new DownstreamReRouteBuilder()
.WithEnableRateLimiting(true)
.WithRateLimitOptions(new RateLimitOptionsBuilder().Build())
.Build();
var rateLimitOptions = new RateLimitOptionsBuilder()
.WithRateLimitRule(new RateLimitRule("1s", 1, 1))
.Build();
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
DynamicReRoutes = new List<FileDynamicReRoute>
{
new FileDynamicReRoute
{
ServiceName = "test",
RateLimitRule = new FileRateLimitRule
{
Period = "1s",
PeriodTimespan = 1,
Limit = 1
}
}
},
}))
.And(x => x.GivenTheConfigIsValid())
.And(x => GivenTheRateLimitCreatorReturns(rateLimitOptions))
.And(x => GivenTheDownstreamAddresses())
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheDynamicReRouteIsSetUp("test", rateLimitOptions.RateLimitRule))
.BDDfy();
}
private void GivenTheRateLimitCreatorReturns(RateLimitOptions rateLimitOptions)
{
_rateLimitOptions
.Setup(x => x.Create(It.IsAny<FileRateLimitRule>(), It.IsAny<FileGlobalConfiguration>()))
.Returns(rateLimitOptions);
}
private void GivenTheConfigIsInvalid(List<Error> errors) private void GivenTheConfigIsInvalid(List<Error> errors)
{ {
_validator _validator
@ -844,7 +893,7 @@
private void ThenTheRateLimitOptionsCreatorIsCalledCorrectly() private void ThenTheRateLimitOptionsCreatorIsCalledCorrectly()
{ {
_rateLimitOptions _rateLimitOptions
.Verify(x => x.Create(It.IsAny<FileReRoute>(), It.IsAny<FileGlobalConfiguration>(), It.IsAny<bool>()), Times.Once); .Verify(x => x.Create(It.IsAny<FileRateLimitRule>(), It.IsAny<FileGlobalConfiguration>()), Times.Once);
} }
private void GivenTheConfigIsValid() private void GivenTheConfigIsValid()
@ -864,6 +913,16 @@
_config = _internalConfigurationCreator.Create(_fileConfiguration).Result; _config = _internalConfigurationCreator.Create(_fileConfiguration).Result;
} }
private void ThenTheDynamicReRouteIsSetUp(string serviceName, RateLimitRule rateLimitOptions)
{
var dynamic = _config.Data.ReRoutes[0].DownstreamReRoute[0];
dynamic.ServiceName.ShouldBe(serviceName);
dynamic.EnableEndpointEndpointRateLimiting.ShouldBeTrue();
dynamic.RateLimitOptions.RateLimitRule.Period.ShouldBe(rateLimitOptions.Period);
dynamic.RateLimitOptions.RateLimitRule.Limit.ShouldBe(rateLimitOptions.Limit);
dynamic.RateLimitOptions.RateLimitRule.PeriodTimespan.ShouldBe(rateLimitOptions.PeriodTimespan);
}
private void ThenTheReRoutesAre(List<ReRoute> expectedReRoutes) private void ThenTheReRoutesAre(List<ReRoute> expectedReRoutes)
{ {
for (int i = 0; i < _config.Data.ReRoutes.Count; i++) for (int i = 0; i < _config.Data.ReRoutes.Count; i++)

View File

@ -88,7 +88,7 @@ namespace Ocelot.UnitTests.Configuration
private void WhenICreate() private void WhenICreate()
{ {
_result = _creator.Create(_fileReRoute, _fileGlobalConfig, _enabled); _result = _creator.Create(_fileReRoute.RateLimitOptions, _fileGlobalConfig);
} }
private void ThenTheFollowingIsReturned(RateLimitOptions expected) private void ThenTheFollowingIsReturned(RateLimitOptions expected)

View File

@ -1,9 +1,3 @@
using Ocelot.DownstreamRouteFinder.Finder;
using Xunit;
using Shouldly;
using Ocelot.Configuration;
using System.Net.Http;
namespace Ocelot.UnitTests.DownstreamRouteFinder namespace Ocelot.UnitTests.DownstreamRouteFinder
{ {
using System; using System;
@ -14,6 +8,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.LoadBalancers;
using Responses; using Responses;
using TestStack.BDDfy; using TestStack.BDDfy;
using Ocelot.DownstreamRouteFinder.Finder;
using Xunit;
using Shouldly;
using Ocelot.Configuration;
using System.Net.Http;
using System.Collections.Generic;
public class DownstreamRouteCreatorTests public class DownstreamRouteCreatorTests
{ {
@ -53,6 +53,34 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_create_downstream_route_with_rate_limit_options()
{
var rateLimitOptions = new RateLimitOptionsBuilder()
.WithEnableRateLimiting(true)
.WithClientIdHeader("test")
.Build();
var downstreamReRoute = new DownstreamReRouteBuilder()
.WithServiceName("auth")
.WithRateLimitOptions(rateLimitOptions)
.Build();
var reRoute = new ReRouteBuilder()
.WithDownstreamReRoute(downstreamReRoute)
.Build();
var reRoutes = new List<ReRoute> { reRoute };
var configuration = new InternalConfiguration(reRoutes, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions);
this.Given(_ => GivenTheConfiguration(configuration))
.When(_ => WhenICreate())
.Then(_ => ThenTheDownstreamRouteIsCreated())
.And(_ => WithRateLimitOptions(rateLimitOptions))
.BDDfy();
}
[Fact] [Fact]
public void should_cache_downstream_route() public void should_cache_downstream_route()
{ {
@ -174,6 +202,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.Returns(options); .Returns(options);
} }
private void WithRateLimitOptions(RateLimitOptions expected)
{
_result.Data.ReRoute.DownstreamReRoute[0].EnableEndpointEndpointRateLimiting.ShouldBeTrue();
_result.Data.ReRoute.DownstreamReRoute[0].RateLimitOptions.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting);
_result.Data.ReRoute.DownstreamReRoute[0].RateLimitOptions.ClientIdHeader.ShouldBe(expected.ClientIdHeader);
}
private void ThenTheDownstreamRouteIsCreated() private void ThenTheDownstreamRouteIsCreated()
{ {
_result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test");

View File

@ -53,6 +53,21 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_return_downstream_route_finder_when_not_dynamic_re_route_and_service_discovery_on()
{
var spConfig = new ServiceProviderConfigurationBuilder().WithHost("test").WithPort(50).WithType("test").Build();
var reRoutes = new List<ReRoute>
{
new ReRouteBuilder().WithUpstreamPathTemplate("woot").Build()
};
this.Given(_ => GivenTheReRoutes(reRoutes, spConfig))
.When(_ => WhenIGet())
.Then(_ => ThenTheResultShouldBe<Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder>())
.BDDfy();
}
[Fact] [Fact]
public void should_return_downstream_route_finder_as_no_service_discovery_given_no_host() public void should_return_downstream_route_finder_as_no_service_discovery_given_no_host()
{ {
@ -101,6 +116,21 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_return_downstream_route_creator_with_dynamic_re_route()
{
var spConfig = new ServiceProviderConfigurationBuilder().WithHost("test").WithPort(50).WithType("test").Build();
var reRoutes = new List<ReRoute>
{
new ReRouteBuilder().Build()
};
this.Given(_ => GivenTheReRoutes(reRoutes, spConfig))
.When(_ => WhenIGet())
.Then(_ => ThenTheResultShouldBe<Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteCreator>())
.BDDfy();
}
private void ThenTheResultShouldBe<T>() private void ThenTheResultShouldBe<T>()
{ {
_result.ShouldBeOfType<T>(); _result.ShouldBeOfType<T>();