Feature/automatic routes with sd (#351)

* #340 started looking at supporting automatic routing when using service discovery

* #340 getting old routing tests to pass

* #340 renamed stuff to provider rather than finder, as its not longer finding anything

* #340 working towards supporting dynamic routing

* #340 loads of refactoring to make configuration work with dynamic routing

* #340 refactor consul config code so the registry class owns it

* #340 default to consul to maintain backwards compat

* #340 added docs, finished this branches todos
This commit is contained in:
Tom Pallister
2018-05-14 21:26:10 +01:00
committed by GitHub
parent dadb43ef6f
commit 1e2e953b2c
64 changed files with 2082 additions and 1221 deletions

View File

@ -0,0 +1,194 @@
using Ocelot.DownstreamRouteFinder.Finder;
using Xunit;
using Shouldly;
using Ocelot.Configuration;
using System.Net.Http;
namespace Ocelot.UnitTests.DownstreamRouteFinder
{
using Moq;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator;
using Ocelot.DownstreamRouteFinder;
using Ocelot.LoadBalancer.LoadBalancers;
using Responses;
using TestStack.BDDfy;
public class DownstreamRouteCreatorTests
{
private readonly DownstreamRouteCreator _creator;
private readonly QoSOptions _qoSOptions;
private readonly HttpHandlerOptions _handlerOptions;
private readonly LoadBalancerOptions _loadBalancerOptions;
private Response<DownstreamRoute> _result;
private string _upstreamHost;
private string _upstreamUrlPath;
private string _upstreamHttpMethod;
private IInternalConfiguration _configuration;
private Mock<IQoSOptionsCreator> _qosOptionsCreator;
public DownstreamRouteCreatorTests()
{
_qosOptionsCreator = new Mock<IQoSOptionsCreator>();
_qoSOptions = new QoSOptionsBuilder().Build();
_handlerOptions = new HttpHandlerOptionsBuilder().Build();
_loadBalancerOptions = new LoadBalancerOptionsBuilder().WithType(nameof(NoLoadBalancer)).Build();
_qosOptionsCreator
.Setup(x => x.Create(It.IsAny<QoSOptions>(), It.IsAny<string>(), It.IsAny<string[]>()))
.Returns(_qoSOptions);
_creator = new DownstreamRouteCreator(_qosOptionsCreator.Object);
}
[Fact]
public void should_create_downstream_route()
{
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions);
this.Given(_ => GivenTheConfiguration(configuration))
.When(_ => WhenICreate())
.Then(_ => ThenTheDownstreamRouteIsCreated())
.BDDfy();
}
[Fact]
public void should_create_downstream_route_with_no_path()
{
var upstreamUrlPath = "/auth/";
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions);
this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath))
.When(_ => WhenICreate())
.Then(_ => ThenTheDownstreamPathIsForwardSlash())
.BDDfy();
}
[Fact]
public void should_create_downstream_route_and_remove_query_string()
{
var upstreamUrlPath = "/auth/test?test=1&best=2";
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions);
this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath))
.When(_ => WhenICreate())
.Then(_ => ThenTheQueryStringIsRemoved())
.BDDfy();
}
[Fact]
public void should_create_downstream_route_for_sticky_sessions()
{
var loadBalancerOptions = new LoadBalancerOptionsBuilder().WithType(nameof(CookieStickySessions)).WithKey("boom").WithExpiryInMs(1).Build();
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", loadBalancerOptions, "http", _qoSOptions, _handlerOptions);
this.Given(_ => GivenTheConfiguration(configuration))
.When(_ => WhenICreate())
.Then(_ => ThenTheStickySessionLoadBalancerIsUsed(loadBalancerOptions))
.BDDfy();
}
[Fact]
public void should_create_downstream_route_with_qos()
{
var qoSOptions = new QoSOptionsBuilder()
.WithExceptionsAllowedBeforeBreaking(1)
.WithTimeoutValue(1)
.Build();
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", qoSOptions, _handlerOptions);
this.Given(_ => GivenTheConfiguration(configuration))
.And(_ => GivenTheQosCreatorReturns(qoSOptions))
.When(_ => WhenICreate())
.Then(_ => ThenTheQosOptionsAreSet(qoSOptions))
.BDDfy();
}
[Fact]
public void should_create_downstream_route_with_handler_options()
{
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions);
this.Given(_ => GivenTheConfiguration(configuration))
.When(_ => WhenICreate())
.Then(_ => ThenTheHandlerOptionsAreSet())
.BDDfy();
}
private void GivenTheQosCreatorReturns(QoSOptions options)
{
_qosOptionsCreator
.Setup(x => x.Create(It.IsAny<QoSOptions>(), It.IsAny<string>(), It.IsAny<string[]>()))
.Returns(options);
}
private void ThenTheDownstreamRouteIsCreated()
{
_result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test");
_result.Data.ReRoute.UpstreamHttpMethod[0].ShouldBe(HttpMethod.Get);
_result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth");
_result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET");
_result.Data.ReRoute.DownstreamReRoute[0].UseServiceDiscovery.ShouldBeTrue();
_result.Data.ReRoute.DownstreamReRoute[0].HttpHandlerOptions.ShouldNotBeNull();
_result.Data.ReRoute.DownstreamReRoute[0].QosOptions.ShouldNotBeNull();
_result.Data.ReRoute.DownstreamReRoute[0].DownstreamScheme.ShouldBe("http");
_result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerOptions.Type.ShouldBe(nameof(NoLoadBalancer));
_result.Data.ReRoute.DownstreamReRoute[0].HttpHandlerOptions.ShouldBe(_handlerOptions);
_result.Data.ReRoute.DownstreamReRoute[0].QosOptions.ShouldBe(_qoSOptions);
}
private void ThenTheDownstreamPathIsForwardSlash()
{
_result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/");
_result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth");
_result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/|GET");
}
private void ThenTheQueryStringIsRemoved()
{
_result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test");
_result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth");
_result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET");
}
private void ThenTheStickySessionLoadBalancerIsUsed(LoadBalancerOptions expected)
{
_result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe($"{nameof(CookieStickySessions)}:boom");
_result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerOptions.Type.ShouldBe(nameof(CookieStickySessions));
_result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerOptions.ShouldBe(expected);
}
private void ThenTheQosOptionsAreSet(QoSOptions expected)
{
_result.Data.ReRoute.DownstreamReRoute[0].QosOptions.ShouldBe(expected);
_result.Data.ReRoute.DownstreamReRoute[0].QosOptions.UseQos.ShouldBeTrue();
_qosOptionsCreator
.Verify(x => x.Create(expected, _upstreamUrlPath, It.IsAny<string[]>()), Times.Once);
}
private void GivenTheConfiguration(IInternalConfiguration config)
{
_upstreamHost = "doesnt matter";
_upstreamUrlPath = "/auth/test";
_upstreamHttpMethod = "GET";
_configuration = config;
}
private void GivenTheConfiguration(IInternalConfiguration config, string upstreamUrlPath)
{
_upstreamHost = "doesnt matter";
_upstreamUrlPath = upstreamUrlPath;
_upstreamHttpMethod = "GET";
_configuration = config;
}
private void ThenTheHandlerOptionsAreSet()
{
_result.Data.ReRoute.DownstreamReRoute[0].HttpHandlerOptions.ShouldBe(_handlerOptions);
}
private void WhenICreate()
{
_result = _creator.Get(_upstreamUrlPath, _upstreamHttpMethod, _configuration, _upstreamHost);
}
}
}

View File

@ -1,98 +1,99 @@
namespace Ocelot.UnitTests.DownstreamRouteFinder
{
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.DownstreamRouteFinder;
using Ocelot.DownstreamRouteFinder.Finder;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Logging;
using Ocelot.Responses;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
using Ocelot.Configuration.Repository;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
public class DownstreamRouteFinderMiddlewareTests
{
private readonly Mock<IDownstreamRouteFinder> _finder;
private readonly Mock<IInternalConfigurationRepository> _repo;
private Response<DownstreamRoute> _downstreamRoute;
private IInternalConfiguration _config;
private Mock<IOcelotLoggerFactory> _loggerFactory;
private Mock<IOcelotLogger> _logger;
private readonly DownstreamRouteFinderMiddleware _middleware;
private readonly DownstreamContext _downstreamContext;
private OcelotRequestDelegate _next;
private readonly Mock<IMultiplexer> _multiplexer;
public DownstreamRouteFinderMiddlewareTests()
{
_repo = new Mock<IInternalConfigurationRepository>();
_finder = new Mock<IDownstreamRouteFinder>();
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
_loggerFactory = new Mock<IOcelotLoggerFactory>();
_logger = new Mock<IOcelotLogger>();
_loggerFactory.Setup(x => x.CreateLogger<DownstreamRouteFinderMiddleware>()).Returns(_logger.Object);
_next = context => Task.CompletedTask;
_multiplexer = new Mock<IMultiplexer>();
_middleware = new DownstreamRouteFinderMiddleware(_next, _loggerFactory.Object, _finder.Object, _repo.Object, _multiplexer.Object);
}
[Fact]
public void should_call_scoped_data_repository_correctly()
{
var config = new InternalConfiguration(null, null, new ServiceProviderConfigurationBuilder().Build(), "");
var downstreamReRoute = new DownstreamReRouteBuilder()
.WithDownstreamPathTemplate("any old string")
.WithUpstreamHttpMethod(new List<string> {"Get"})
.Build();
this.Given(x => x.GivenTheDownStreamRouteFinderReturns(
new DownstreamRoute(
new List<PlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithDownstreamReRoute(downstreamReRoute)
.WithUpstreamHttpMethod(new List<string> { "Get" })
.Build())))
.And(x => GivenTheFollowingConfig(config))
.When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
.BDDfy();
}
private void WhenICallTheMiddleware()
{
_middleware.Invoke(_downstreamContext).GetAwaiter().GetType();
}
private void GivenTheFollowingConfig(IInternalConfiguration config)
{
_config = config;
_repo
.Setup(x => x.Get())
.Returns(new OkResponse<IInternalConfiguration>(_config));
}
private void GivenTheDownStreamRouteFinderReturns(DownstreamRoute downstreamRoute)
{
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
_finder
.Setup(x => x.FindDownstreamRoute(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IInternalConfiguration>(), It.IsAny<string>()))
.Returns(_downstreamRoute);
}
private void ThenTheScopedDataRepositoryIsCalledCorrectly()
{
_downstreamContext.TemplatePlaceholderNameAndValues.ShouldBe(_downstreamRoute.Data.TemplatePlaceholderNameAndValues);
_downstreamContext.ServiceProviderConfiguration.ShouldBe(_config.ServiceProviderConfiguration);
}
}
}
namespace Ocelot.UnitTests.DownstreamRouteFinder
{
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.DownstreamRouteFinder;
using Ocelot.DownstreamRouteFinder.Finder;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Logging;
using Ocelot.Responses;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
using Ocelot.Configuration.Repository;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
public class DownstreamRouteFinderMiddlewareTests
{
private readonly Mock<IDownstreamRouteProvider> _finder;
private readonly Mock<IDownstreamRouteProviderFactory> _factory;
private readonly Mock<IInternalConfigurationRepository> _repo;
private Response<DownstreamRoute> _downstreamRoute;
private IInternalConfiguration _config;
private Mock<IOcelotLoggerFactory> _loggerFactory;
private Mock<IOcelotLogger> _logger;
private readonly DownstreamRouteFinderMiddleware _middleware;
private readonly DownstreamContext _downstreamContext;
private OcelotRequestDelegate _next;
private readonly Mock<IMultiplexer> _multiplexer;
public DownstreamRouteFinderMiddlewareTests()
{
_repo = new Mock<IInternalConfigurationRepository>();
_finder = new Mock<IDownstreamRouteProvider>();
_factory = new Mock<IDownstreamRouteProviderFactory>();
_factory.Setup(x => x.Get(It.IsAny<IInternalConfiguration>())).Returns(_finder.Object);
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
_loggerFactory = new Mock<IOcelotLoggerFactory>();
_logger = new Mock<IOcelotLogger>();
_loggerFactory.Setup(x => x.CreateLogger<DownstreamRouteFinderMiddleware>()).Returns(_logger.Object);
_next = context => Task.CompletedTask;
_multiplexer = new Mock<IMultiplexer>();
_middleware = new DownstreamRouteFinderMiddleware(_next, _loggerFactory.Object, _factory.Object, _repo.Object, _multiplexer.Object);
}
[Fact]
public void should_call_scoped_data_repository_correctly()
{
var config = new InternalConfiguration(null, null, new ServiceProviderConfigurationBuilder().Build(), "", new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build());
var downstreamReRoute = new DownstreamReRouteBuilder()
.WithDownstreamPathTemplate("any old string")
.WithUpstreamHttpMethod(new List<string> {"Get"})
.Build();
this.Given(x => x.GivenTheDownStreamRouteFinderReturns(
new DownstreamRoute(
new List<PlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithDownstreamReRoute(downstreamReRoute)
.WithUpstreamHttpMethod(new List<string> { "Get" })
.Build())))
.And(x => GivenTheFollowingConfig(config))
.When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
.BDDfy();
}
private void WhenICallTheMiddleware()
{
_middleware.Invoke(_downstreamContext).GetAwaiter().GetType();
}
private void GivenTheFollowingConfig(IInternalConfiguration config)
{
_config = config;
_downstreamContext.Configuration = config;
}
private void GivenTheDownStreamRouteFinderReturns(DownstreamRoute downstreamRoute)
{
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
_finder
.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IInternalConfiguration>(), It.IsAny<string>()))
.Returns(_downstreamRoute);
}
private void ThenTheScopedDataRepositoryIsCalledCorrectly()
{
_downstreamContext.TemplatePlaceholderNameAndValues.ShouldBe(_downstreamRoute.Data.TemplatePlaceholderNameAndValues);
_downstreamContext.Configuration.ServiceProviderConfiguration.ShouldBe(_config.ServiceProviderConfiguration);
}
}
}

View File

@ -0,0 +1,98 @@
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.DownstreamRouteFinder;
using Ocelot.DownstreamRouteFinder.Finder;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Responses;
using Ocelot.Values;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.DownstreamRouteFinder
{
using Ocelot.Configuration.Creator;
public class DownstreamRouteProviderFactoryTests
{
private readonly DownstreamRouteProviderFactory _factory;
private IInternalConfiguration _config;
private IDownstreamRouteProvider _result;
public DownstreamRouteProviderFactoryTests()
{
var services = new ServiceCollection();
services.AddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
services.AddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
services.AddSingleton<IQoSOptionsCreator, QoSOptionsCreator>();
services.AddSingleton<IDownstreamRouteProvider, Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder>();
services.AddSingleton<IDownstreamRouteProvider, Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteCreator>();
var provider = services.BuildServiceProvider();
_factory = new DownstreamRouteProviderFactory(provider);
}
[Fact]
public void should_return_downstream_route_finder()
{
var reRoutes = new List<ReRoute>
{
new ReRouteBuilder().Build()
};
this.Given(_ => GivenTheReRoutes(reRoutes))
.When(_ => WhenIGet())
.Then(_ => ThenTheResultShouldBe<Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder>())
.BDDfy();
}
[Fact]
public void should_return_downstream_route_finder_as_no_service_discovery()
{
var spConfig = new ServiceProviderConfigurationBuilder().Build();
var reRoutes = new List<ReRoute>
{
};
this.Given(_ => GivenTheReRoutes(reRoutes, spConfig))
.When(_ => WhenIGet())
.Then(_ => ThenTheResultShouldBe<Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder>())
.BDDfy();
}
[Fact]
public void should_return_downstream_route_creator()
{
var spConfig = new ServiceProviderConfigurationBuilder().WithHost("test").WithPort(50).Build();
var reRoutes = new List<ReRoute>
{
};
this.Given(_ => GivenTheReRoutes(reRoutes, spConfig))
.When(_ => WhenIGet())
.Then(_ => ThenTheResultShouldBe<Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteCreator>())
.BDDfy();
}
private void ThenTheResultShouldBe<T>()
{
_result.ShouldBeOfType<T>();
}
private void WhenIGet()
{
_result = _factory.Get(_config);
}
private void GivenTheReRoutes(List<ReRoute> reRoutes)
{
_config = new InternalConfiguration(reRoutes, "", null, "", new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build());
}
private void GivenTheReRoutes(List<ReRoute> reRoutes, ServiceProviderConfiguration config)
{
_config = new InternalConfiguration(reRoutes, "", config, "", new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build());
}
}
}