From 789782c8707cbe9bc90ffe8107eb1c6edb330953 Mon Sep 17 00:00:00 2001 From: David Lievrouw Date: Wed, 10 Jul 2019 18:10:26 +0200 Subject: [PATCH 1/9] Add 'Name' property to ILoadBalancer, for future use in LoadBalancerFactory. --- src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs | 2 ++ src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs | 2 ++ src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs | 2 ++ src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs | 2 ++ src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs | 2 ++ test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs | 4 ++++ 6 files changed, 14 insertions(+) diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs b/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs index 775ed7ce..2429e158 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs @@ -84,5 +84,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers public void Release(ServiceHostAndPort hostAndPort) { } + + public string Name => GetType().Name; } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs index 4f8ec2b7..6d74b686 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs @@ -10,5 +10,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers Task> Lease(DownstreamContext context); void Release(ServiceHostAndPort hostAndPort); + + string Name { get; } } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs index ee7c0fdd..24d953e2 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs @@ -71,6 +71,8 @@ namespace Ocelot.LoadBalancer.LoadBalancers } } + public string Name => GetType().Name; + private Lease AddConnection(Lease lease) { return new Lease(lease.HostAndPort, lease.Connections + 1); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs index 112fd5bb..becf96b6 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs @@ -33,5 +33,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers public void Release(ServiceHostAndPort hostAndPort) { } + + public string Name => GetType().Name; } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs index 3500efe0..6be24f71 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs @@ -38,5 +38,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers public void Release(ServiceHostAndPort hostAndPort) { } + + public string Name => GetType().Name; } } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index 57252b2b..6f5a50c1 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -164,6 +164,8 @@ namespace Ocelot.UnitTests.LoadBalancer { throw new NotImplementedException(); } + + public string Name => GetType().Name; } private class FakeRoundRobinLoadBalancer : ILoadBalancer @@ -177,6 +179,8 @@ namespace Ocelot.UnitTests.LoadBalancer { throw new NotImplementedException(); } + + public string Name => GetType().Name; } } } From c73cf5990855a3ca4971f7ff39fddecaa9be0121 Mon Sep 17 00:00:00 2001 From: David Lievrouw Date: Wed, 10 Jul 2019 18:28:11 +0200 Subject: [PATCH 2/9] Revert "Add 'Name' property to ILoadBalancer, for future use in LoadBalancerFactory." This reverts commit 78ead1606dc6a4d6b47b5e63bbf7927ef02e9320. --- src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs | 2 -- src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs | 2 -- src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs | 2 -- src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs | 2 -- src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs | 2 -- test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs | 4 ---- 6 files changed, 14 deletions(-) diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs b/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs index 2429e158..775ed7ce 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs @@ -84,7 +84,5 @@ namespace Ocelot.LoadBalancer.LoadBalancers public void Release(ServiceHostAndPort hostAndPort) { } - - public string Name => GetType().Name; } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs index 6d74b686..4f8ec2b7 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs @@ -10,7 +10,5 @@ namespace Ocelot.LoadBalancer.LoadBalancers Task> Lease(DownstreamContext context); void Release(ServiceHostAndPort hostAndPort); - - string Name { get; } } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs index 24d953e2..ee7c0fdd 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs @@ -71,8 +71,6 @@ namespace Ocelot.LoadBalancer.LoadBalancers } } - public string Name => GetType().Name; - private Lease AddConnection(Lease lease) { return new Lease(lease.HostAndPort, lease.Connections + 1); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs index becf96b6..112fd5bb 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs @@ -33,7 +33,5 @@ namespace Ocelot.LoadBalancer.LoadBalancers public void Release(ServiceHostAndPort hostAndPort) { } - - public string Name => GetType().Name; } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs index 6be24f71..3500efe0 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs @@ -38,7 +38,5 @@ namespace Ocelot.LoadBalancer.LoadBalancers public void Release(ServiceHostAndPort hostAndPort) { } - - public string Name => GetType().Name; } } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index 6f5a50c1..57252b2b 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -164,8 +164,6 @@ namespace Ocelot.UnitTests.LoadBalancer { throw new NotImplementedException(); } - - public string Name => GetType().Name; } private class FakeRoundRobinLoadBalancer : ILoadBalancer @@ -179,8 +177,6 @@ namespace Ocelot.UnitTests.LoadBalancer { throw new NotImplementedException(); } - - public string Name => GetType().Name; } } } From de012a079409fa1aad23dbeef29ee89faf871381 Mon Sep 17 00:00:00 2001 From: David Lievrouw Date: Wed, 10 Jul 2019 19:15:17 +0200 Subject: [PATCH 3/9] Rewire LoadBalancerFactory to allow injecting custom load balancers, by introducing LoadBalancerCreators --- .../DependencyInjection/OcelotBuilder.cs | 4 + .../CookieStickySessionsCreator.cs | 20 +++ .../LoadBalancers/ILoadBalancerCreator.cs | 11 ++ .../LoadBalancers/LeastConnectionCreator.cs | 15 +++ .../LoadBalancers/LoadBalancerFactory.cs | 55 ++++---- .../LoadBalancers/NoLoadBalancerCreator.cs | 15 +++ .../LoadBalancer/LoadBalancers/RoundRobin.cs | 16 +-- .../LoadBalancers/RoundRobinCreator.cs | 15 +++ .../DependencyInjection/OcelotBuilderTests.cs | 40 ++++++ .../CookieStickySessionsCreatorTests.cs | 73 +++++++++++ .../LeastConnectionCreatorTests.cs | 73 +++++++++++ .../LoadBalancer/LoadBalancerFactoryTests.cs | 117 ++++++++++++------ .../NoLoadBalancerCreatorTests.cs | 72 +++++++++++ .../LoadBalancer/RoundRobinCreatorTests.cs | 72 +++++++++++ 14 files changed, 521 insertions(+), 77 deletions(-) create mode 100644 src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessionsCreator.cs create mode 100644 src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerCreator.cs create mode 100644 src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionCreator.cs create mode 100644 src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancerCreator.cs create mode 100644 src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinCreator.cs create mode 100644 test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsCreatorTests.cs create mode 100644 test/Ocelot.UnitTests/LoadBalancer/LeastConnectionCreatorTests.cs create mode 100644 test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerCreatorTests.cs create mode 100644 test/Ocelot.UnitTests/LoadBalancer/RoundRobinCreatorTests.cs diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 0336f691..a9290ad7 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -87,6 +87,10 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessionsCreator.cs b/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessionsCreator.cs new file mode 100644 index 00000000..e78b1884 --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessionsCreator.cs @@ -0,0 +1,20 @@ +namespace Ocelot.LoadBalancer.LoadBalancers +{ + using System.Threading.Tasks; + using Ocelot.Configuration; + using Ocelot.Infrastructure; + using Ocelot.ServiceDiscovery.Providers; + + public class CookieStickySessionsCreator : ILoadBalancerCreator + { + public ILoadBalancer Create(DownstreamReRoute reRoute, IServiceDiscoveryProvider serviceProvider) + { + var loadBalancer = new RoundRobin(async () => await serviceProvider.Get()); + var bus = new InMemoryBus(); + return new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, + reRoute.LoadBalancerOptions.ExpiryInMs, bus); + } + + public string Type => nameof(CookieStickySessions); + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerCreator.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerCreator.cs new file mode 100644 index 00000000..4831e62f --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerCreator.cs @@ -0,0 +1,11 @@ +using Ocelot.Configuration; +using Ocelot.ServiceDiscovery.Providers; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public interface ILoadBalancerCreator + { + ILoadBalancer Create(DownstreamReRoute reRoute, IServiceDiscoveryProvider serviceProvider); + string Type { get; } + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionCreator.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionCreator.cs new file mode 100644 index 00000000..e9e9c561 --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionCreator.cs @@ -0,0 +1,15 @@ +namespace Ocelot.LoadBalancer.LoadBalancers +{ + using Ocelot.Configuration; + using Ocelot.ServiceDiscovery.Providers; + + public class LeastConnectionCreator : ILoadBalancerCreator + { + public ILoadBalancer Create(DownstreamReRoute reRoute, IServiceDiscoveryProvider serviceProvider) + { + return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName); + } + + public string Type => nameof(LeastConnection); + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs index 12725a57..2b7e71e2 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs @@ -1,47 +1,42 @@ -using Ocelot.Configuration; -using Ocelot.Infrastructure; -using Ocelot.Responses; -using Ocelot.ServiceDiscovery; -using System.Threading.Tasks; - -namespace Ocelot.LoadBalancer.LoadBalancers +namespace Ocelot.LoadBalancer.LoadBalancers { + using System.Collections.Generic; + using System.Linq; + using Ocelot.Configuration; + using Ocelot.Responses; + using System.Threading.Tasks; + using Ocelot.ServiceDiscovery; + public class LoadBalancerFactory : ILoadBalancerFactory { private readonly IServiceDiscoveryProviderFactory _serviceProviderFactory; + private readonly IEnumerable _loadBalancerCreators; - public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFactory) + public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFactory, IEnumerable loadBalancerCreators) { _serviceProviderFactory = serviceProviderFactory; + _loadBalancerCreators = loadBalancerCreators; } - public async Task> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config) + public Task> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config) { - var response = _serviceProviderFactory.Get(config, reRoute); + var serviceProviderFactoryResponse = _serviceProviderFactory.Get(config, reRoute); - if (response.IsError) + Response response; + if (serviceProviderFactoryResponse.IsError) { - return new ErrorResponse(response.Errors); + response = new ErrorResponse(serviceProviderFactoryResponse.Errors); + } + else + { + var serviceProvider = serviceProviderFactoryResponse.Data; + var requestedType = reRoute.LoadBalancerOptions?.Type ?? nameof(NoLoadBalancer); + var applicableCreator = _loadBalancerCreators.Single(c => c.Type == requestedType); + var createdLoadBalancer = applicableCreator.Create(reRoute, serviceProvider); + response = new OkResponse(createdLoadBalancer); } - var serviceProvider = response.Data; - - switch (reRoute.LoadBalancerOptions?.Type) - { - case nameof(RoundRobin): - return new OkResponse(new RoundRobin(async () => await serviceProvider.Get())); - - case nameof(LeastConnection): - return new OkResponse(new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName)); - - case nameof(CookieStickySessions): - var loadBalancer = new RoundRobin(async () => await serviceProvider.Get()); - var bus = new InMemoryBus(); - return new OkResponse(new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs, bus)); - - default: - return new OkResponse(new NoLoadBalancer(async () => await serviceProvider.Get())); - } + return Task.FromResult(response); } } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancerCreator.cs b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancerCreator.cs new file mode 100644 index 00000000..fc1de3fd --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancerCreator.cs @@ -0,0 +1,15 @@ +namespace Ocelot.LoadBalancer.LoadBalancers +{ + using Ocelot.Configuration; + using Ocelot.ServiceDiscovery.Providers; + + public class NoLoadBalancerCreator : ILoadBalancerCreator + { + public ILoadBalancer Create(DownstreamReRoute reRoute, IServiceDiscoveryProvider serviceProvider) + { + return new NoLoadBalancer(async () => await serviceProvider.Get()); + } + + public string Type => nameof(NoLoadBalancer); + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs index 3500efe0..492a2c57 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs @@ -1,12 +1,12 @@ -using Ocelot.Middleware; -using Ocelot.Responses; -using Ocelot.Values; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Ocelot.LoadBalancer.LoadBalancers +namespace Ocelot.LoadBalancer.LoadBalancers { + using Ocelot.Middleware; + using Ocelot.Responses; + using Ocelot.Values; + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + public class RoundRobin : ILoadBalancer { private readonly Func>> _services; diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinCreator.cs b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinCreator.cs new file mode 100644 index 00000000..8981074d --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinCreator.cs @@ -0,0 +1,15 @@ +namespace Ocelot.LoadBalancer.LoadBalancers +{ + using Ocelot.Configuration; + using Ocelot.ServiceDiscovery.Providers; + + public class RoundRobinCreator : ILoadBalancerCreator + { + public ILoadBalancer Create(DownstreamReRoute reRoute, IServiceDiscoveryProvider serviceProvider) + { + return new RoundRobin(async () => await serviceProvider.Get()); + } + + public string Type => nameof(RoundRobin); + } +} diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 0f1ddb60..499c2a75 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -1,3 +1,7 @@ +using Ocelot.Configuration; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.ServiceDiscovery.Providers; + namespace Ocelot.UnitTests.DependencyInjection { using Microsoft.AspNetCore.Hosting; @@ -158,11 +162,26 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } + [Fact] + public void should_add_custom_load_balancer_creators() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddSingletonLoadBalancerCreator()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) + .BDDfy(); + } + private void AddSingletonDefinedAggregator() where T : class, IDefinedAggregator { _ocelotBuilder.AddSingletonDefinedAggregator(); } + + private void AddSingletonLoadBalancerCreator() + where T : class, ILoadBalancerCreator + { + _ocelotBuilder.Services.AddSingleton(); + } private void AddTransientDefinedAggregator() where T : class, IDefinedAggregator @@ -238,6 +257,17 @@ namespace Ocelot.UnitTests.DependencyInjection handlers[0].ShouldBeOfType(); handlers[1].ShouldBeOfType(); } + + private void ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators() + { + _serviceProvider = _services.BuildServiceProvider(); + var creators = _serviceProvider.GetServices().ToList(); + creators.Count(c => c.GetType() == typeof(NoLoadBalancerCreator)).ShouldBe(1); + creators.Count(c => c.GetType() == typeof(RoundRobinCreator)).ShouldBe(1); + creators.Count(c => c.GetType() == typeof(CookieStickySessionsCreator)).ShouldBe(1); + creators.Count(c => c.GetType() == typeof(LeastConnectionCreator)).ShouldBe(1); + creators.Count(c => c.GetType() == typeof(FakeCustomLoadBalancerCreator)).ShouldBe(1); + } private void ThenTheAggregatorsAreTransient() { @@ -323,5 +353,15 @@ namespace Ocelot.UnitTests.DependencyInjection { _ex.ShouldBeNull(); } + + private class FakeCustomLoadBalancerCreator : ILoadBalancerCreator + { + public ILoadBalancer Create(DownstreamReRoute reRoute, IServiceDiscoveryProvider serviceProvider) + { + throw new NotImplementedException(); + } + + public string Type { get; } + } } } diff --git a/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsCreatorTests.cs b/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsCreatorTests.cs new file mode 100644 index 00000000..20f65da4 --- /dev/null +++ b/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsCreatorTests.cs @@ -0,0 +1,73 @@ +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.ServiceDiscovery.Providers; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class CookieStickySessionsCreatorTests + { + private readonly CookieStickySessionsCreator _creator; + private readonly Mock _serviceProvider; + private DownstreamReRoute _reRoute; + private ILoadBalancer _loadBalancer; + private string _typeName; + + public CookieStickySessionsCreatorTests() + { + _creator = new CookieStickySessionsCreator(); + _serviceProvider = new Mock(); + } + + [Fact] + public void should_return_expected_name() + { + var reRoute = new DownstreamReRouteBuilder() + .WithLoadBalancerOptions(new LoadBalancerOptions("myType", "myKey", 1000)) + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_instance_of_expected_load_balancer_type() + { + this.When(x => x.WhenIGetTheLoadBalancerTypeName()) + .Then(x => x.ThenTheLoadBalancerTypeIs("CookieStickySessions")) + .BDDfy(); + } + + private void GivenAReRoute(DownstreamReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGetTheLoadBalancer() + { + _loadBalancer = _creator.Create(_reRoute, _serviceProvider.Object); + } + + private void WhenIGetTheLoadBalancerTypeName() + { + _typeName = _creator.Type; + } + + private void ThenTheLoadBalancerIsReturned() + where T : ILoadBalancer + { + _loadBalancer.ShouldBeOfType(); + } + + private void ThenTheLoadBalancerTypeIs(string type) + { + _typeName.ShouldBe(type); + } + } +} diff --git a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionCreatorTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionCreatorTests.cs new file mode 100644 index 00000000..507958fd --- /dev/null +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionCreatorTests.cs @@ -0,0 +1,73 @@ +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.ServiceDiscovery.Providers; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class LeastConnectionCreatorTests + { + private readonly LeastConnectionCreator _creator; + private readonly Mock _serviceProvider; + private DownstreamReRoute _reRoute; + private ILoadBalancer _loadBalancer; + private string _typeName; + + public LeastConnectionCreatorTests() + { + _creator = new LeastConnectionCreator(); + _serviceProvider = new Mock(); + } + + [Fact] + public void should_return_expected_name() + { + var reRoute = new DownstreamReRouteBuilder() + .WithServiceName("myService") + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_instance_of_expected_load_balancer_type() + { + this.When(x => x.WhenIGetTheLoadBalancerTypeName()) + .Then(x => x.ThenTheLoadBalancerTypeIs("LeastConnection")) + .BDDfy(); + } + + private void GivenAReRoute(DownstreamReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGetTheLoadBalancer() + { + _loadBalancer = _creator.Create(_reRoute, _serviceProvider.Object); + } + + private void WhenIGetTheLoadBalancerTypeName() + { + _typeName = _creator.Type; + } + + private void ThenTheLoadBalancerIsReturned() + where T : ILoadBalancer + { + _loadBalancer.ShouldBeOfType(); + } + + private void ThenTheLoadBalancerTypeIs(string type) + { + _typeName.ShouldBe(type); + } + } +} diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index 8df47cea..70b9a837 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -7,6 +7,9 @@ using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery.Providers; using Shouldly; using System.Collections.Generic; +using System.Threading.Tasks; +using Ocelot.Middleware; +using Ocelot.Values; using TestStack.BDDfy; using Xunit; @@ -18,6 +21,7 @@ namespace Ocelot.UnitTests.LoadBalancer private readonly LoadBalancerFactory _factory; private Response _result; private readonly Mock _serviceProviderFactory; + private readonly IEnumerable _loadBalancerCreators; private readonly Mock _serviceProvider; private ServiceProviderConfiguration _serviceProviderConfig; @@ -25,7 +29,13 @@ namespace Ocelot.UnitTests.LoadBalancer { _serviceProviderFactory = new Mock(); _serviceProvider = new Mock(); - _factory = new LoadBalancerFactory(_serviceProviderFactory.Object); + _loadBalancerCreators = new ILoadBalancerCreator[] + { + new FakeLoadBalancerCreator(), + new FakeLoadBalancerCreator(), + new FakeLoadBalancerCreator(nameof(NoLoadBalancer)), + }; + _factory = new LoadBalancerFactory(_serviceProviderFactory.Object, _loadBalancerCreators); } [Fact] @@ -39,15 +49,15 @@ namespace Ocelot.UnitTests.LoadBalancer .And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build())) .And(x => x.GivenTheServiceProviderFactoryReturns()) .When(x => x.WhenIGetTheLoadBalancer()) - .Then(x => x.ThenTheLoadBalancerIsReturned()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) .BDDfy(); } [Fact] - public void should_return_round_robin_load_balancer() + public void should_return_matching_load_balancer() { var reRoute = new DownstreamReRouteBuilder() - .WithLoadBalancerOptions(new LoadBalancerOptions("RoundRobin", "", 0)) + .WithLoadBalancerOptions(new LoadBalancerOptions("FakeLoadBalancerTwo", "", 0)) .WithUpstreamHttpMethod(new List { "Get" }) .Build(); @@ -55,31 +65,15 @@ namespace Ocelot.UnitTests.LoadBalancer .And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build())) .And(x => x.GivenTheServiceProviderFactoryReturns()) .When(x => x.WhenIGetTheLoadBalancer()) - .Then(x => x.ThenTheLoadBalancerIsReturned()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) .BDDfy(); } - - [Fact] - public void should_return_round_least_connection_balancer() - { - var reRoute = new DownstreamReRouteBuilder() - .WithLoadBalancerOptions(new LoadBalancerOptions("LeastConnection", "", 0)) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build(); - - this.Given(x => x.GivenAReRoute(reRoute)) - .And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build())) - .And(x => x.GivenTheServiceProviderFactoryReturns()) - .When(x => x.WhenIGetTheLoadBalancer()) - .Then(x => x.ThenTheLoadBalancerIsReturned()) - .BDDfy(); - } - + [Fact] public void should_call_service_provider() { var reRoute = new DownstreamReRouteBuilder() - .WithLoadBalancerOptions(new LoadBalancerOptions("RoundRobin", "", 0)) + .WithLoadBalancerOptions(new LoadBalancerOptions("FakeLoadBalancerOne", "", 0)) .WithUpstreamHttpMethod(new List { "Get" }) .Build(); @@ -91,22 +85,6 @@ namespace Ocelot.UnitTests.LoadBalancer .BDDfy(); } - [Fact] - public void should_return_sticky_session() - { - var reRoute = new DownstreamReRouteBuilder() - .WithLoadBalancerOptions(new LoadBalancerOptions("CookieStickySessions", "", 0)) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build(); - - this.Given(x => x.GivenAReRoute(reRoute)) - .And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build())) - .And(x => x.GivenTheServiceProviderFactoryReturns()) - .When(x => x.WhenIGetTheLoadBalancer()) - .Then(x => x.ThenTheLoadBalancerIsReturned()) - .BDDfy(); - } - private void GivenAServiceProviderConfig(ServiceProviderConfiguration serviceProviderConfig) { _serviceProviderConfig = serviceProviderConfig; @@ -139,5 +117,66 @@ namespace Ocelot.UnitTests.LoadBalancer { _result.Data.ShouldBeOfType(); } + + private class FakeLoadBalancerCreator : ILoadBalancerCreator + where T : ILoadBalancer, new() + { + + public FakeLoadBalancerCreator() + { + Type = typeof(T).Name; + } + + public FakeLoadBalancerCreator(string type) + { + Type = type; + } + + public ILoadBalancer Create(DownstreamReRoute reRoute, IServiceDiscoveryProvider serviceProvider) + { + return new T(); + } + + public string Type { get; } + } + + private class FakeLoadBalancerOne : ILoadBalancer + { + public Task> Lease(DownstreamContext context) + { + throw new System.NotImplementedException(); + } + + public void Release(ServiceHostAndPort hostAndPort) + { + throw new System.NotImplementedException(); + } + } + + private class FakeLoadBalancerTwo : ILoadBalancer + { + public Task> Lease(DownstreamContext context) + { + throw new System.NotImplementedException(); + } + + public void Release(ServiceHostAndPort hostAndPort) + { + throw new System.NotImplementedException(); + } + } + + private class FakeNoLoadBalancer : ILoadBalancer + { + public Task> Lease(DownstreamContext context) + { + throw new System.NotImplementedException(); + } + + public void Release(ServiceHostAndPort hostAndPort) + { + throw new System.NotImplementedException(); + } + } } } diff --git a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerCreatorTests.cs b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerCreatorTests.cs new file mode 100644 index 00000000..6b61c5a9 --- /dev/null +++ b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerCreatorTests.cs @@ -0,0 +1,72 @@ +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.ServiceDiscovery.Providers; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class NoLoadBalancerCreatorTests + { + private readonly NoLoadBalancerCreator _creator; + private readonly Mock _serviceProvider; + private DownstreamReRoute _reRoute; + private ILoadBalancer _loadBalancer; + private string _typeName; + + public NoLoadBalancerCreatorTests() + { + _creator = new NoLoadBalancerCreator(); + _serviceProvider = new Mock(); + } + + [Fact] + public void should_return_expected_name() + { + var reRoute = new DownstreamReRouteBuilder() + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_instance_of_expected_load_balancer_type() + { + this.When(x => x.WhenIGetTheLoadBalancerTypeName()) + .Then(x => x.ThenTheLoadBalancerTypeIs("NoLoadBalancer")) + .BDDfy(); + } + + private void GivenAReRoute(DownstreamReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGetTheLoadBalancer() + { + _loadBalancer = _creator.Create(_reRoute, _serviceProvider.Object); + } + + private void WhenIGetTheLoadBalancerTypeName() + { + _typeName = _creator.Type; + } + + private void ThenTheLoadBalancerIsReturned() + where T : ILoadBalancer + { + _loadBalancer.ShouldBeOfType(); + } + + private void ThenTheLoadBalancerTypeIs(string type) + { + _typeName.ShouldBe(type); + } + } +} diff --git a/test/Ocelot.UnitTests/LoadBalancer/RoundRobinCreatorTests.cs b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinCreatorTests.cs new file mode 100644 index 00000000..80d43568 --- /dev/null +++ b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinCreatorTests.cs @@ -0,0 +1,72 @@ +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.ServiceDiscovery.Providers; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class RoundRobinCreatorTests + { + private readonly RoundRobinCreator _creator; + private readonly Mock _serviceProvider; + private DownstreamReRoute _reRoute; + private ILoadBalancer _loadBalancer; + private string _typeName; + + public RoundRobinCreatorTests() + { + _creator = new RoundRobinCreator(); + _serviceProvider = new Mock(); + } + + [Fact] + public void should_return_expected_name() + { + var reRoute = new DownstreamReRouteBuilder() + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_instance_of_expected_load_balancer_type() + { + this.When(x => x.WhenIGetTheLoadBalancerTypeName()) + .Then(x => x.ThenTheLoadBalancerTypeIs("RoundRobin")) + .BDDfy(); + } + + private void GivenAReRoute(DownstreamReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGetTheLoadBalancer() + { + _loadBalancer = _creator.Create(_reRoute, _serviceProvider.Object); + } + + private void WhenIGetTheLoadBalancerTypeName() + { + _typeName = _creator.Type; + } + + private void ThenTheLoadBalancerIsReturned() + where T : ILoadBalancer + { + _loadBalancer.ShouldBeOfType(); + } + + private void ThenTheLoadBalancerTypeIs(string type) + { + _typeName.ShouldBe(type); + } + } +} From 80ab5f6a912f2225e2a3caf24d679560a52247ae Mon Sep 17 00:00:00 2001 From: David Lievrouw Date: Wed, 10 Jul 2019 19:36:01 +0200 Subject: [PATCH 4/9] Extend OcelotBuilder with overloads of the AddCustomLoadBalancer registration helper method --- .../DependencyInjection/IOcelotBuilder.cs | 20 +++++++++ .../DependencyInjection/OcelotBuilder.cs | 43 +++++++++++++++++++ .../DelegateInvokingLoadBalancerCreator.cs | 25 +++++++++++ .../DependencyInjection/OcelotBuilderTests.cs | 25 ++++++----- 4 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 src/Ocelot/LoadBalancer/LoadBalancers/DelegateInvokingLoadBalancerCreator.cs diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 13ec5a6a..33b3424e 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -3,6 +3,9 @@ using Microsoft.Extensions.DependencyInjection; using Ocelot.Middleware.Multiplexer; using System; using System.Net.Http; +using Ocelot.Configuration; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.ServiceDiscovery.Providers; namespace Ocelot.DependencyInjection { @@ -25,6 +28,23 @@ namespace Ocelot.DependencyInjection IOcelotBuilder AddTransientDefinedAggregator() where T : class, IDefinedAggregator; + IOcelotBuilder AddCustomLoadBalancer() + where T : ILoadBalancer, new(); + + IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) + where T : ILoadBalancer; + + IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) + where T : ILoadBalancer; + + IOcelotBuilder AddCustomLoadBalancer( + Func loadBalancerFactoryFunc) + where T : ILoadBalancer; + + IOcelotBuilder AddCustomLoadBalancer( + Func loadBalancerFactoryFunc) + where T : ILoadBalancer; + IOcelotBuilder AddConfigPlaceholders(); } } diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index a9290ad7..9e348183 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -1,3 +1,5 @@ +using Ocelot.ServiceDiscovery.Providers; + using Ocelot.Configuration.ChangeTracking; namespace Ocelot.DependencyInjection @@ -173,6 +175,47 @@ namespace Ocelot.DependencyInjection return this; } + public IOcelotBuilder AddCustomLoadBalancer() + where T : ILoadBalancer, new() + { + AddCustomLoadBalancer((provider, reRoute, serviceDiscoveryProvider) => new T()); + return this; + } + + public IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) + where T : ILoadBalancer + { + AddCustomLoadBalancer((provider, reRoute, serviceDiscoveryProvider) => + loadBalancerFactoryFunc()); + return this; + } + + public IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) + where T : ILoadBalancer + { + AddCustomLoadBalancer((provider, reRoute, serviceDiscoveryProvider) => + loadBalancerFactoryFunc(provider)); + return this; + } + + public IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) + where T : ILoadBalancer + { + AddCustomLoadBalancer((provider, reRoute, serviceDiscoveryProvider) => + loadBalancerFactoryFunc(reRoute, serviceDiscoveryProvider)); + return this; + } + + public IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) + where T : ILoadBalancer + { + Services.AddSingleton(provider => + new DelegateInvokingLoadBalancerCreator( + (reRoute, serviceDiscoveryProvider) => + loadBalancerFactoryFunc(provider, reRoute, serviceDiscoveryProvider))); + return this; + } + private void AddSecurity() { Services.TryAddSingleton(); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/DelegateInvokingLoadBalancerCreator.cs b/src/Ocelot/LoadBalancer/LoadBalancers/DelegateInvokingLoadBalancerCreator.cs new file mode 100644 index 00000000..535550dc --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/DelegateInvokingLoadBalancerCreator.cs @@ -0,0 +1,25 @@ +namespace Ocelot.LoadBalancer.LoadBalancers +{ + using System; + using Ocelot.Configuration; + using Ocelot.ServiceDiscovery.Providers; + + public class DelegateInvokingLoadBalancerCreator : ILoadBalancerCreator + where T : ILoadBalancer + { + private readonly Func _creatorFunc; + + public DelegateInvokingLoadBalancerCreator( + Func creatorFunc) + { + _creatorFunc = creatorFunc; + } + + public ILoadBalancer Create(DownstreamReRoute reRoute, IServiceDiscoveryProvider serviceProvider) + { + return _creatorFunc(reRoute, serviceProvider); + } + + public string Type => typeof(T).Name; + } +} diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 499c2a75..f85bbb11 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -1,6 +1,8 @@ -using Ocelot.Configuration; +using System.Threading.Tasks; using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.ServiceDiscovery.Providers; +using Ocelot.Middleware; +using Ocelot.Responses; +using Ocelot.Values; namespace Ocelot.UnitTests.DependencyInjection { @@ -166,7 +168,7 @@ namespace Ocelot.UnitTests.DependencyInjection public void should_add_custom_load_balancer_creators() { this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddSingletonLoadBalancerCreator()) + .When(x => AddCustomLoadBalancer()) .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) .BDDfy(); } @@ -177,10 +179,10 @@ namespace Ocelot.UnitTests.DependencyInjection _ocelotBuilder.AddSingletonDefinedAggregator(); } - private void AddSingletonLoadBalancerCreator() - where T : class, ILoadBalancerCreator + private void AddCustomLoadBalancer() + where T : ILoadBalancer, new() { - _ocelotBuilder.Services.AddSingleton(); + _ocelotBuilder.AddCustomLoadBalancer(); } private void AddTransientDefinedAggregator() @@ -266,7 +268,7 @@ namespace Ocelot.UnitTests.DependencyInjection creators.Count(c => c.GetType() == typeof(RoundRobinCreator)).ShouldBe(1); creators.Count(c => c.GetType() == typeof(CookieStickySessionsCreator)).ShouldBe(1); creators.Count(c => c.GetType() == typeof(LeastConnectionCreator)).ShouldBe(1); - creators.Count(c => c.GetType() == typeof(FakeCustomLoadBalancerCreator)).ShouldBe(1); + creators.Count(c => c.GetType() == typeof(DelegateInvokingLoadBalancerCreator)).ShouldBe(1); } private void ThenTheAggregatorsAreTransient() @@ -354,14 +356,17 @@ namespace Ocelot.UnitTests.DependencyInjection _ex.ShouldBeNull(); } - private class FakeCustomLoadBalancerCreator : ILoadBalancerCreator + private class FakeCustomLoadBalancer : ILoadBalancer { - public ILoadBalancer Create(DownstreamReRoute reRoute, IServiceDiscoveryProvider serviceProvider) + public Task> Lease(DownstreamContext context) { throw new NotImplementedException(); } - public string Type { get; } + public void Release(ServiceHostAndPort hostAndPort) + { + throw new NotImplementedException(); + } } } } From fd35ad0514bc9875cde91245066b4e2400767553 Mon Sep 17 00:00:00 2001 From: David Lievrouw Date: Wed, 10 Jul 2019 20:59:04 +0200 Subject: [PATCH 5/9] Add ability to register custom load balancers --- .../DependencyInjection/OcelotBuilderTests.cs | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index f85bbb11..577fd059 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -154,6 +154,42 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } + [Fact] + public void should_add_custom_load_balancer_creators_by_default_ctor() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => _ocelotBuilder.AddCustomLoadBalancer()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) + .BDDfy(); + } + + [Fact] + public void should_add_custom_load_balancer_creators_by_factory_method() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => _ocelotBuilder.AddCustomLoadBalancer(() => new FakeCustomLoadBalancer())) + .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) + .BDDfy(); + } + + [Fact] + public void should_add_custom_load_balancer_creators_by_di_factory_method() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => _ocelotBuilder.AddCustomLoadBalancer(provider => new FakeCustomLoadBalancer())) + .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) + .BDDfy(); + } + + [Fact] + public void should_add_custom_load_balancer_creators_by_factory_method_with_arguments() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => _ocelotBuilder.AddCustomLoadBalancer((reroute, discoveryProvider) => new FakeCustomLoadBalancer())) + .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) + .BDDfy(); + } + [Fact] public void should_replace_iplaceholder() { @@ -168,7 +204,7 @@ namespace Ocelot.UnitTests.DependencyInjection public void should_add_custom_load_balancer_creators() { this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddCustomLoadBalancer()) + .When(x => _ocelotBuilder.AddCustomLoadBalancer((provider, reroute, discoveryProvider) => new FakeCustomLoadBalancer())) .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) .BDDfy(); } @@ -179,12 +215,6 @@ namespace Ocelot.UnitTests.DependencyInjection _ocelotBuilder.AddSingletonDefinedAggregator(); } - private void AddCustomLoadBalancer() - where T : ILoadBalancer, new() - { - _ocelotBuilder.AddCustomLoadBalancer(); - } - private void AddTransientDefinedAggregator() where T : class, IDefinedAggregator { From 22f304cecfc25950a876afa1a96168eda7db2f42 Mon Sep 17 00:00:00 2001 From: David Lievrouw Date: Wed, 10 Jul 2019 20:59:38 +0200 Subject: [PATCH 6/9] Cover LoadBalancerFactory edge-case by a unit test. --- .../LoadBalancer/LoadBalancerFactoryTests.cs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index 70b9a837..bd9ac6c0 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -8,6 +8,7 @@ using Ocelot.ServiceDiscovery.Providers; using Shouldly; using System.Collections.Generic; using System.Threading.Tasks; +using Ocelot.Infrastructure.RequestData; using Ocelot.Middleware; using Ocelot.Values; using TestStack.BDDfy; @@ -39,7 +40,7 @@ namespace Ocelot.UnitTests.LoadBalancer } [Fact] - public void should_return_no_load_balancer() + public void should_return_no_load_balancer_by_default() { var reRoute = new DownstreamReRouteBuilder() .WithUpstreamHttpMethod(new List { "Get" }) @@ -85,6 +86,22 @@ namespace Ocelot.UnitTests.LoadBalancer .BDDfy(); } + [Fact] + public void should_return_error_response_when_call_to_service_provider_fails() + { + var reRoute = new DownstreamReRouteBuilder() + .WithLoadBalancerOptions(new LoadBalancerOptions("FakeLoadBalancerOne", "", 0)) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build())) + .And(x => x.GivenTheServiceProviderFactoryFails()) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenAnErrorResponseIsReturned()) + .BDDfy(); + } + private void GivenAServiceProviderConfig(ServiceProviderConfiguration serviceProviderConfig) { _serviceProviderConfig = serviceProviderConfig; @@ -97,6 +114,13 @@ namespace Ocelot.UnitTests.LoadBalancer .Returns(new OkResponse(_serviceProvider.Object)); } + private void GivenTheServiceProviderFactoryFails() + { + _serviceProviderFactory + .Setup(x => x.Get(It.IsAny(), It.IsAny())) + .Returns(new ErrorResponse(new CannotFindDataError("For tests"))); + } + private void ThenTheServiceProviderIsCalledCorrectly() { _serviceProviderFactory @@ -118,6 +142,11 @@ namespace Ocelot.UnitTests.LoadBalancer _result.Data.ShouldBeOfType(); } + private void ThenAnErrorResponseIsReturned() + { + _result.IsError.ShouldBeTrue(); + } + private class FakeLoadBalancerCreator : ILoadBalancerCreator where T : ILoadBalancer, new() { From f9ce987cc51a4142a6a1f7065d38de66f88f07db Mon Sep 17 00:00:00 2001 From: David Lievrouw Date: Wed, 10 Jul 2019 21:11:34 +0200 Subject: [PATCH 7/9] Cover DelegateInvokingLoadBalancerCreator by unit tests. --- ...elegateInvokingLoadBalancerCreatorTests.cs | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 test/Ocelot.UnitTests/LoadBalancer/DelegateInvokingLoadBalancerCreatorTests.cs diff --git a/test/Ocelot.UnitTests/LoadBalancer/DelegateInvokingLoadBalancerCreatorTests.cs b/test/Ocelot.UnitTests/LoadBalancer/DelegateInvokingLoadBalancerCreatorTests.cs new file mode 100644 index 00000000..2be4ee62 --- /dev/null +++ b/test/Ocelot.UnitTests/LoadBalancer/DelegateInvokingLoadBalancerCreatorTests.cs @@ -0,0 +1,102 @@ +using System; +using System.Threading.Tasks; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Middleware; +using Ocelot.Responses; +using Ocelot.ServiceDiscovery.Providers; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class DelegateInvokingLoadBalancerCreatorTests + { + private readonly DelegateInvokingLoadBalancerCreator _creator; + private readonly Func _creatorFunc; + private readonly Mock _serviceProvider; + private DownstreamReRoute _reRoute; + private ILoadBalancer _loadBalancer; + private string _typeName; + + public DelegateInvokingLoadBalancerCreatorTests() + { + _creatorFunc = (reRoute, serviceDiscoveryProvider) => + new FakeLoadBalancer(reRoute, serviceDiscoveryProvider); + _creator = new DelegateInvokingLoadBalancerCreator(_creatorFunc); + _serviceProvider = new Mock(); + } + + [Fact] + public void should_return_expected_name() + { + this.When(x => x.WhenIGetTheLoadBalancerTypeName()) + .Then(x => x.ThenTheLoadBalancerTypeIs("FakeLoadBalancer")) + .BDDfy(); + } + + [Fact] + public void should_return_result_of_specified_creator_func() + { + var reRoute = new DownstreamReRouteBuilder() + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + private void GivenAReRoute(DownstreamReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGetTheLoadBalancer() + { + _loadBalancer = _creator.Create(_reRoute, _serviceProvider.Object); + } + + private void WhenIGetTheLoadBalancerTypeName() + { + _typeName = _creator.Type; + } + + private void ThenTheLoadBalancerIsReturned() + where T : ILoadBalancer + { + _loadBalancer.ShouldBeOfType(); + } + + private void ThenTheLoadBalancerTypeIs(string type) + { + _typeName.ShouldBe(type); + } + + private class FakeLoadBalancer : ILoadBalancer + { + public FakeLoadBalancer(DownstreamReRoute reRoute, IServiceDiscoveryProvider serviceDiscoveryProvider) + { + ReRoute = reRoute; + ServiceDiscoveryProvider = serviceDiscoveryProvider; + } + + public DownstreamReRoute ReRoute { get; } + public IServiceDiscoveryProvider ServiceDiscoveryProvider { get; } + + public Task> Lease(DownstreamContext context) + { + throw new NotImplementedException(); + } + + public void Release(ServiceHostAndPort hostAndPort) + { + throw new NotImplementedException(); + } + } + } +} From 2606d919135ddb731fdc116c66f535a877ddfb19 Mon Sep 17 00:00:00 2001 From: David Lievrouw Date: Wed, 10 Jul 2019 21:13:21 +0200 Subject: [PATCH 8/9] Fix unit test naming issue. --- .../LoadBalancer/CookieStickySessionsCreatorTests.cs | 4 ++-- .../LoadBalancer/LeastConnectionCreatorTests.cs | 4 ++-- .../LoadBalancer/NoLoadBalancerCreatorTests.cs | 4 ++-- test/Ocelot.UnitTests/LoadBalancer/RoundRobinCreatorTests.cs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsCreatorTests.cs b/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsCreatorTests.cs index 20f65da4..bf004010 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsCreatorTests.cs @@ -24,7 +24,7 @@ namespace Ocelot.UnitTests.LoadBalancer } [Fact] - public void should_return_expected_name() + public void should_return_instance_of_expected_load_balancer_type() { var reRoute = new DownstreamReRouteBuilder() .WithLoadBalancerOptions(new LoadBalancerOptions("myType", "myKey", 1000)) @@ -37,7 +37,7 @@ namespace Ocelot.UnitTests.LoadBalancer } [Fact] - public void should_return_instance_of_expected_load_balancer_type() + public void should_return_expected_name() { this.When(x => x.WhenIGetTheLoadBalancerTypeName()) .Then(x => x.ThenTheLoadBalancerTypeIs("CookieStickySessions")) diff --git a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionCreatorTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionCreatorTests.cs index 507958fd..37d678cd 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionCreatorTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionCreatorTests.cs @@ -24,7 +24,7 @@ namespace Ocelot.UnitTests.LoadBalancer } [Fact] - public void should_return_expected_name() + public void should_return_instance_of_expected_load_balancer_type() { var reRoute = new DownstreamReRouteBuilder() .WithServiceName("myService") @@ -37,7 +37,7 @@ namespace Ocelot.UnitTests.LoadBalancer } [Fact] - public void should_return_instance_of_expected_load_balancer_type() + public void should_return_expected_name() { this.When(x => x.WhenIGetTheLoadBalancerTypeName()) .Then(x => x.ThenTheLoadBalancerTypeIs("LeastConnection")) diff --git a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerCreatorTests.cs b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerCreatorTests.cs index 6b61c5a9..1cdcb3ab 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerCreatorTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerCreatorTests.cs @@ -24,7 +24,7 @@ namespace Ocelot.UnitTests.LoadBalancer } [Fact] - public void should_return_expected_name() + public void should_return_instance_of_expected_load_balancer_type() { var reRoute = new DownstreamReRouteBuilder() .Build(); @@ -36,7 +36,7 @@ namespace Ocelot.UnitTests.LoadBalancer } [Fact] - public void should_return_instance_of_expected_load_balancer_type() + public void should_return_expected_name() { this.When(x => x.WhenIGetTheLoadBalancerTypeName()) .Then(x => x.ThenTheLoadBalancerTypeIs("NoLoadBalancer")) diff --git a/test/Ocelot.UnitTests/LoadBalancer/RoundRobinCreatorTests.cs b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinCreatorTests.cs index 80d43568..83f7cb6f 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/RoundRobinCreatorTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinCreatorTests.cs @@ -24,7 +24,7 @@ namespace Ocelot.UnitTests.LoadBalancer } [Fact] - public void should_return_expected_name() + public void should_return_instance_of_expected_load_balancer_type() { var reRoute = new DownstreamReRouteBuilder() .Build(); @@ -36,7 +36,7 @@ namespace Ocelot.UnitTests.LoadBalancer } [Fact] - public void should_return_instance_of_expected_load_balancer_type() + public void should_return_expected_name() { this.When(x => x.WhenIGetTheLoadBalancerTypeName()) .Then(x => x.ThenTheLoadBalancerTypeIs("RoundRobin")) From 1223c1985f9125dc6c38e56ea0def33d34af5896 Mon Sep 17 00:00:00 2001 From: David Lievrouw Date: Thu, 11 Jul 2019 17:10:19 +0200 Subject: [PATCH 9/9] Add comment indicating that the FakeCustomLoadBalancer implementation is not relevant for OcelotBuilder tests. --- test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 577fd059..1a3ac2cb 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -390,11 +390,13 @@ namespace Ocelot.UnitTests.DependencyInjection { public Task> Lease(DownstreamContext context) { + // Not relevant for these tests throw new NotImplementedException(); } public void Release(ServiceHostAndPort hostAndPort) { + // Not relevant for these tests throw new NotImplementedException(); } }