namespace Ocelot.AcceptanceTests { using System; using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; using Ocelot.LoadBalancer.LoadBalancers; using Shouldly; using TestStack.BDDfy; using Xunit; public class LoadBalancerTests : IDisposable { private readonly Steps _steps; private int _counterOne; private int _counterTwo; private static readonly object _syncLock = new object(); private readonly ServiceHandler _serviceHandler; public LoadBalancerTests() { _serviceHandler = new ServiceHandler(); _steps = new Steps(); } [Fact] public void should_load_balance_request_with_least_connection() { int portOne = 50591; int portTwo = 51482; var downstreamServiceOneUrl = $"http://localhost:{portOne}"; var downstreamServiceTwoUrl = $"http://localhost:{portTwo}"; var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "/", DownstreamScheme = "http", UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get" }, LoadBalancerOptions = new FileLoadBalancerOptions { Type = nameof(LeastConnection) }, DownstreamHostAndPorts = new List { new FileHostAndPort { Host = "localhost", Port = portOne }, new FileHostAndPort { Host = "localhost", Port = portTwo } } } }, GlobalConfiguration = new FileGlobalConfiguration() { } }; this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50)) .Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50)) .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26)) .BDDfy(); } [Fact] public void should_load_balance_request_with_round_robin() { var downstreamPortOne = 51701; var downstreamPortTwo = 53802; var downstreamServiceOneUrl = $"http://localhost:{downstreamPortOne}"; var downstreamServiceTwoUrl = $"http://localhost:{downstreamPortTwo}"; var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "/", DownstreamScheme = "http", UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get" }, LoadBalancerOptions = new FileLoadBalancerOptions { Type = nameof(RoundRobin) }, DownstreamHostAndPorts = new List { new FileHostAndPort { Host = "localhost", Port = downstreamPortOne }, new FileHostAndPort { Host = "localhost", Port = downstreamPortTwo } } } }, GlobalConfiguration = new FileGlobalConfiguration() { } }; this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50)) .Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50)) .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26)) .BDDfy(); } private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top) { _counterOne.ShouldBeInRange(bottom, top); _counterOne.ShouldBeInRange(bottom, top); } private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected) { var total = _counterOne + _counterTwo; total.ShouldBe(expected); } private void GivenProductServiceOneIsRunning(string url, int statusCode) { _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => { try { var response = string.Empty; lock (_syncLock) { _counterOne++; response = _counterOne.ToString(); } context.Response.StatusCode = statusCode; await context.Response.WriteAsync(response); } catch (Exception exception) { await context.Response.WriteAsync(exception.StackTrace); } }); } private void GivenProductServiceTwoIsRunning(string url, int statusCode) { _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => { try { var response = string.Empty; lock (_syncLock) { _counterTwo++; response = _counterTwo.ToString(); } context.Response.StatusCode = statusCode; await context.Response.WriteAsync(response); } catch (Exception exception) { await context.Response.WriteAsync(exception.StackTrace); } }); } public void Dispose() { _serviceHandler?.Dispose(); _steps.Dispose(); } } }