Merge branch 'develop' into CircuitBreakerPattern

This commit is contained in:
geffzhang
2017-02-07 18:41:11 +08:00
committed by GitHub
96 changed files with 3067 additions and 232 deletions

BIN
test/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,220 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.AcceptanceTests
{
public class ServiceDiscoveryTests : IDisposable
{
private IWebHost _builderOne;
private IWebHost _builderTwo;
private IWebHost _fakeConsulBuilder;
private readonly Steps _steps;
private readonly List<ServiceEntry> _serviceEntries;
private int _counterOne;
private int _counterTwo;
private static readonly object _syncLock = new object();
public ServiceDiscoveryTests()
{
_steps = new Steps();
_serviceEntries = new List<ServiceEntry>();
}
[Fact]
public void should_use_service_discovery_and_load_balance_request()
{
var serviceName = "product";
var downstreamServiceOneUrl = "http://localhost:50879";
var downstreamServiceTwoUrl = "http://localhost:50880";
var fakeConsulServiceDiscoveryUrl = "http://localhost:8500";
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = "localhost",
Port = 50879,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
var serviceEntryTwo = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = "localhost",
Port = 50880,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
UpstreamTemplate = "/",
UpstreamHttpMethod = "Get",
ServiceName = serviceName,
LoadBalancer = "LeastConnection",
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Provider = "Consul",
Host = "localhost",
Port = 8500
}
}
};
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50))
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50))
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes())
.BDDfy();
}
private void ThenBothServicesCalledRealisticAmountOfTimes()
{
_counterOne.ShouldBe(25);
_counterTwo.ShouldBe(25);
}
private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected)
{
var total = _counterOne + _counterTwo;
total.ShouldBe(expected);
}
private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries)
{
foreach(var serviceEntry in serviceEntries)
{
_serviceEntries.Add(serviceEntry);
}
}
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url)
{
_fakeConsulBuilder = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
if(context.Request.Path.Value == "/v1/health/service/product")
{
await context.Response.WriteJsonAsync(_serviceEntries);
}
});
})
.Build();
_fakeConsulBuilder.Start();
}
private void GivenProductServiceOneIsRunning(string url, int statusCode)
{
_builderOne = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
try
{
var response = string.Empty;
lock (_syncLock)
{
_counterOne++;
response = _counterOne.ToString();
}
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(response);
}
catch (System.Exception exception)
{
await context.Response.WriteAsync(exception.StackTrace);
}
});
})
.Build();
_builderOne.Start();
}
private void GivenProductServiceTwoIsRunning(string url, int statusCode)
{
_builderTwo = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
try
{
var response = string.Empty;
lock (_syncLock)
{
_counterTwo++;
response = _counterTwo.ToString();
}
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(response);
}
catch (System.Exception exception)
{
await context.Response.WriteAsync(exception.StackTrace);
}
});
})
.Build();
_builderTwo.Start();
}
public void Dispose()
{
_builderOne?.Dispose();
_builderTwo?.Dispose();
_steps.Dispose();
}
}
}

View File

@ -5,6 +5,8 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using CacheManager.Core;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
@ -29,6 +31,12 @@ namespace Ocelot.AcceptanceTests
private BearerToken _token;
public HttpClient OcelotClient => _ocelotClient;
public string RequestIdKey = "OcRequestId";
private Random _random;
public Steps()
{
_random = new Random();
}
public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
{
@ -153,6 +161,28 @@ namespace Ocelot.AcceptanceTests
_response = _ocelotClient.GetAsync(url).Result;
}
public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times)
{
var tasks = new Task[times];
for (int i = 0; i < times; i++)
{
var urlCopy = url;
tasks[i] = GetForServiceDiscoveryTest(urlCopy);
Thread.Sleep(_random.Next(40,60));
}
Task.WaitAll(tasks);
}
private async Task GetForServiceDiscoveryTest(string url)
{
var response = await _ocelotClient.GetAsync(url);
var content = await response.Content.ReadAsStringAsync();
int count = int.Parse(content);
count.ShouldBeGreaterThan(0);
}
public void WhenIGetUrlOnTheApiGateway(string url, string requestId)
{
_ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId);

View File

@ -4,14 +4,12 @@
public static class TestConfiguration
{
public static double Version => 1.4;
public static double Version => 1.1;
public static string ConfigurationPath => GetConfigurationPath();
public static string GetConfigurationPath()
{
var osArchitecture = RuntimeInformation.OSArchitecture.ToString();
var oSDescription = string.Empty;
if(RuntimeInformation.OSDescription.ToLower().Contains("darwin"))
{

View File

@ -1 +1 @@
{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"ExceptionsAllowedBeforeBreaking":3,"DurationOfBreak":5,"TimeoutValue":5000}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Address":null}}}
{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"LoadBalancer":null, ,"ExceptionsAllowedBeforeBreaking":3,"DurationOfBreak":5,"TimeoutValue":5000}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}}

View File

@ -1,5 +1,5 @@
{
"version": "1.0.0-*",
"version": "0.0.0-dev",
"buildOptions": {
"copyToOutput": {
@ -22,25 +22,26 @@
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Microsoft.AspNetCore.Http": "1.1.0",
"Microsoft.DotNet.InternalAbstractions": "1.0.0",
"Ocelot": "1.0.0-*",
"Ocelot": "0.0.0-dev",
"xunit": "2.2.0-beta2-build3300",
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"Ocelot.ManualTest": "1.0.0-*",
"Ocelot.ManualTest": "0.0.0-dev",
"Microsoft.AspNetCore.TestHost": "1.1.0",
"IdentityServer4": "1.0.1",
"Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.NETCore.App": "1.1.0",
"Shouldly": "2.8.2",
"TestStack.BDDfy": "4.3.2"
"TestStack.BDDfy": "4.3.2",
"Consul": "0.7.2.1"
},
"runtimes": {
"win10-x64": {},
"osx.10.11-x64":{},
"osx.10.11-x64": {},
"win7-x64": {}
},
"frameworks": {
"netcoreapp1.4": {
"netcoreapp1.1": {
"imports": [
]
}

View File

@ -1,12 +1,12 @@
{
"version": "1.0.0-*",
"version": "0.0.0-dev",
"buildOptions": {
"emitEntryPoint": true
},
"dependencies": {
"Ocelot": "1.0.0-*",
"BenchmarkDotNet": "0.10.1"
"Ocelot": "0.0.0-dev",
"BenchmarkDotNet": "0.10.2"
},
"runtimes": {
"win10-x64": {},
@ -14,7 +14,7 @@
"win7-x64": {}
},
"frameworks": {
"netcoreapp1.4": {
"netcoreapp1.1": {
"imports": [
]
}

View File

@ -1,5 +1,5 @@
{
"version": "1.0.0-*",
"version": "0.0.0-dev",
"dependencies": {
"Microsoft.AspNetCore.Http": "1.1.0",
@ -10,7 +10,7 @@
"Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Ocelot": "1.0.0-*",
"Ocelot": "0.0.0-dev",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.NETCore.App": "1.1.0"
},
@ -24,7 +24,7 @@
"win7-x64": {}
},
"frameworks": {
"netcoreapp1.4": {
"netcoreapp1.1": {
"imports": [
]
}

View File

@ -8,6 +8,7 @@ using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Parser;
using Ocelot.Configuration.Validator;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Responses;
using Shouldly;
using TestStack.BDDfy;
@ -24,6 +25,9 @@ namespace Ocelot.UnitTests.Configuration
private readonly Mock<IClaimToThingConfigurationParser> _configParser;
private readonly Mock<ILogger<FileOcelotConfigurationCreator>> _logger;
private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator;
private readonly Mock<ILoadBalancerFactory> _loadBalancerFactory;
private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse;
private readonly Mock<ILoadBalancer> _loadBalancer;
public FileConfigurationCreatorTests()
{
@ -31,8 +35,37 @@ namespace Ocelot.UnitTests.Configuration
_configParser = new Mock<IClaimToThingConfigurationParser>();
_validator = new Mock<IConfigurationValidator>();
_fileConfig = new Mock<IOptions<FileConfiguration>>();
_loadBalancerFactory = new Mock<ILoadBalancerFactory>();
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
_loadBalancer = new Mock<ILoadBalancer>();
_ocelotConfigurationCreator = new FileOcelotConfigurationCreator(
_fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object);
_fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object,
_loadBalancerFactory.Object, _loadBalancerHouse.Object);
}
[Fact]
public void should_create_load_balancer()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamHost = "127.0.0.1",
UpstreamTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
}
},
}))
.And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheLoadBalancerFactoryReturns())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly())
.And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly())
.BDDfy();
}
[Fact]
@ -66,6 +99,7 @@ namespace Ocelot.UnitTests.Configuration
.BDDfy();
}
[Fact]
public void should_use_downstream_scheme()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
@ -117,7 +151,7 @@ namespace Ocelot.UnitTests.Configuration
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Provider = "consul",
Address = "127.0.0.1"
Host = "127.0.0.1"
}
}
}))
@ -376,6 +410,7 @@ namespace Ocelot.UnitTests.Configuration
}))
.And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheConfigHeaderExtractorReturns(new ClaimToThing("CustomerId", "CustomerId", "", 0)))
.And(x => x.GivenTheLoadBalancerFactoryReturns())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(expected))
.And(x => x.ThenTheAuthenticationOptionsAre(expected))
@ -430,6 +465,7 @@ namespace Ocelot.UnitTests.Configuration
}
}))
.And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheLoadBalancerFactoryReturns())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(expected))
.And(x => x.ThenTheAuthenticationOptionsAre(expected))
@ -543,7 +579,7 @@ namespace Ocelot.UnitTests.Configuration
private void WhenICreateTheConfig()
{
_config = _ocelotConfigurationCreator.Create();
_config = _ocelotConfigurationCreator.Create().Result;
}
private void ThenTheReRoutesAre(List<ReRoute> expectedReRoutes)
@ -576,5 +612,24 @@ namespace Ocelot.UnitTests.Configuration
}
}
private void GivenTheLoadBalancerFactoryReturns()
{
_loadBalancerFactory
.Setup(x => x.Get(It.IsAny<ReRoute>()))
.ReturnsAsync(_loadBalancer.Object);
}
private void TheLoadBalancerFactoryIsCalledCorrectly()
{
_loadBalancerFactory
.Verify(x => x.Get(It.IsAny<ReRoute>()), Times.Once);
}
private void ThenTheLoadBalancerHouseIsCalledCorrectly()
{
_loadBalancerHouse
.Verify(x => x.Add(It.IsAny<string>(), _loadBalancer.Object), Times.Once);
}
}
}

View File

@ -81,7 +81,7 @@ namespace Ocelot.UnitTests.Configuration
{
_creator
.Setup(x => x.Create())
.Returns(config);
.ReturnsAsync(config);
}
private void GivenTheRepoReturns(Response<IOcelotConfiguration> config)
@ -93,7 +93,7 @@ namespace Ocelot.UnitTests.Configuration
private void WhenIGetTheConfig()
{
_result = _ocelotConfigurationProvider.Get();
_result = _ocelotConfigurationProvider.Get().Result;
}
private void TheFollowingIsReturned(Response<IOcelotConfiguration> expected)

View File

@ -84,7 +84,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
_downstreamRouteFinder
.Setup(x => x.FindDownstreamRoute(It.IsAny<string>(), It.IsAny<string>()))
.Returns(_downstreamRoute);
.ReturnsAsync(_downstreamRoute);
}
public void Dispose()

View File

@ -159,7 +159,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
_reRoutesConfig = reRoutesConfig;
_mockConfig
.Setup(x => x.Get())
.Returns(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(_reRoutesConfig)));
.ReturnsAsync(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(_reRoutesConfig)));
}
private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath)
@ -169,7 +169,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
private void WhenICallTheFinder()
{
_result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod);
_result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod).Result;
}
private void ThenTheFollowingIsReturned(DownstreamRoute expected)

View File

@ -36,6 +36,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
private HttpResponseMessage _result;
private OkResponse<DownstreamPath> _downstreamPath;
private OkResponse<DownstreamUrl> _downstreamUrl;
private HostAndPort _hostAndPort;
public DownstreamUrlCreatorMiddlewareTests()
{
@ -69,14 +70,25 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
[Fact]
public void should_call_dependencies_correctly()
{
var hostAndPort = new HostAndPort("127.0.0.1", 80);
this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build())))
.And(x => x.GivenTheHostAndPortIs(hostAndPort))
.And(x => x.TheUrlReplacerReturns("/api/products/1"))
.And(x => x.TheUrlBuilderReturns("http://www.bbc.co.uk/api/products/1"))
.And(x => x.TheUrlBuilderReturns("http://127.0.0.1:80/api/products/1"))
.When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
.BDDfy();
}
private void GivenTheHostAndPortIs(HostAndPort hostAndPort)
{
_hostAndPort = hostAndPort;
_scopedRepository
.Setup(x => x.Get<HostAndPort>("HostAndPort"))
.Returns(new OkResponse<HostAndPort>(_hostAndPort));
}
private void TheUrlBuilderReturns(string dsUrl)
{
_downstreamUrl = new OkResponse<DownstreamUrl>(new DownstreamUrl(dsUrl));

View File

@ -0,0 +1,238 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Responses;
using Ocelot.Values;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.LoadBalancer
{
public class LeastConnectionTests
{
private HostAndPort _hostAndPort;
private Response<HostAndPort> _result;
private LeastConnectionLoadBalancer _leastConnection;
private List<Service> _services;
private Random _random;
public LeastConnectionTests()
{
_random = new Random();
}
[Fact]
public void should_be_able_to_lease_and_release_concurrently()
{
var serviceName = "products";
var availableServices = new List<Service>
{
new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
};
_services = availableServices;
_leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName);
var tasks = new Task[100];
for(var i = 0; i < tasks.Length; i++)
{
tasks[i] = LeaseDelayAndRelease();
}
Task.WaitAll(tasks);
}
private async Task LeaseDelayAndRelease()
{
var hostAndPort = await _leastConnection.Lease();
await Task.Delay(_random.Next(1, 100));
_leastConnection.Release(hostAndPort.Data);
}
[Fact]
public void should_get_next_url()
{
var serviceName = "products";
var hostAndPort = new HostAndPort("localhost", 80);
var availableServices = new List<Service>
{
new Service(serviceName, hostAndPort, string.Empty, string.Empty, new string[0])
};
this.Given(x => x.GivenAHostAndPort(hostAndPort))
.And(x => x.GivenTheLoadBalancerStarts(availableServices, serviceName))
.When(x => x.WhenIGetTheNextHostAndPort())
.Then(x => x.ThenTheNextHostAndPortIsReturned())
.BDDfy();
}
[Fact]
public void should_serve_from_service_with_least_connections()
{
var serviceName = "products";
var availableServices = new List<Service>
{
new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
new Service(serviceName, new HostAndPort("127.0.0.3", 80), string.Empty, string.Empty, new string[0])
};
_services = availableServices;
_leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName);
var response = _leastConnection.Lease().Result;
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
response = _leastConnection.Lease().Result;
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
response = _leastConnection.Lease().Result;
response.Data.DownstreamHost.ShouldBe(availableServices[2].HostAndPort.DownstreamHost);
}
[Fact]
public void should_build_connections_per_service()
{
var serviceName = "products";
var availableServices = new List<Service>
{
new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
};
_services = availableServices;
_leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName);
var response = _leastConnection.Lease().Result;
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
response = _leastConnection.Lease().Result;
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
response = _leastConnection.Lease().Result;
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
response = _leastConnection.Lease().Result;
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
}
[Fact]
public void should_release_connection()
{
var serviceName = "products";
var availableServices = new List<Service>
{
new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
};
_services = availableServices;
_leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName);
var response = _leastConnection.Lease().Result;
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
response = _leastConnection.Lease().Result;
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
response = _leastConnection.Lease().Result;
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
response = _leastConnection.Lease().Result;
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
//release this so 2 should have 1 connection and we should get 2 back as our next host and port
_leastConnection.Release(availableServices[1].HostAndPort);
response = _leastConnection.Lease().Result;
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
}
[Fact]
public void should_return_error_if_services_are_null()
{
var serviceName = "products";
var hostAndPort = new HostAndPort("localhost", 80);
this.Given(x => x.GivenAHostAndPort(hostAndPort))
.And(x => x.GivenTheLoadBalancerStarts(null, serviceName))
.When(x => x.WhenIGetTheNextHostAndPort())
.Then(x => x.ThenServiceAreNullErrorIsReturned())
.BDDfy();
}
[Fact]
public void should_return_error_if_services_are_empty()
{
var serviceName = "products";
var hostAndPort = new HostAndPort("localhost", 80);
this.Given(x => x.GivenAHostAndPort(hostAndPort))
.And(x => x.GivenTheLoadBalancerStarts(new List<Service>(), serviceName))
.When(x => x.WhenIGetTheNextHostAndPort())
.Then(x => x.ThenServiceAreEmptyErrorIsReturned())
.BDDfy();
}
private void ThenServiceAreNullErrorIsReturned()
{
_result.IsError.ShouldBeTrue();
_result.Errors[0].ShouldBeOfType<ServicesAreNullError>();
}
private void ThenServiceAreEmptyErrorIsReturned()
{
_result.IsError.ShouldBeTrue();
_result.Errors[0].ShouldBeOfType<ServicesAreEmptyError>();
}
private void GivenTheLoadBalancerStarts(List<Service> services, string serviceName)
{
_services = services;
_leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName);
}
private void WhenTheLoadBalancerStarts(List<Service> services, string serviceName)
{
GivenTheLoadBalancerStarts(services, serviceName);
}
private void GivenAHostAndPort(HostAndPort hostAndPort)
{
_hostAndPort = hostAndPort;
}
private void WhenIGetTheNextHostAndPort()
{
_result = _leastConnection.Lease().Result;
}
private void ThenTheNextHostAndPortIsReturned()
{
_result.Data.DownstreamHost.ShouldBe(_hostAndPort.DownstreamHost);
_result.Data.DownstreamPort.ShouldBe(_hostAndPort.DownstreamPort);
}
}
}

View File

@ -0,0 +1,110 @@
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.ServiceDiscovery;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.LoadBalancer
{
public class LoadBalancerFactoryTests
{
private ReRoute _reRoute;
private LoadBalancerFactory _factory;
private ILoadBalancer _result;
private Mock<IServiceDiscoveryProviderFactory> _serviceProviderFactory;
private Mock<IServiceDiscoveryProvider> _serviceProvider;
public LoadBalancerFactoryTests()
{
_serviceProviderFactory = new Mock<IServiceDiscoveryProviderFactory>();
_serviceProvider = new Mock<IServiceDiscoveryProvider>();
_factory = new LoadBalancerFactory(_serviceProviderFactory.Object);
}
private void GivenTheServiceProviderFactoryReturns()
{
_serviceProviderFactory
.Setup(x => x.Get(It.IsAny<ServiceProviderConfiguraion>()))
.Returns(_serviceProvider.Object);
}
[Fact]
public void should_return_no_load_balancer()
{
var reRoute = new ReRouteBuilder()
.Build();
this.Given(x => x.GivenAReRoute(reRoute))
.And(x => x.GivenTheServiceProviderFactoryReturns())
.When(x => x.WhenIGetTheLoadBalancer())
.Then(x => x.ThenTheLoadBalancerIsReturned<NoLoadBalancer>())
.BDDfy();
}
[Fact]
public void should_return_round_robin_load_balancer()
{
var reRoute = new ReRouteBuilder()
.WithLoadBalancer("RoundRobin")
.Build();
this.Given(x => x.GivenAReRoute(reRoute))
.And(x => x.GivenTheServiceProviderFactoryReturns())
.When(x => x.WhenIGetTheLoadBalancer())
.Then(x => x.ThenTheLoadBalancerIsReturned<RoundRobinLoadBalancer>())
.BDDfy();
}
[Fact]
public void should_return_round_least_connection_balancer()
{
var reRoute = new ReRouteBuilder()
.WithLoadBalancer("LeastConnection")
.Build();
this.Given(x => x.GivenAReRoute(reRoute))
.And(x => x.GivenTheServiceProviderFactoryReturns())
.When(x => x.WhenIGetTheLoadBalancer())
.Then(x => x.ThenTheLoadBalancerIsReturned<LeastConnectionLoadBalancer>())
.BDDfy();
}
[Fact]
public void should_call_service_provider()
{
var reRoute = new ReRouteBuilder()
.WithLoadBalancer("RoundRobin")
.Build();
this.Given(x => x.GivenAReRoute(reRoute))
.And(x => x.GivenTheServiceProviderFactoryReturns())
.When(x => x.WhenIGetTheLoadBalancer())
.Then(x => x.ThenTheServiceProviderIsCalledCorrectly())
.BDDfy();
}
private void ThenTheServiceProviderIsCalledCorrectly()
{
_serviceProviderFactory
.Verify(x => x.Get(It.IsAny<ServiceProviderConfiguraion>()), Times.Once);
}
private void GivenAReRoute(ReRoute reRoute)
{
_reRoute = reRoute;
}
private void WhenIGetTheLoadBalancer()
{
_result = _factory.Get(_reRoute).Result;
}
private void ThenTheLoadBalancerIsReturned<T>()
{
_result.ShouldBeOfType<T>();
}
}
}

View File

@ -0,0 +1,136 @@
using System;
using System.Threading.Tasks;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Responses;
using Ocelot.Values;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.LoadBalancer
{
public class LoadBalancerHouseTests
{
private ILoadBalancer _loadBalancer;
private readonly LoadBalancerHouse _loadBalancerHouse;
private Response _addResult;
private Response<ILoadBalancer> _getResult;
private string _key;
public LoadBalancerHouseTests()
{
_loadBalancerHouse = new LoadBalancerHouse();
}
[Fact]
public void should_store_load_balancer()
{
var key = "test";
this.Given(x => x.GivenThereIsALoadBalancer(key, new FakeLoadBalancer()))
.When(x => x.WhenIAddTheLoadBalancer())
.Then(x => x.ThenItIsAdded())
.BDDfy();
}
[Fact]
public void should_get_load_balancer()
{
var key = "test";
this.Given(x => x.GivenThereIsALoadBalancer(key, new FakeLoadBalancer()))
.When(x => x.WhenWeGetTheLoadBalancer(key))
.Then(x => x.ThenItIsReturned())
.BDDfy();
}
[Fact]
public void should_store_load_balancers_by_key()
{
var key = "test";
var keyTwo = "testTwo";
this.Given(x => x.GivenThereIsALoadBalancer(key, new FakeLoadBalancer()))
.And(x => x.GivenThereIsALoadBalancer(keyTwo, new FakeRoundRobinLoadBalancer()))
.When(x => x.WhenWeGetTheLoadBalancer(key))
.Then(x => x.ThenTheLoadBalancerIs<FakeLoadBalancer>())
.When(x => x.WhenWeGetTheLoadBalancer(keyTwo))
.Then(x => x.ThenTheLoadBalancerIs<FakeRoundRobinLoadBalancer>())
.BDDfy();
}
[Fact]
public void should_return_error_if_no_load_balancer_with_key()
{
this.When(x => x.WhenWeGetTheLoadBalancer("test"))
.Then(x => x.ThenAnErrorIsReturned())
.BDDfy();
}
private void ThenAnErrorIsReturned()
{
_getResult.IsError.ShouldBeTrue();
_getResult.Errors[0].ShouldBeOfType<UnableToFindLoadBalancerError>();
}
private void ThenTheLoadBalancerIs<T>()
{
_getResult.Data.ShouldBeOfType<T>();
}
private void ThenItIsAdded()
{
_addResult.IsError.ShouldBe(false);
_addResult.ShouldBeOfType<OkResponse>();
}
private void WhenIAddTheLoadBalancer()
{
_addResult = _loadBalancerHouse.Add(_key, _loadBalancer);
}
private void GivenThereIsALoadBalancer(string key, ILoadBalancer loadBalancer)
{
_key = key;
_loadBalancer = loadBalancer;
WhenIAddTheLoadBalancer();
}
private void WhenWeGetTheLoadBalancer(string key)
{
_getResult = _loadBalancerHouse.Get(key);
}
private void ThenItIsReturned()
{
_getResult.Data.ShouldBe(_loadBalancer);
}
class FakeLoadBalancer : ILoadBalancer
{
public Task<Response<HostAndPort>> Lease()
{
throw new NotImplementedException();
}
public void Release(HostAndPort hostAndPort)
{
throw new NotImplementedException();
}
}
class FakeRoundRobinLoadBalancer : ILoadBalancer
{
public Task<Response<HostAndPort>> Lease()
{
throw new NotImplementedException();
}
public void Release(HostAndPort hostAndPort)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -0,0 +1,210 @@
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Ocelot.Configuration.Builder;
using Ocelot.DownstreamRouteFinder;
using Ocelot.Errors;
using Ocelot.Infrastructure.RequestData;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.LoadBalancer.Middleware;
using Ocelot.Logging;
using Ocelot.Responses;
using Ocelot.Values;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.LoadBalancer
{
public class LoadBalancerMiddlewareTests
{
private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse;
private readonly Mock<IRequestScopedDataRepository> _scopedRepository;
private readonly Mock<ILoadBalancer> _loadBalancer;
private readonly string _url;
private readonly TestServer _server;
private readonly HttpClient _client;
private HttpResponseMessage _result;
private HostAndPort _hostAndPort;
private OkResponse<Ocelot.Request.Request> _request;
private OkResponse<string> _downstreamUrl;
private OkResponse<DownstreamRoute> _downstreamRoute;
private ErrorResponse<ILoadBalancer> _getLoadBalancerHouseError;
private ErrorResponse<HostAndPort> _getHostAndPortError;
public LoadBalancerMiddlewareTests()
{
_url = "http://localhost:51879";
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
_scopedRepository = new Mock<IRequestScopedDataRepository>();
_loadBalancer = new Mock<ILoadBalancer>();
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
var builder = new WebHostBuilder()
.ConfigureServices(x =>
{
x.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
x.AddLogging();
x.AddSingleton(_loadBalancerHouse.Object);
x.AddSingleton(_scopedRepository.Object);
})
.UseUrls(_url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(_url)
.Configure(app =>
{
app.UseLoadBalancingMiddleware();
});
_server = new TestServer(builder);
_client = _server.CreateClient();
}
[Fact]
public void should_call_scoped_data_repository_correctly()
{
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder()
.Build());
this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
.And(x => x.GivenTheLoadBalancerHouseReturns())
.And(x => x.GivenTheLoadBalancerReturns())
.When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
.BDDfy();
}
[Fact]
public void should_set_pipeline_error_if_cannot_get_load_balancer()
{
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder()
.Build());
this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
.And(x => x.GivenTheLoadBalancerHouseReturnsAnError())
.When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline())
.BDDfy();
}
[Fact]
public void should_set_pipeline_error_if_cannot_get_least()
{
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder()
.Build());
this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
.And(x => x.GivenTheLoadBalancerHouseReturns())
.And(x => x.GivenTheLoadBalancerReturnsAnError())
.When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline())
.BDDfy();
}
private void GivenTheLoadBalancerReturnsAnError()
{
_getHostAndPortError = new ErrorResponse<HostAndPort>(new List<Error>() { new ServicesAreNullError($"services were null for bah") });
_loadBalancer
.Setup(x => x.Lease())
.ReturnsAsync(_getHostAndPortError);
}
private void GivenTheLoadBalancerReturns()
{
_hostAndPort = new HostAndPort("127.0.0.1", 80);
_loadBalancer
.Setup(x => x.Lease())
.ReturnsAsync(new OkResponse<HostAndPort>(_hostAndPort));
}
private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute)
{
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
_scopedRepository
.Setup(x => x.Get<DownstreamRoute>(It.IsAny<string>()))
.Returns(_downstreamRoute);
}
private void GivenTheLoadBalancerHouseReturns()
{
_loadBalancerHouse
.Setup(x => x.Get(It.IsAny<string>()))
.Returns(new OkResponse<ILoadBalancer>(_loadBalancer.Object));
}
private void GivenTheLoadBalancerHouseReturnsAnError()
{
_getLoadBalancerHouseError = new ErrorResponse<ILoadBalancer>(new List<Ocelot.Errors.Error>()
{
new UnableToFindLoadBalancerError($"unabe to find load balancer for bah")
});
_loadBalancerHouse
.Setup(x => x.Get(It.IsAny<string>()))
.Returns(_getLoadBalancerHouseError);
}
private void ThenTheScopedDataRepositoryIsCalledCorrectly()
{
_scopedRepository
.Verify(x => x.Add("HostAndPort", _hostAndPort), Times.Once());
}
private void ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline()
{
_scopedRepository
.Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once);
_scopedRepository
.Verify(x => x.Add("OcelotMiddlewareErrors", _getLoadBalancerHouseError.Errors), Times.Once);
}
private void ThenAnErrorSayingReleaseFailedIsSetOnThePipeline()
{
_scopedRepository
.Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once);
_scopedRepository
.Verify(x => x.Add("OcelotMiddlewareErrors", It.IsAny<List<Error>>()), Times.Once);
}
private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline()
{
_scopedRepository
.Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once);
_scopedRepository
.Verify(x => x.Add("OcelotMiddlewareErrors", _getHostAndPortError.Errors), Times.Once);
}
private void WhenICallTheMiddleware()
{
_result = _client.GetAsync(_url).Result;
}
private void GivenTheDownStreamUrlIs(string downstreamUrl)
{
_downstreamUrl = new OkResponse<string>(downstreamUrl);
_scopedRepository
.Setup(x => x.Get<string>(It.IsAny<string>()))
.Returns(_downstreamUrl);
}
public void Dispose()
{
_client.Dispose();
_server.Dispose();
}
}
}

View File

@ -0,0 +1,48 @@
using System.Collections.Generic;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Responses;
using Ocelot.Values;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.LoadBalancer
{
public class NoLoadBalancerTests
{
private List<Service> _services;
private NoLoadBalancer _loadBalancer;
private Response<HostAndPort> _result;
[Fact]
public void should_return_host_and_port()
{
var hostAndPort = new HostAndPort("127.0.0.1", 80);
var services = new List<Service>
{
new Service("product", hostAndPort, string.Empty, string.Empty, new string[0])
};
this.Given(x => x.GivenServices(services))
.When(x => x.WhenIGetTheNextHostAndPort())
.Then(x => x.ThenTheHostAndPortIs(hostAndPort))
.BDDfy();
}
private void GivenServices(List<Service> services)
{
_services = services;
}
private void WhenIGetTheNextHostAndPort()
{
_loadBalancer = new NoLoadBalancer(_services);
_result = _loadBalancer.Lease().Result;
}
private void ThenTheHostAndPortIs(HostAndPort expected)
{
_result.Data.ShouldBe(expected);
}
}
}

View File

@ -0,0 +1,68 @@
using System.Collections.Generic;
using System.Diagnostics;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Responses;
using Ocelot.Values;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.LoadBalancer
{
public class RoundRobinTests
{
private readonly RoundRobinLoadBalancer _roundRobin;
private readonly List<Service> _services;
private Response<HostAndPort> _hostAndPort;
public RoundRobinTests()
{
_services = new List<Service>
{
new Service("product", new HostAndPort("127.0.0.1", 5000), string.Empty, string.Empty, new string[0]),
new Service("product", new HostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0]),
new Service("product", new HostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0])
};
_roundRobin = new RoundRobinLoadBalancer(_services);
}
[Fact]
public void should_get_next_address()
{
this.Given(x => x.GivenIGetTheNextAddress())
.Then(x => x.ThenTheNextAddressIndexIs(0))
.Given(x => x.GivenIGetTheNextAddress())
.Then(x => x.ThenTheNextAddressIndexIs(1))
.Given(x => x.GivenIGetTheNextAddress())
.Then(x => x.ThenTheNextAddressIndexIs(2))
.BDDfy();
}
[Fact]
public void should_go_back_to_first_address_after_finished_last()
{
var stopWatch = Stopwatch.StartNew();
while (stopWatch.ElapsedMilliseconds < 1000)
{
var address = _roundRobin.Lease().Result;
address.Data.ShouldBe(_services[0].HostAndPort);
address = _roundRobin.Lease().Result;
address.Data.ShouldBe(_services[1].HostAndPort);
address = _roundRobin.Lease().Result;
address.Data.ShouldBe(_services[2].HostAndPort);
}
}
private void GivenIGetTheNextAddress()
{
_hostAndPort = _roundRobin.Lease().Result;
}
private void ThenTheNextAddressIndexIs(int index)
{
_hostAndPort.Data.ShouldBe(_services[index].HostAndPort);
}
}
}

View File

@ -62,19 +62,11 @@ namespace Ocelot.UnitTests.Responder
{
this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage()))
.And(x => x.GivenThereAreNoPipelineErrors())
.And(x => x.GivenTheResponderReturns())
.When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenThereAreNoErrors())
.BDDfy();
}
private void GivenTheResponderReturns()
{
_responder
.Setup(x => x.SetResponseOnHttpContext(It.IsAny<HttpContext>(), It.IsAny<HttpResponseMessage>()))
.ReturnsAsync(new OkResponse());
}
private void GivenThereAreNoPipelineErrors()
{
_scopedRepository

View File

@ -0,0 +1,53 @@
using System.Collections.Generic;
using Ocelot.ServiceDiscovery;
using Ocelot.Values;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.ServiceDiscovery
{
public class ConfigurationServiceProviderTests
{
private ConfigurationServiceProvider _serviceProvider;
private HostAndPort _hostAndPort;
private List<Service> _result;
private List<Service> _expected;
[Fact]
public void should_return_services()
{
var hostAndPort = new HostAndPort("127.0.0.1", 80);
var services = new List<Service>
{
new Service("product", hostAndPort, string.Empty, string.Empty, new string[0])
};
this.Given(x => x.GivenServices(services))
.When(x => x.WhenIGetTheService())
.Then(x => x.ThenTheFollowingIsReturned(services))
.BDDfy();
}
private void GivenServices(List<Service> services)
{
_expected = services;
}
private void WhenIGetTheService()
{
_serviceProvider = new ConfigurationServiceProvider(_expected);
_result = _serviceProvider.Get().Result;
}
private void ThenTheFollowingIsReturned(List<Service> services)
{
_result[0].HostAndPort.DownstreamHost.ShouldBe(services[0].HostAndPort.DownstreamHost);
_result[0].HostAndPort.DownstreamPort.ShouldBe(services[0].HostAndPort.DownstreamPort);
_result[0].Name.ShouldBe(services[0].Name);
}
}
}

View File

@ -0,0 +1,57 @@
using Ocelot.Configuration;
using Ocelot.ServiceDiscovery;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.ServiceDiscovery
{
public class ServiceProviderFactoryTests
{
private ServiceProviderConfiguraion _serviceConfig;
private IServiceDiscoveryProvider _result;
private readonly ServiceDiscoveryProviderFactory _factory;
public ServiceProviderFactoryTests()
{
_factory = new ServiceDiscoveryProviderFactory();
}
[Fact]
public void should_return_no_service_provider()
{
var serviceConfig = new ServiceProviderConfiguraion("product", "127.0.0.1", 80, false, "Does not matter", string.Empty, 0);
this.Given(x => x.GivenTheReRoute(serviceConfig))
.When(x => x.WhenIGetTheServiceProvider())
.Then(x => x.ThenTheServiceProviderIs<ConfigurationServiceProvider>())
.BDDfy();
}
[Fact]
public void should_return_consul_service_provider()
{
var serviceConfig = new ServiceProviderConfiguraion("product", string.Empty, 0, true, "Consul", string.Empty, 0);
this.Given(x => x.GivenTheReRoute(serviceConfig))
.When(x => x.WhenIGetTheServiceProvider())
.Then(x => x.ThenTheServiceProviderIs<ConsulServiceDiscoveryProvider>())
.BDDfy();
}
private void GivenTheReRoute(ServiceProviderConfiguraion serviceConfig)
{
_serviceConfig = serviceConfig;
}
private void WhenIGetTheServiceProvider()
{
_result = _factory.Get(_serviceConfig);
}
private void ThenTheServiceProviderIs<T>()
{
_result.ShouldBeOfType<T>();
}
}
}

View File

@ -0,0 +1,136 @@
using System.Collections.Generic;
using Ocelot.Values;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.ServiceDiscovery
{
public class ServiceRegistryTests
{
private Service _service;
private List<Service> _services;
private ServiceRegistry _serviceRegistry;
private ServiceRepository _serviceRepository;
public ServiceRegistryTests()
{
_serviceRepository = new ServiceRepository();
_serviceRegistry = new ServiceRegistry(_serviceRepository);
}
[Fact]
public void should_register_service()
{
this.Given(x => x.GivenAServiceToRegister("product", "localhost:5000", 80))
.When(x => x.WhenIRegisterTheService())
.Then(x => x.ThenTheServiceIsRegistered())
.BDDfy();
}
public void should_lookup_service()
{
this.Given(x => x.GivenAServiceIsRegistered("product", "localhost:600", 80))
.When(x => x.WhenILookupTheService("product"))
.Then(x => x.ThenTheServiceDetailsAreReturned())
.BDDfy();
}
private void ThenTheServiceDetailsAreReturned()
{
_services[0].HostAndPort.DownstreamHost.ShouldBe(_service.HostAndPort.DownstreamHost);
_services[0].HostAndPort.DownstreamPort.ShouldBe(_service.HostAndPort.DownstreamPort);
_services[0].Name.ShouldBe(_service.Name);
}
private void WhenILookupTheService(string name)
{
_services = _serviceRegistry.Lookup(name);
}
private void GivenAServiceIsRegistered(string name, string address, int port)
{
_service = new Service(name, new HostAndPort(address, port), string.Empty, string.Empty, new string[0]);
_serviceRepository.Set(_service);
}
private void GivenAServiceToRegister(string name, string address, int port)
{
_service = new Service(name, new HostAndPort(address, port), string.Empty, string.Empty, new string[0]);
}
private void WhenIRegisterTheService()
{
_serviceRegistry.Register(_service);
}
private void ThenTheServiceIsRegistered()
{
var serviceNameAndAddress = _serviceRepository.Get(_service.Name);
serviceNameAndAddress[0].HostAndPort.DownstreamHost.ShouldBe(_service.HostAndPort.DownstreamHost);
serviceNameAndAddress[0].HostAndPort.DownstreamPort.ShouldBe(_service.HostAndPort.DownstreamPort);
serviceNameAndAddress[0].Name.ShouldBe(_service.Name);
}
}
public interface IServiceRegistry
{
void Register(Service serviceNameAndAddress);
List<Service> Lookup(string name);
}
public class ServiceRegistry : IServiceRegistry
{
private readonly IServiceRepository _repository;
public ServiceRegistry(IServiceRepository repository)
{
_repository = repository;
}
public void Register(Service serviceNameAndAddress)
{
_repository.Set(serviceNameAndAddress);
}
public List<Service> Lookup(string name)
{
return _repository.Get(name);
}
}
public interface IServiceRepository
{
List<Service> Get(string serviceName);
void Set(Service serviceNameAndAddress);
}
public class ServiceRepository : IServiceRepository
{
private Dictionary<string, List<Service>> _registeredServices;
public ServiceRepository()
{
_registeredServices = new Dictionary<string, List<Service>>();
}
public List<Service> Get(string serviceName)
{
return _registeredServices[serviceName];
}
public void Set(Service serviceNameAndAddress)
{
List<Service> services;
if(_registeredServices.TryGetValue(serviceNameAndAddress.Name, out services))
{
services.Add(serviceNameAndAddress);
_registeredServices[serviceNameAndAddress.Name] = services;
}
else
{
_registeredServices[serviceNameAndAddress.Name] = new List<Service>(){ serviceNameAndAddress };
}
}
}
}

View File

@ -1,5 +1,5 @@
{
"version": "1.0.0-*",
"version": "0.0.0-dev",
"testRunner": "xunit",
@ -13,7 +13,7 @@
"Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Microsoft.AspNetCore.Http": "1.1.0",
"Ocelot": "1.0.0-*",
"Ocelot": "0.0.0-dev",
"xunit": "2.2.0-beta2-build3300",
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"Moq": "4.6.38-alpha",
@ -32,7 +32,7 @@
"win7-x64": {}
},
"frameworks": {
"netcoreapp1.4": {
"netcoreapp1.1": {
"imports": [
]
}