mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-06-19 22:48:15 +08:00
Feature/sticky sessions (#336)
* started messing around with sticky sessions idea * more tests for sticky session thing * more faffing cant make up my mind how to do this * +semver: breaking added sticky session load balancer and changed way load balancer configuration is set by user * #336 made tests BDDFy
This commit is contained in:
276
test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsTests.cs
Normal file
276
test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsTests.cs
Normal file
@ -0,0 +1,276 @@
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using Moq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using System.Threading;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.UnitTests.Responder;
|
||||
using TestStack.BDDfy;
|
||||
|
||||
public class CookieStickySessionsTests
|
||||
{
|
||||
private readonly CookieStickySessions _stickySessions;
|
||||
private readonly Mock<ILoadBalancer> _loadBalancer;
|
||||
private DownstreamContext _downstreamContext;
|
||||
private Response<ServiceHostAndPort> _result;
|
||||
private Response<ServiceHostAndPort> _firstHostAndPort;
|
||||
private Response<ServiceHostAndPort> _secondHostAndPort;
|
||||
|
||||
public CookieStickySessionsTests()
|
||||
{
|
||||
_loadBalancer = new Mock<ILoadBalancer>();
|
||||
const int defaultExpiryInMs = 100;
|
||||
_stickySessions = new CookieStickySessions(_loadBalancer.Object, "sessionid", defaultExpiryInMs);
|
||||
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_host_and_port()
|
||||
{
|
||||
this.Given(_ => GivenTheLoadBalancerReturns())
|
||||
.When(_ => WhenILease())
|
||||
.Then(_ => ThenTheHostAndPortIsNotNull())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_same_host_and_port()
|
||||
{
|
||||
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
|
||||
.And(_ => GivenTheDownstreamRequestHasSessionId("321"))
|
||||
.When(_ => WhenILeaseTwiceInARow())
|
||||
.Then(_ => ThenTheFirstAndSecondResponseAreTheSame())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_different_host_and_port_if_load_balancer_does()
|
||||
{
|
||||
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
|
||||
.When(_ => WhenIMakeTwoRequetsWithDifferentSessionValues())
|
||||
.Then(_ => ThenADifferentHostAndPortIsReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_error()
|
||||
{
|
||||
this.Given(_ => GivenTheLoadBalancerReturnsError())
|
||||
.When(_ => WhenILease())
|
||||
.Then(_ => ThenAnErrorIsReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_expire_sticky_session()
|
||||
{
|
||||
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
|
||||
.When(_ => WhenTheStickySessionExpires())
|
||||
.Then(_ => ThenANewHostAndPortIsReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_refresh_sticky_session()
|
||||
{
|
||||
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
|
||||
.When(_ => WhenIMakeRequestsToKeepRefreshingTheSession())
|
||||
.Then(_ => ThenTheSessionIsRefreshed())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_dispose()
|
||||
{
|
||||
_stickySessions.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_release()
|
||||
{
|
||||
_stickySessions.Release(new ServiceHostAndPort("", 0));
|
||||
}
|
||||
|
||||
private async Task ThenTheSessionIsRefreshed()
|
||||
{
|
||||
var postExpireHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||
postExpireHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||
postExpireHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||
|
||||
_loadBalancer
|
||||
.Verify(x => x.Lease(It.IsAny<DownstreamContext>()), Times.Once);
|
||||
}
|
||||
|
||||
private async Task WhenIMakeRequestsToKeepRefreshingTheSession()
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
var cookies = new FakeCookies();
|
||||
cookies.AddCookie("sessionid", "321");
|
||||
context.Request.Cookies = cookies;
|
||||
_downstreamContext = new DownstreamContext(context);
|
||||
|
||||
var firstHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||
firstHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||
firstHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||
|
||||
Thread.Sleep(80);
|
||||
|
||||
var secondHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||
secondHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||
secondHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||
|
||||
Thread.Sleep(80);
|
||||
}
|
||||
|
||||
private async Task ThenANewHostAndPortIsReturned()
|
||||
{
|
||||
var postExpireHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||
postExpireHostAndPort.Data.DownstreamHost.ShouldBe("two");
|
||||
postExpireHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||
}
|
||||
|
||||
private async Task WhenTheStickySessionExpires()
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
var cookies = new FakeCookies();
|
||||
cookies.AddCookie("sessionid", "321");
|
||||
context.Request.Cookies = cookies;
|
||||
_downstreamContext = new DownstreamContext(context);
|
||||
|
||||
var firstHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||
var secondHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||
|
||||
firstHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||
firstHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||
|
||||
secondHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||
secondHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||
|
||||
Thread.Sleep(150);
|
||||
}
|
||||
|
||||
private void ThenAnErrorIsReturned()
|
||||
{
|
||||
_result.IsError.ShouldBeTrue();
|
||||
}
|
||||
|
||||
private void GivenTheLoadBalancerReturnsError()
|
||||
{
|
||||
_loadBalancer
|
||||
.Setup(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||
.ReturnsAsync(new ErrorResponse<ServiceHostAndPort>(new AnyError()));
|
||||
}
|
||||
|
||||
private void ThenADifferentHostAndPortIsReturned()
|
||||
{
|
||||
_firstHostAndPort.Data.DownstreamHost.ShouldBe("one");
|
||||
_firstHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||
_secondHostAndPort.Data.DownstreamHost.ShouldBe("two");
|
||||
_secondHostAndPort.Data.DownstreamPort.ShouldBe(80);
|
||||
}
|
||||
|
||||
private async Task WhenIMakeTwoRequetsWithDifferentSessionValues()
|
||||
{
|
||||
var contextOne = new DefaultHttpContext();
|
||||
var cookiesOne = new FakeCookies();
|
||||
cookiesOne.AddCookie("sessionid", "321");
|
||||
contextOne.Request.Cookies = cookiesOne;
|
||||
var contextTwo = new DefaultHttpContext();
|
||||
var cookiesTwo = new FakeCookies();
|
||||
cookiesTwo.AddCookie("sessionid", "123");
|
||||
contextTwo.Request.Cookies = cookiesTwo;
|
||||
_firstHostAndPort = await _stickySessions.Lease(new DownstreamContext(contextOne));
|
||||
_secondHostAndPort = await _stickySessions.Lease(new DownstreamContext(contextTwo));
|
||||
}
|
||||
|
||||
private void GivenTheLoadBalancerReturnsSequence()
|
||||
{
|
||||
_loadBalancer
|
||||
.SetupSequence(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(new ServiceHostAndPort("one", 80)))
|
||||
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(new ServiceHostAndPort("two", 80)));
|
||||
}
|
||||
|
||||
private void ThenTheFirstAndSecondResponseAreTheSame()
|
||||
{
|
||||
_firstHostAndPort.Data.DownstreamHost.ShouldBe(_secondHostAndPort.Data.DownstreamHost);
|
||||
_firstHostAndPort.Data.DownstreamPort.ShouldBe(_secondHostAndPort.Data.DownstreamPort);
|
||||
}
|
||||
|
||||
private async Task WhenILeaseTwiceInARow()
|
||||
{
|
||||
_firstHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||
_secondHostAndPort = await _stickySessions.Lease(_downstreamContext);
|
||||
}
|
||||
|
||||
private void GivenTheDownstreamRequestHasSessionId(string value)
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
var cookies = new FakeCookies();
|
||||
cookies.AddCookie("sessionid", value);
|
||||
context.Request.Cookies = cookies;
|
||||
_downstreamContext = new DownstreamContext(context);
|
||||
}
|
||||
|
||||
private void GivenTheLoadBalancerReturns()
|
||||
{
|
||||
_loadBalancer
|
||||
.Setup(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(new ServiceHostAndPort("", 80)));
|
||||
}
|
||||
|
||||
private async Task WhenILease()
|
||||
{
|
||||
_result = await _stickySessions.Lease(_downstreamContext);
|
||||
}
|
||||
|
||||
private void ThenTheHostAndPortIsNotNull()
|
||||
{
|
||||
_result.Data.ShouldNotBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
class FakeCookies : IRequestCookieCollection
|
||||
{
|
||||
private readonly Dictionary<string, string> _cookies = new Dictionary<string, string>();
|
||||
|
||||
public string this[string key] => _cookies[key];
|
||||
|
||||
public int Count => _cookies.Count;
|
||||
|
||||
public ICollection<string> Keys => _cookies.Keys;
|
||||
|
||||
public void AddCookie(string key, string value)
|
||||
{
|
||||
_cookies[key] = value;
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return _cookies.ContainsKey(key);
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
||||
{
|
||||
return _cookies.GetEnumerator();
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out string value)
|
||||
{
|
||||
return _cookies.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _cookies.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,284 +1,288 @@
|
||||
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 ServiceHostAndPort _hostAndPort;
|
||||
private Response<ServiceHostAndPort> _result;
|
||||
private LeastConnection _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 ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
_services = availableServices;
|
||||
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||
|
||||
var tasks = new Task[100];
|
||||
|
||||
for(var i = 0; i < tasks.Length; i++)
|
||||
{
|
||||
tasks[i] = LeaseDelayAndRelease();
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_handle_service_returning_to_available()
|
||||
{
|
||||
var serviceName = "products";
|
||||
|
||||
var availableServices = new List<Service>
|
||||
{
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
_leastConnection = new LeastConnection(() => Task.FromResult(availableServices), serviceName);
|
||||
|
||||
var hostAndPortOne = _leastConnection.Lease().Result;
|
||||
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||
var hostAndPortTwo = _leastConnection.Lease().Result;
|
||||
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2");
|
||||
_leastConnection.Release(hostAndPortOne.Data);
|
||||
_leastConnection.Release(hostAndPortTwo.Data);
|
||||
|
||||
availableServices = new List<Service>
|
||||
{
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
hostAndPortOne = _leastConnection.Lease().Result;
|
||||
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||
hostAndPortTwo = _leastConnection.Lease().Result;
|
||||
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||
_leastConnection.Release(hostAndPortOne.Data);
|
||||
_leastConnection.Release(hostAndPortTwo.Data);
|
||||
|
||||
availableServices = new List<Service>
|
||||
{
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
hostAndPortOne = _leastConnection.Lease().Result;
|
||||
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||
hostAndPortTwo = _leastConnection.Lease().Result;
|
||||
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2");
|
||||
_leastConnection.Release(hostAndPortOne.Data);
|
||||
_leastConnection.Release(hostAndPortTwo.Data);
|
||||
}
|
||||
|
||||
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 ServiceHostAndPort("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 ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.3", 80), string.Empty, string.Empty, new string[0])
|
||||
};
|
||||
|
||||
_services = availableServices;
|
||||
_leastConnection = new LeastConnection(() => 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 ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
_services = availableServices;
|
||||
_leastConnection = new LeastConnection(() => 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 ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
_services = availableServices;
|
||||
_leastConnection = new LeastConnection(() => 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 ServiceHostAndPort("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 ServiceHostAndPort("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 LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||
}
|
||||
|
||||
private void WhenTheLoadBalancerStarts(List<Service> services, string serviceName)
|
||||
{
|
||||
GivenTheLoadBalancerStarts(services, serviceName);
|
||||
}
|
||||
|
||||
private void GivenAHostAndPort(ServiceHostAndPort 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
public class LeastConnectionTests
|
||||
{
|
||||
private ServiceHostAndPort _hostAndPort;
|
||||
private Response<ServiceHostAndPort> _result;
|
||||
private LeastConnection _leastConnection;
|
||||
private List<Service> _services;
|
||||
private Random _random;
|
||||
private DownstreamContext _context;
|
||||
|
||||
public LeastConnectionTests()
|
||||
{
|
||||
_context = new DownstreamContext(new DefaultHttpContext());
|
||||
_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 ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
_services = availableServices;
|
||||
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||
|
||||
var tasks = new Task[100];
|
||||
|
||||
for(var i = 0; i < tasks.Length; i++)
|
||||
{
|
||||
tasks[i] = LeaseDelayAndRelease();
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_handle_service_returning_to_available()
|
||||
{
|
||||
var serviceName = "products";
|
||||
|
||||
var availableServices = new List<Service>
|
||||
{
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
_leastConnection = new LeastConnection(() => Task.FromResult(availableServices), serviceName);
|
||||
|
||||
var hostAndPortOne = _leastConnection.Lease(_context).Result;
|
||||
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||
var hostAndPortTwo = _leastConnection.Lease(_context).Result;
|
||||
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2");
|
||||
_leastConnection.Release(hostAndPortOne.Data);
|
||||
_leastConnection.Release(hostAndPortTwo.Data);
|
||||
|
||||
availableServices = new List<Service>
|
||||
{
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
hostAndPortOne = _leastConnection.Lease(_context).Result;
|
||||
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||
hostAndPortTwo = _leastConnection.Lease(_context).Result;
|
||||
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||
_leastConnection.Release(hostAndPortOne.Data);
|
||||
_leastConnection.Release(hostAndPortTwo.Data);
|
||||
|
||||
availableServices = new List<Service>
|
||||
{
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
hostAndPortOne = _leastConnection.Lease(_context).Result;
|
||||
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||
hostAndPortTwo = _leastConnection.Lease(_context).Result;
|
||||
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2");
|
||||
_leastConnection.Release(hostAndPortOne.Data);
|
||||
_leastConnection.Release(hostAndPortTwo.Data);
|
||||
}
|
||||
|
||||
private async Task LeaseDelayAndRelease()
|
||||
{
|
||||
var hostAndPort = await _leastConnection.Lease(_context);
|
||||
await Task.Delay(_random.Next(1, 100));
|
||||
_leastConnection.Release(hostAndPort.Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_get_next_url()
|
||||
{
|
||||
var serviceName = "products";
|
||||
|
||||
var hostAndPort = new ServiceHostAndPort("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 ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.3", 80), string.Empty, string.Empty, new string[0])
|
||||
};
|
||||
|
||||
_services = availableServices;
|
||||
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||
|
||||
var response = _leastConnection.Lease(_context).Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease(_context).Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease(_context).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 ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
_services = availableServices;
|
||||
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||
|
||||
var response = _leastConnection.Lease(_context).Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease(_context).Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease(_context).Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease(_context).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 ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||
new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||
};
|
||||
|
||||
_services = availableServices;
|
||||
_leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||
|
||||
var response = _leastConnection.Lease(_context).Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease(_context).Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease(_context).Result;
|
||||
|
||||
response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost);
|
||||
|
||||
response = _leastConnection.Lease(_context).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(_context).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 ServiceHostAndPort("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 ServiceHostAndPort("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 LeastConnection(() => Task.FromResult(_services), serviceName);
|
||||
}
|
||||
|
||||
private void WhenTheLoadBalancerStarts(List<Service> services, string serviceName)
|
||||
{
|
||||
GivenTheLoadBalancerStarts(services, serviceName);
|
||||
}
|
||||
|
||||
private void GivenAHostAndPort(ServiceHostAndPort hostAndPort)
|
||||
{
|
||||
_hostAndPort = hostAndPort;
|
||||
}
|
||||
|
||||
private void WhenIGetTheNextHostAndPort()
|
||||
{
|
||||
_result = _leastConnection.Lease(_context).Result;
|
||||
}
|
||||
|
||||
private void ThenTheNextHostAndPortIsReturned()
|
||||
{
|
||||
_result.Data.DownstreamHost.ShouldBe(_hostAndPort.DownstreamHost);
|
||||
_result.Data.DownstreamPort.ShouldBe(_hostAndPort.DownstreamPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,126 +1,142 @@
|
||||
using Moq;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
using Shouldly;
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.ServiceDiscovery.Providers;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
public class LoadBalancerFactoryTests
|
||||
{
|
||||
private DownstreamReRoute _reRoute;
|
||||
private LoadBalancerFactory _factory;
|
||||
private ILoadBalancer _result;
|
||||
private Mock<IServiceDiscoveryProviderFactory> _serviceProviderFactory;
|
||||
private Mock<IServiceDiscoveryProvider> _serviceProvider;
|
||||
private ServiceProviderConfiguration _serviceProviderConfig;
|
||||
|
||||
public LoadBalancerFactoryTests()
|
||||
{
|
||||
_serviceProviderFactory = new Mock<IServiceDiscoveryProviderFactory>();
|
||||
_serviceProvider = new Mock<IServiceDiscoveryProvider>();
|
||||
_factory = new LoadBalancerFactory(_serviceProviderFactory.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_no_load_balancer()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithUpstreamHttpMethod(new List<string> { "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<NoLoadBalancer>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_round_robin_load_balancer()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithLoadBalancer("RoundRobin")
|
||||
.WithUpstreamHttpMethod(new List<string> {"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<RoundRobin>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_round_least_connection_balancer()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithLoadBalancer("LeastConnection")
|
||||
.WithUpstreamHttpMethod(new List<string> {"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<LeastConnection>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_call_service_provider()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithLoadBalancer("RoundRobin")
|
||||
.WithUpstreamHttpMethod(new List<string> {"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.ThenTheServiceProviderIsCalledCorrectly())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenAServiceProviderConfig(ServiceProviderConfiguration serviceProviderConfig)
|
||||
{
|
||||
_serviceProviderConfig = serviceProviderConfig;
|
||||
}
|
||||
|
||||
private void GivenTheServiceProviderFactoryReturns()
|
||||
{
|
||||
_serviceProviderFactory
|
||||
.Setup(x => x.Get(It.IsAny<ServiceProviderConfiguration>(), It.IsAny<DownstreamReRoute>()))
|
||||
.Returns(_serviceProvider.Object);
|
||||
}
|
||||
|
||||
private void ThenTheServiceProviderIsCalledCorrectly()
|
||||
{
|
||||
_serviceProviderFactory
|
||||
.Verify(x => x.Get(It.IsAny<ServiceProviderConfiguration>(), It.IsAny<DownstreamReRoute>()), Times.Once);
|
||||
}
|
||||
|
||||
private void GivenAReRoute(DownstreamReRoute reRoute)
|
||||
{
|
||||
_reRoute = reRoute;
|
||||
}
|
||||
|
||||
private void WhenIGetTheLoadBalancer()
|
||||
{
|
||||
_result = _factory.Get(_reRoute, _serviceProviderConfig).Result;
|
||||
}
|
||||
|
||||
private void ThenTheLoadBalancerIsReturned<T>()
|
||||
{
|
||||
_result.ShouldBeOfType<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
using Moq;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
using Shouldly;
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.ServiceDiscovery.Providers;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
public class LoadBalancerFactoryTests
|
||||
{
|
||||
private DownstreamReRoute _reRoute;
|
||||
private readonly LoadBalancerFactory _factory;
|
||||
private ILoadBalancer _result;
|
||||
private readonly Mock<IServiceDiscoveryProviderFactory> _serviceProviderFactory;
|
||||
private readonly Mock<IServiceDiscoveryProvider> _serviceProvider;
|
||||
private ServiceProviderConfiguration _serviceProviderConfig;
|
||||
|
||||
public LoadBalancerFactoryTests()
|
||||
{
|
||||
_serviceProviderFactory = new Mock<IServiceDiscoveryProviderFactory>();
|
||||
_serviceProvider = new Mock<IServiceDiscoveryProvider>();
|
||||
_factory = new LoadBalancerFactory(_serviceProviderFactory.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_no_load_balancer()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithUpstreamHttpMethod(new List<string> { "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<NoLoadBalancer>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_round_robin_load_balancer()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithLoadBalancerOptions(new LoadBalancerOptions("RoundRobin", "", 0))
|
||||
.WithUpstreamHttpMethod(new List<string> {"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<RoundRobin>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_round_least_connection_balancer()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithLoadBalancerOptions(new LoadBalancerOptions("LeastConnection", "", 0))
|
||||
.WithUpstreamHttpMethod(new List<string> {"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<LeastConnection>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_call_service_provider()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithLoadBalancerOptions(new LoadBalancerOptions("RoundRobin", "", 0))
|
||||
.WithUpstreamHttpMethod(new List<string> {"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.ThenTheServiceProviderIsCalledCorrectly())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_sticky_session()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithLoadBalancerOptions(new LoadBalancerOptions("CookieStickySessions", "", 0))
|
||||
.WithUpstreamHttpMethod(new List<string> {"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<CookieStickySessions>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenAServiceProviderConfig(ServiceProviderConfiguration serviceProviderConfig)
|
||||
{
|
||||
_serviceProviderConfig = serviceProviderConfig;
|
||||
}
|
||||
|
||||
private void GivenTheServiceProviderFactoryReturns()
|
||||
{
|
||||
_serviceProviderFactory
|
||||
.Setup(x => x.Get(It.IsAny<ServiceProviderConfiguration>(), It.IsAny<DownstreamReRoute>()))
|
||||
.Returns(_serviceProvider.Object);
|
||||
}
|
||||
|
||||
private void ThenTheServiceProviderIsCalledCorrectly()
|
||||
{
|
||||
_serviceProviderFactory
|
||||
.Verify(x => x.Get(It.IsAny<ServiceProviderConfiguration>(), It.IsAny<DownstreamReRoute>()), Times.Once);
|
||||
}
|
||||
|
||||
private void GivenAReRoute(DownstreamReRoute reRoute)
|
||||
{
|
||||
_reRoute = reRoute;
|
||||
}
|
||||
|
||||
private void WhenIGetTheLoadBalancer()
|
||||
{
|
||||
_result = _factory.Get(_reRoute, _serviceProviderConfig).Result;
|
||||
}
|
||||
|
||||
private void ThenTheLoadBalancerIsReturned<T>()
|
||||
{
|
||||
_result.ShouldBeOfType<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,163 +1,182 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Builder;
|
||||
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 DownstreamReRoute _reRoute;
|
||||
private ILoadBalancer _loadBalancer;
|
||||
private readonly LoadBalancerHouse _loadBalancerHouse;
|
||||
private Response<ILoadBalancer> _getResult;
|
||||
private readonly Mock<ILoadBalancerFactory> _factory;
|
||||
private readonly ServiceProviderConfiguration _serviceProviderConfig;
|
||||
|
||||
public LoadBalancerHouseTests()
|
||||
{
|
||||
_factory = new Mock<ILoadBalancerFactory>();
|
||||
_loadBalancerHouse = new LoadBalancerHouse(_factory.Object);
|
||||
_serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123, string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_store_load_balancer_on_first_request()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder().WithReRouteKey("test").Build();
|
||||
|
||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||
.Then(x => x.ThenItIsAdded())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_not_store_load_balancer_on_second_request()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build();
|
||||
|
||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
||||
.Then(x => x.ThenItIsReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_store_load_balancers_by_key()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build();
|
||||
var reRouteTwo = new DownstreamReRouteBuilder().WithLoadBalancer("FakeRoundRobinLoadBalancer").WithReRouteKey("testtwo").Build();
|
||||
|
||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||
.And(x => x.GivenThereIsALoadBalancer(reRouteTwo, new FakeRoundRobinLoadBalancer()))
|
||||
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
||||
.Then(x => x.ThenTheLoadBalancerIs<FakeLoadBalancer>())
|
||||
.When(x => x.WhenWeGetTheLoadBalancer(reRouteTwo))
|
||||
.Then(x => x.ThenTheLoadBalancerIs<FakeRoundRobinLoadBalancer>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_error_if_exception()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder().Build();
|
||||
|
||||
this.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
||||
.Then(x => x.ThenAnErrorIsReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_get_new_load_balancer_if_reroute_load_balancer_has_changed()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build();
|
||||
|
||||
var reRouteTwo = new DownstreamReRouteBuilder().WithLoadBalancer("LeastConnection").WithReRouteKey("test").Build();
|
||||
|
||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
||||
.Then(x => x.ThenTheLoadBalancerIs<FakeLoadBalancer>())
|
||||
.When(x => x.WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(reRouteTwo))
|
||||
.Then(x => x.ThenTheLoadBalancerIs<LeastConnection>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(DownstreamReRoute reRoute)
|
||||
{
|
||||
_reRoute = reRoute;
|
||||
_factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(new LeastConnection(null, null));
|
||||
_getResult = _loadBalancerHouse.Get(_reRoute, _serviceProviderConfig).Result;
|
||||
}
|
||||
|
||||
private void ThenAnErrorIsReturned()
|
||||
{
|
||||
_getResult.IsError.ShouldBeTrue();
|
||||
_getResult.Errors[0].ShouldBeOfType<UnableToFindLoadBalancerError>();
|
||||
}
|
||||
|
||||
private void ThenTheLoadBalancerIs<T>()
|
||||
{
|
||||
_getResult.Data.ShouldBeOfType<T>();
|
||||
}
|
||||
|
||||
private void ThenItIsAdded()
|
||||
{
|
||||
_getResult.IsError.ShouldBe(false);
|
||||
_getResult.ShouldBeOfType<OkResponse<ILoadBalancer>>();
|
||||
_getResult.Data.ShouldBe(_loadBalancer);
|
||||
_factory.Verify(x => x.Get(_reRoute, _serviceProviderConfig), Times.Once);
|
||||
}
|
||||
|
||||
private void GivenThereIsALoadBalancer(DownstreamReRoute reRoute, ILoadBalancer loadBalancer)
|
||||
{
|
||||
_reRoute = reRoute;
|
||||
_loadBalancer = loadBalancer;
|
||||
_factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(loadBalancer);
|
||||
_getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result;
|
||||
}
|
||||
|
||||
private void WhenWeGetTheLoadBalancer(DownstreamReRoute reRoute)
|
||||
{
|
||||
_getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result;
|
||||
}
|
||||
|
||||
private void ThenItIsReturned()
|
||||
{
|
||||
_getResult.Data.ShouldBe(_loadBalancer);
|
||||
_factory.Verify(x => x.Get(_reRoute, _serviceProviderConfig), Times.Once);
|
||||
}
|
||||
|
||||
class FakeLoadBalancer : ILoadBalancer
|
||||
{
|
||||
public Task<Response<ServiceHostAndPort>> Lease()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Release(ServiceHostAndPort hostAndPort)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
class FakeRoundRobinLoadBalancer : ILoadBalancer
|
||||
{
|
||||
public Task<Response<ServiceHostAndPort>> Lease()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Release(ServiceHostAndPort hostAndPort)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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.Values;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
public class LoadBalancerHouseTests
|
||||
{
|
||||
private DownstreamReRoute _reRoute;
|
||||
private ILoadBalancer _loadBalancer;
|
||||
private readonly LoadBalancerHouse _loadBalancerHouse;
|
||||
private Response<ILoadBalancer> _getResult;
|
||||
private readonly Mock<ILoadBalancerFactory> _factory;
|
||||
private readonly ServiceProviderConfiguration _serviceProviderConfig;
|
||||
|
||||
public LoadBalancerHouseTests()
|
||||
{
|
||||
_factory = new Mock<ILoadBalancerFactory>();
|
||||
_loadBalancerHouse = new LoadBalancerHouse(_factory.Object);
|
||||
_serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123, string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_store_load_balancer_on_first_request()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithReRouteKey("test")
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||
.Then(x => x.ThenItIsAdded())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_not_store_load_balancer_on_second_request()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithLoadBalancerOptions(new LoadBalancerOptions("FakeLoadBalancer", "", 0))
|
||||
.WithReRouteKey("test")
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
||||
.Then(x => x.ThenItIsReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_store_load_balancers_by_key()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithLoadBalancerOptions(new LoadBalancerOptions("FakeLoadBalancer", "", 0))
|
||||
.WithReRouteKey("test")
|
||||
.Build();
|
||||
|
||||
var reRouteTwo = new DownstreamReRouteBuilder()
|
||||
.WithLoadBalancerOptions(new LoadBalancerOptions("FakeRoundRobinLoadBalancer", "", 0))
|
||||
.WithReRouteKey("testtwo")
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||
.And(x => x.GivenThereIsALoadBalancer(reRouteTwo, new FakeRoundRobinLoadBalancer()))
|
||||
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
||||
.Then(x => x.ThenTheLoadBalancerIs<FakeLoadBalancer>())
|
||||
.When(x => x.WhenWeGetTheLoadBalancer(reRouteTwo))
|
||||
.Then(x => x.ThenTheLoadBalancerIs<FakeRoundRobinLoadBalancer>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_error_if_exception()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder().Build();
|
||||
|
||||
this.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
||||
.Then(x => x.ThenAnErrorIsReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_get_new_load_balancer_if_reroute_load_balancer_has_changed()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithLoadBalancerOptions(new LoadBalancerOptions("FakeLoadBalancer", "", 0))
|
||||
.WithReRouteKey("test")
|
||||
.Build();
|
||||
|
||||
var reRouteTwo = new DownstreamReRouteBuilder()
|
||||
.WithLoadBalancerOptions(new LoadBalancerOptions("LeastConnection", "", 0))
|
||||
.WithReRouteKey("test")
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer()))
|
||||
.When(x => x.WhenWeGetTheLoadBalancer(reRoute))
|
||||
.Then(x => x.ThenTheLoadBalancerIs<FakeLoadBalancer>())
|
||||
.When(x => x.WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(reRouteTwo))
|
||||
.Then(x => x.ThenTheLoadBalancerIs<LeastConnection>())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(DownstreamReRoute reRoute)
|
||||
{
|
||||
_reRoute = reRoute;
|
||||
_factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(new LeastConnection(null, null));
|
||||
_getResult = _loadBalancerHouse.Get(_reRoute, _serviceProviderConfig).Result;
|
||||
}
|
||||
|
||||
private void ThenAnErrorIsReturned()
|
||||
{
|
||||
_getResult.IsError.ShouldBeTrue();
|
||||
_getResult.Errors[0].ShouldBeOfType<UnableToFindLoadBalancerError>();
|
||||
}
|
||||
|
||||
private void ThenTheLoadBalancerIs<T>()
|
||||
{
|
||||
_getResult.Data.ShouldBeOfType<T>();
|
||||
}
|
||||
|
||||
private void ThenItIsAdded()
|
||||
{
|
||||
_getResult.IsError.ShouldBe(false);
|
||||
_getResult.ShouldBeOfType<OkResponse<ILoadBalancer>>();
|
||||
_getResult.Data.ShouldBe(_loadBalancer);
|
||||
_factory.Verify(x => x.Get(_reRoute, _serviceProviderConfig), Times.Once);
|
||||
}
|
||||
|
||||
private void GivenThereIsALoadBalancer(DownstreamReRoute reRoute, ILoadBalancer loadBalancer)
|
||||
{
|
||||
_reRoute = reRoute;
|
||||
_loadBalancer = loadBalancer;
|
||||
_factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(loadBalancer);
|
||||
_getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result;
|
||||
}
|
||||
|
||||
private void WhenWeGetTheLoadBalancer(DownstreamReRoute reRoute)
|
||||
{
|
||||
_getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result;
|
||||
}
|
||||
|
||||
private void ThenItIsReturned()
|
||||
{
|
||||
_getResult.Data.ShouldBe(_loadBalancer);
|
||||
_factory.Verify(x => x.Get(_reRoute, _serviceProviderConfig), Times.Once);
|
||||
}
|
||||
|
||||
class FakeLoadBalancer : ILoadBalancer
|
||||
{
|
||||
public Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Release(ServiceHostAndPort hostAndPort)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
class FakeRoundRobinLoadBalancer : ILoadBalancer
|
||||
{
|
||||
public Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Release(ServiceHostAndPort hostAndPort)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,193 +1,193 @@
|
||||
using Ocelot.Middleware;
|
||||
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Moq;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.Errors;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.LoadBalancer.Middleware;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Request.Middleware;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
public class LoadBalancerMiddlewareTests
|
||||
{
|
||||
private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse;
|
||||
private readonly Mock<ILoadBalancer> _loadBalancer;
|
||||
private ServiceHostAndPort _hostAndPort;
|
||||
private ErrorResponse<ILoadBalancer> _getLoadBalancerHouseError;
|
||||
private ErrorResponse<ServiceHostAndPort> _getHostAndPortError;
|
||||
private HttpRequestMessage _downstreamRequest;
|
||||
private ServiceProviderConfiguration _config;
|
||||
private Mock<IOcelotLoggerFactory> _loggerFactory;
|
||||
private Mock<IOcelotLogger> _logger;
|
||||
private LoadBalancingMiddleware _middleware;
|
||||
private DownstreamContext _downstreamContext;
|
||||
private OcelotRequestDelegate _next;
|
||||
|
||||
public LoadBalancerMiddlewareTests()
|
||||
{
|
||||
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
|
||||
_loadBalancer = new Mock<ILoadBalancer>();
|
||||
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
|
||||
_downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "http://test.com/");
|
||||
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
|
||||
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
||||
_logger = new Mock<IOcelotLogger>();
|
||||
_loggerFactory.Setup(x => x.CreateLogger<LoadBalancingMiddleware>()).Returns(_logger.Object);
|
||||
_next = context => Task.CompletedTask;
|
||||
_downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_call_scoped_data_repository_correctly()
|
||||
{
|
||||
var downstreamRoute = new DownstreamReRouteBuilder()
|
||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||
.Build();
|
||||
|
||||
var serviceProviderConfig = new ServiceProviderConfigurationBuilder()
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123"))
|
||||
.And(x => GivenTheConfigurationIs(serviceProviderConfig))
|
||||
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue>()))
|
||||
.And(x => x.GivenTheLoadBalancerHouseReturns())
|
||||
.And(x => x.GivenTheLoadBalancerReturns())
|
||||
.When(x => x.WhenICallTheMiddleware())
|
||||
.Then(x => x.ThenTheDownstreamUrlIsReplacedWith("http://127.0.0.1:80/abc?q=123"))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_set_pipeline_error_if_cannot_get_load_balancer()
|
||||
{
|
||||
var downstreamRoute = new DownstreamReRouteBuilder()
|
||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||
.Build();
|
||||
|
||||
var serviceProviderConfig = new ServiceProviderConfigurationBuilder()
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123"))
|
||||
.And(x => GivenTheConfigurationIs(serviceProviderConfig))
|
||||
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue>()))
|
||||
.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 DownstreamReRouteBuilder()
|
||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||
.Build();
|
||||
|
||||
var serviceProviderConfig = new ServiceProviderConfigurationBuilder()
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123"))
|
||||
.And(x => GivenTheConfigurationIs(serviceProviderConfig))
|
||||
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue>()))
|
||||
.And(x => x.GivenTheLoadBalancerHouseReturns())
|
||||
.And(x => x.GivenTheLoadBalancerReturnsAnError())
|
||||
.When(x => x.WhenICallTheMiddleware())
|
||||
.Then(x => x.ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void WhenICallTheMiddleware()
|
||||
{
|
||||
_middleware = new LoadBalancingMiddleware(_next, _loggerFactory.Object, _loadBalancerHouse.Object);
|
||||
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private void GivenTheConfigurationIs(ServiceProviderConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
_downstreamContext.ServiceProviderConfiguration = config;
|
||||
}
|
||||
|
||||
private void GivenTheDownStreamUrlIs(string downstreamUrl)
|
||||
{
|
||||
_downstreamRequest.RequestUri = new System.Uri(downstreamUrl);
|
||||
_downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest);
|
||||
}
|
||||
|
||||
private void GivenTheLoadBalancerReturnsAnError()
|
||||
{
|
||||
_getHostAndPortError = new ErrorResponse<ServiceHostAndPort>(new List<Error>() { new ServicesAreNullError($"services were null for bah") });
|
||||
_loadBalancer
|
||||
.Setup(x => x.Lease())
|
||||
.ReturnsAsync(_getHostAndPortError);
|
||||
}
|
||||
|
||||
private void GivenTheLoadBalancerReturns()
|
||||
{
|
||||
_hostAndPort = new ServiceHostAndPort("127.0.0.1", 80);
|
||||
_loadBalancer
|
||||
.Setup(x => x.Lease())
|
||||
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(_hostAndPort));
|
||||
}
|
||||
|
||||
private void GivenTheDownStreamRouteIs(DownstreamReRoute downstreamRoute, List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue> placeholder)
|
||||
{
|
||||
_downstreamContext.TemplatePlaceholderNameAndValues = placeholder;
|
||||
_downstreamContext.DownstreamReRoute = downstreamRoute;
|
||||
}
|
||||
|
||||
private void GivenTheLoadBalancerHouseReturns()
|
||||
{
|
||||
_loadBalancerHouse
|
||||
.Setup(x => x.Get(It.IsAny<DownstreamReRoute>(), It.IsAny<ServiceProviderConfiguration>()))
|
||||
.ReturnsAsync(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<DownstreamReRoute>(), It.IsAny<ServiceProviderConfiguration>()))
|
||||
.ReturnsAsync(_getLoadBalancerHouseError);
|
||||
}
|
||||
|
||||
private void ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline()
|
||||
{
|
||||
_downstreamContext.IsError.ShouldBeTrue();
|
||||
_downstreamContext.Errors.ShouldBe(_getLoadBalancerHouseError.Errors);
|
||||
}
|
||||
|
||||
private void ThenAnErrorSayingReleaseFailedIsSetOnThePipeline()
|
||||
{
|
||||
_downstreamContext.IsError.ShouldBeTrue();
|
||||
_downstreamContext.Errors.ShouldBe(It.IsAny<List<Error>>());
|
||||
}
|
||||
|
||||
private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline()
|
||||
{
|
||||
_downstreamContext.IsError.ShouldBeTrue();
|
||||
_downstreamContext.Errors.ShouldBe(_getHostAndPortError.Errors);
|
||||
}
|
||||
|
||||
private void ThenTheDownstreamUrlIsReplacedWith(string expectedUri)
|
||||
{
|
||||
_downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
using Ocelot.Middleware;
|
||||
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Moq;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.Errors;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.LoadBalancer.Middleware;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Request.Middleware;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
public class LoadBalancerMiddlewareTests
|
||||
{
|
||||
private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse;
|
||||
private readonly Mock<ILoadBalancer> _loadBalancer;
|
||||
private ServiceHostAndPort _hostAndPort;
|
||||
private ErrorResponse<ILoadBalancer> _getLoadBalancerHouseError;
|
||||
private ErrorResponse<ServiceHostAndPort> _getHostAndPortError;
|
||||
private HttpRequestMessage _downstreamRequest;
|
||||
private ServiceProviderConfiguration _config;
|
||||
private Mock<IOcelotLoggerFactory> _loggerFactory;
|
||||
private Mock<IOcelotLogger> _logger;
|
||||
private LoadBalancingMiddleware _middleware;
|
||||
private DownstreamContext _downstreamContext;
|
||||
private OcelotRequestDelegate _next;
|
||||
|
||||
public LoadBalancerMiddlewareTests()
|
||||
{
|
||||
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
|
||||
_loadBalancer = new Mock<ILoadBalancer>();
|
||||
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
|
||||
_downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "http://test.com/");
|
||||
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
|
||||
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
||||
_logger = new Mock<IOcelotLogger>();
|
||||
_loggerFactory.Setup(x => x.CreateLogger<LoadBalancingMiddleware>()).Returns(_logger.Object);
|
||||
_next = context => Task.CompletedTask;
|
||||
_downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_call_scoped_data_repository_correctly()
|
||||
{
|
||||
var downstreamRoute = new DownstreamReRouteBuilder()
|
||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||
.Build();
|
||||
|
||||
var serviceProviderConfig = new ServiceProviderConfigurationBuilder()
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123"))
|
||||
.And(x => GivenTheConfigurationIs(serviceProviderConfig))
|
||||
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue>()))
|
||||
.And(x => x.GivenTheLoadBalancerHouseReturns())
|
||||
.And(x => x.GivenTheLoadBalancerReturns())
|
||||
.When(x => x.WhenICallTheMiddleware())
|
||||
.Then(x => x.ThenTheDownstreamUrlIsReplacedWith("http://127.0.0.1:80/abc?q=123"))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_set_pipeline_error_if_cannot_get_load_balancer()
|
||||
{
|
||||
var downstreamRoute = new DownstreamReRouteBuilder()
|
||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||
.Build();
|
||||
|
||||
var serviceProviderConfig = new ServiceProviderConfigurationBuilder()
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123"))
|
||||
.And(x => GivenTheConfigurationIs(serviceProviderConfig))
|
||||
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue>()))
|
||||
.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 DownstreamReRouteBuilder()
|
||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||
.Build();
|
||||
|
||||
var serviceProviderConfig = new ServiceProviderConfigurationBuilder()
|
||||
.Build();
|
||||
|
||||
this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123"))
|
||||
.And(x => GivenTheConfigurationIs(serviceProviderConfig))
|
||||
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute, new List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue>()))
|
||||
.And(x => x.GivenTheLoadBalancerHouseReturns())
|
||||
.And(x => x.GivenTheLoadBalancerReturnsAnError())
|
||||
.When(x => x.WhenICallTheMiddleware())
|
||||
.Then(x => x.ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void WhenICallTheMiddleware()
|
||||
{
|
||||
_middleware = new LoadBalancingMiddleware(_next, _loggerFactory.Object, _loadBalancerHouse.Object);
|
||||
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private void GivenTheConfigurationIs(ServiceProviderConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
_downstreamContext.ServiceProviderConfiguration = config;
|
||||
}
|
||||
|
||||
private void GivenTheDownStreamUrlIs(string downstreamUrl)
|
||||
{
|
||||
_downstreamRequest.RequestUri = new System.Uri(downstreamUrl);
|
||||
_downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest);
|
||||
}
|
||||
|
||||
private void GivenTheLoadBalancerReturnsAnError()
|
||||
{
|
||||
_getHostAndPortError = new ErrorResponse<ServiceHostAndPort>(new List<Error>() { new ServicesAreNullError($"services were null for bah") });
|
||||
_loadBalancer
|
||||
.Setup(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||
.ReturnsAsync(_getHostAndPortError);
|
||||
}
|
||||
|
||||
private void GivenTheLoadBalancerReturns()
|
||||
{
|
||||
_hostAndPort = new ServiceHostAndPort("127.0.0.1", 80);
|
||||
_loadBalancer
|
||||
.Setup(x => x.Lease(It.IsAny<DownstreamContext>()))
|
||||
.ReturnsAsync(new OkResponse<ServiceHostAndPort>(_hostAndPort));
|
||||
}
|
||||
|
||||
private void GivenTheDownStreamRouteIs(DownstreamReRoute downstreamRoute, List<Ocelot.DownstreamRouteFinder.UrlMatcher.PlaceholderNameAndValue> placeholder)
|
||||
{
|
||||
_downstreamContext.TemplatePlaceholderNameAndValues = placeholder;
|
||||
_downstreamContext.DownstreamReRoute = downstreamRoute;
|
||||
}
|
||||
|
||||
private void GivenTheLoadBalancerHouseReturns()
|
||||
{
|
||||
_loadBalancerHouse
|
||||
.Setup(x => x.Get(It.IsAny<DownstreamReRoute>(), It.IsAny<ServiceProviderConfiguration>()))
|
||||
.ReturnsAsync(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<DownstreamReRoute>(), It.IsAny<ServiceProviderConfiguration>()))
|
||||
.ReturnsAsync(_getLoadBalancerHouseError);
|
||||
}
|
||||
|
||||
private void ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline()
|
||||
{
|
||||
_downstreamContext.IsError.ShouldBeTrue();
|
||||
_downstreamContext.Errors.ShouldBe(_getLoadBalancerHouseError.Errors);
|
||||
}
|
||||
|
||||
private void ThenAnErrorSayingReleaseFailedIsSetOnThePipeline()
|
||||
{
|
||||
_downstreamContext.IsError.ShouldBeTrue();
|
||||
_downstreamContext.Errors.ShouldBe(It.IsAny<List<Error>>());
|
||||
}
|
||||
|
||||
private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline()
|
||||
{
|
||||
_downstreamContext.IsError.ShouldBeTrue();
|
||||
_downstreamContext.Errors.ShouldBe(_getHostAndPortError.Errors);
|
||||
}
|
||||
|
||||
private void ThenTheDownstreamUrlIsReplacedWith(string expectedUri)
|
||||
{
|
||||
_downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,75 +1,77 @@
|
||||
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<ServiceHostAndPort> _result;
|
||||
|
||||
[Fact]
|
||||
public void should_return_host_and_port()
|
||||
{
|
||||
var hostAndPort = new ServiceHostAndPort("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();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_error_if_no_services()
|
||||
{
|
||||
var services = new List<Service>();
|
||||
|
||||
this.Given(x => x.GivenServices(services))
|
||||
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||
.Then(x => x.ThenThereIsAnError())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_error_if_null_services()
|
||||
{
|
||||
List<Service> services = null;
|
||||
|
||||
this.Given(x => x.GivenServices(services))
|
||||
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||
.Then(x => x.ThenThereIsAnError())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void ThenThereIsAnError()
|
||||
{
|
||||
_result.IsError.ShouldBeTrue();
|
||||
}
|
||||
|
||||
private void GivenServices(List<Service> services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
private void WhenIGetTheNextHostAndPort()
|
||||
{
|
||||
_loadBalancer = new NoLoadBalancer(_services);
|
||||
_result = _loadBalancer.Lease().Result;
|
||||
}
|
||||
|
||||
private void ThenTheHostAndPortIs(ServiceHostAndPort expected)
|
||||
{
|
||||
_result.Data.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Middleware;
|
||||
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<ServiceHostAndPort> _result;
|
||||
|
||||
[Fact]
|
||||
public void should_return_host_and_port()
|
||||
{
|
||||
var hostAndPort = new ServiceHostAndPort("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();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_error_if_no_services()
|
||||
{
|
||||
var services = new List<Service>();
|
||||
|
||||
this.Given(x => x.GivenServices(services))
|
||||
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||
.Then(x => x.ThenThereIsAnError())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_error_if_null_services()
|
||||
{
|
||||
List<Service> services = null;
|
||||
|
||||
this.Given(x => x.GivenServices(services))
|
||||
.When(x => x.WhenIGetTheNextHostAndPort())
|
||||
.Then(x => x.ThenThereIsAnError())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void ThenThereIsAnError()
|
||||
{
|
||||
_result.IsError.ShouldBeTrue();
|
||||
}
|
||||
|
||||
private void GivenServices(List<Service> services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
private void WhenIGetTheNextHostAndPort()
|
||||
{
|
||||
_loadBalancer = new NoLoadBalancer(_services);
|
||||
_result = _loadBalancer.Lease(new DownstreamContext(new DefaultHttpContext())).Result;
|
||||
}
|
||||
|
||||
private void ThenTheHostAndPortIs(ServiceHostAndPort expected)
|
||||
{
|
||||
_result.Data.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,69 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
public class RoundRobinTests
|
||||
{
|
||||
private readonly RoundRobin _roundRobin;
|
||||
private readonly List<Service> _services;
|
||||
private Response<ServiceHostAndPort> _hostAndPort;
|
||||
|
||||
public RoundRobinTests()
|
||||
{
|
||||
_services = new List<Service>
|
||||
{
|
||||
new Service("product", new ServiceHostAndPort("127.0.0.1", 5000), string.Empty, string.Empty, new string[0]),
|
||||
new Service("product", new ServiceHostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0]),
|
||||
new Service("product", new ServiceHostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0])
|
||||
};
|
||||
|
||||
_roundRobin = new RoundRobin(() => Task.FromResult(_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Middleware;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Ocelot.UnitTests.LoadBalancer
|
||||
{
|
||||
public class RoundRobinTests
|
||||
{
|
||||
private readonly RoundRobin _roundRobin;
|
||||
private readonly List<Service> _services;
|
||||
private Response<ServiceHostAndPort> _hostAndPort;
|
||||
private DownstreamContext _context;
|
||||
|
||||
public RoundRobinTests()
|
||||
{
|
||||
_context = new DownstreamContext(new DefaultHttpContext());
|
||||
|
||||
_services = new List<Service>
|
||||
{
|
||||
new Service("product", new ServiceHostAndPort("127.0.0.1", 5000), string.Empty, string.Empty, new string[0]),
|
||||
new Service("product", new ServiceHostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0]),
|
||||
new Service("product", new ServiceHostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0])
|
||||
};
|
||||
|
||||
_roundRobin = new RoundRobin(() => Task.FromResult(_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(_context).Result;
|
||||
address.Data.ShouldBe(_services[0].HostAndPort);
|
||||
address = _roundRobin.Lease(_context).Result;
|
||||
address.Data.ShouldBe(_services[1].HostAndPort);
|
||||
address = _roundRobin.Lease(_context).Result;
|
||||
address.Data.ShouldBe(_services[2].HostAndPort);
|
||||
}
|
||||
}
|
||||
|
||||
private void GivenIGetTheNextAddress()
|
||||
{
|
||||
_hostAndPort = _roundRobin.Lease(_context).Result;
|
||||
}
|
||||
|
||||
private void ThenTheNextAddressIndexIs(int index)
|
||||
{
|
||||
_hostAndPort.Data.ShouldBe(_services[index].HostAndPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user