diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs index a2780062..79efddca 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs @@ -17,10 +17,16 @@ namespace Ocelot.Configuration.Repository private string _previousAsJson; private readonly Timer _timer; private bool _polling; + private readonly IConsulPollerConfiguration _config; - public ConsulFileConfigurationPoller(IOcelotLoggerFactory factory, IFileConfigurationRepository repo, IFileConfigurationSetter setter) + public ConsulFileConfigurationPoller( + IOcelotLoggerFactory factory, + IFileConfigurationRepository repo, + IFileConfigurationSetter setter, + IConsulPollerConfiguration config) { _setter = setter; + _config = config; _logger = factory.CreateLogger(); _repo = repo; _previousAsJson = ""; @@ -30,11 +36,11 @@ namespace Ocelot.Configuration.Repository { return; } - + _polling = true; await Poll(); _polling = false; - }, null, 0, 1000); + }, null, 0, _config.Delay); } private async Task Poll() @@ -63,8 +69,7 @@ namespace Ocelot.Configuration.Repository /// /// We could do object comparison here but performance isnt really a problem. This might be an issue one day! /// - /// - /// + /// hash of the config private string ToJson(FileConfiguration config) { var currentHash = JsonConvert.SerializeObject(config); diff --git a/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs b/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs new file mode 100644 index 00000000..93003087 --- /dev/null +++ b/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs @@ -0,0 +1,8 @@ +namespace Ocelot.Configuration.Repository +{ + public interface IConsulPollerConfiguration + { + int Delay { get; } + } + + } diff --git a/src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs b/src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs new file mode 100644 index 00000000..9e411f76 --- /dev/null +++ b/src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs @@ -0,0 +1,7 @@ +namespace Ocelot.Configuration.Repository +{ + public class InMemoryConsulPollerConfiguration : IConsulPollerConfiguration + { + public int Delay => 1000; + } +} diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 9e2e763b..eccab083 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -148,6 +148,7 @@ namespace Ocelot.DependencyInjection // We add this here so that we can always inject something into the factory for IoC.. _services.AddSingleton(); + _services.TryAddSingleton(); } public IOcelotAdministrationBuilder AddAdministration(string path, string secret) diff --git a/test/Ocelot.UnitTests/Wait.cs b/src/Ocelot/Infrastructure/Wait.cs similarity index 82% rename from test/Ocelot.UnitTests/Wait.cs rename to src/Ocelot/Infrastructure/Wait.cs index d8fd3a88..934e31e5 100644 --- a/test/Ocelot.UnitTests/Wait.cs +++ b/src/Ocelot/Infrastructure/Wait.cs @@ -1,4 +1,4 @@ -namespace Ocelot.UnitTests +namespace Ocelot.Infrastructure { public class Wait { @@ -7,4 +7,4 @@ namespace Ocelot.UnitTests return new Waiter(milliSeconds); } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/Waiter.cs b/src/Ocelot/Infrastructure/Waiter.cs similarity index 93% rename from test/Ocelot.UnitTests/Waiter.cs rename to src/Ocelot/Infrastructure/Waiter.cs index 8847c5b9..730bdbff 100644 --- a/test/Ocelot.UnitTests/Waiter.cs +++ b/src/Ocelot/Infrastructure/Waiter.cs @@ -1,47 +1,47 @@ -using System; -using System.Diagnostics; - -namespace Ocelot.UnitTests -{ - public class Waiter - { - private readonly int _milliSeconds; - - public Waiter(int milliSeconds) - { - _milliSeconds = milliSeconds; - } - - public bool Until(Func condition) - { - var stopwatch = Stopwatch.StartNew(); - var passed = false; - while (stopwatch.ElapsedMilliseconds < _milliSeconds) - { - if (condition.Invoke()) - { - passed = true; - break; - } - } - - return passed; - } - - public bool Until(Func condition) - { - var stopwatch = Stopwatch.StartNew(); - var passed = false; - while (stopwatch.ElapsedMilliseconds < _milliSeconds) - { - if (condition.Invoke()) - { - passed = true; - break; - } - } - - return passed; - } - } -} +using System; +using System.Diagnostics; + +namespace Ocelot.Infrastructure +{ + public class Waiter + { + private readonly int _milliSeconds; + + public Waiter(int milliSeconds) + { + _milliSeconds = milliSeconds; + } + + public bool Until(Func condition) + { + var stopwatch = Stopwatch.StartNew(); + var passed = false; + while (stopwatch.ElapsedMilliseconds < _milliSeconds) + { + if (condition.Invoke()) + { + passed = true; + break; + } + } + + return passed; + } + + public bool Until(Func condition) + { + var stopwatch = Stopwatch.StartNew(); + var passed = false; + while (stopwatch.ElapsedMilliseconds < _milliSeconds) + { + if (condition.Invoke()) + { + passed = true; + break; + } + } + + return passed; + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index 7c9ac239..797f340c 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -9,8 +9,10 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using Ocelot.Configuration.File; +using Shouldly; using TestStack.BDDfy; using Xunit; +using static Ocelot.Infrastructure.Wait; namespace Ocelot.AcceptanceTests { @@ -261,21 +263,27 @@ namespace Ocelot.AcceptanceTests .And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(x => GivenTheConsulConfigurationIs(secondConsulConfig)) - .And(x => GivenIWaitForTheConfigToReplicateToOcelot()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .When(x => GivenTheConsulConfigurationIs(secondConsulConfig)) + .Then(x => ThenTheConfigIsUpdatedInOcelot()) .BDDfy(); } - private void GivenIWaitForTheConfigToReplicateToOcelot() + private void ThenTheConfigIsUpdatedInOcelot() { - var stopWatch = Stopwatch.StartNew(); - while (stopWatch.ElapsedMilliseconds < 10000) - { - //do nothing! - } + var result = WaitFor(20000).Until(() => { + try + { + _steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome"); + _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK); + _steps.ThenTheResponseBodyShouldBe("Hello from Laura"); + return true; + } + catch (Exception) + { + return false; + } + }); + result.ShouldBeTrue(); } private void GivenTheConsulConfigurationIs(FileConfiguration config) diff --git a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs b/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs index 4a6faba2..6499c4e0 100644 --- a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs @@ -1,16 +1,17 @@ using System; using System.Collections.Generic; -using System.Diagnostics; +using System.Threading; using Moq; using Ocelot.Configuration.File; using Ocelot.Configuration.Repository; using Ocelot.Configuration.Setter; using Ocelot.Logging; using Ocelot.Responses; +using Ocelot.UnitTests.Responder; using TestStack.BDDfy; using Xunit; using Shouldly; -using static Ocelot.UnitTests.Wait; +using static Ocelot.Infrastructure.Wait; namespace Ocelot.UnitTests.Configuration { @@ -21,6 +22,7 @@ namespace Ocelot.UnitTests.Configuration private Mock _repo; private Mock _setter; private FileConfiguration _fileConfig; + private Mock _config; public ConsulFileConfigurationPollerTests() { @@ -30,8 +32,10 @@ namespace Ocelot.UnitTests.Configuration _repo = new Mock(); _setter = new Mock(); _fileConfig = new FileConfiguration(); + _config = new Mock(); _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse(_fileConfig)); - _poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object); + _config.Setup(x => x.Delay).Returns(10); + _poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object, _config.Object); } public void Dispose() @@ -42,7 +46,7 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_start() { - this.Given(x => ThenTheSetterIsCalled(_fileConfig)) + this.Given(x => ThenTheSetterIsCalled(_fileConfig, 1)) .BDDfy(); } @@ -65,22 +69,82 @@ namespace Ocelot.UnitTests.Configuration } }; - this.Given(x => WhenTheConfigIsChangedInConsul(newConfig)) - .Then(x => ThenTheSetterIsCalled(newConfig)) + this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 0)) + .Then(x => ThenTheSetterIsCalled(newConfig, 1)) .BDDfy(); } - private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig) + [Fact] + public void should_not_poll_if_already_polling() { - _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse(newConfig)); + var newConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "test" + } + }, + } + } + }; + + this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 10)) + .Then(x => ThenTheSetterIsCalled(newConfig, 1)) + .BDDfy(); } - private void ThenTheSetterIsCalled(FileConfiguration fileConfig) + [Fact] + public void should_do_nothing_if_call_to_consul_fails() + { + var newConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "test" + } + }, + } + } + }; + + this.Given(x => WhenConsulErrors()) + .Then(x => ThenTheSetterIsCalled(newConfig, 0)) + .BDDfy(); + } + + private void WhenConsulErrors() + { + _repo + .Setup(x => x.Get()) + .ReturnsAsync(new ErrorResponse(new AnyError())); + } + + private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig, int delay) + { + _repo + .Setup(x => x.Get()) + .Callback(() => Thread.Sleep(delay)) + .ReturnsAsync(new OkResponse(newConfig)); + } + + private void ThenTheSetterIsCalled(FileConfiguration fileConfig, int times) { var result = WaitFor(2000).Until(() => { try { - _setter.Verify(x => x.Set(fileConfig), Times.Once); + _setter.Verify(x => x.Set(fileConfig), Times.Exactly(times)); return true; } catch(Exception) diff --git a/test/Ocelot.UnitTests/ServerHostedMiddlewareTest.cs b/test/Ocelot.UnitTests/ServerHostedMiddlewareTest.cs deleted file mode 100644 index 15a2d673..00000000 --- a/test/Ocelot.UnitTests/ServerHostedMiddlewareTest.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Ocelot.UnitTests -{ - using System; - using System.IO; - using System.Net.Http; - using Microsoft.AspNetCore.TestHost; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.AspNetCore.Builder; - using Moq; - using Ocelot.Infrastructure.RequestData; - - public abstract class ServerHostedMiddlewareTest : IDisposable - { - protected TestServer Server { get; private set; } - protected HttpClient Client { get; private set; } - protected string Url { get; private set; } - protected HttpResponseMessage ResponseMessage { get; private set; } - protected Mock ScopedRepository { get; private set; } - - public ServerHostedMiddlewareTest() - { - Url = "http://localhost:51879"; - ScopedRepository = new Mock(); - } - - protected virtual void GivenTheTestServerIsConfigured() - { - var builder = new WebHostBuilder() - .ConfigureServices(x => GivenTheTestServerServicesAreConfigured(x)) - .UseUrls(Url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => GivenTheTestServerPipelineIsConfigured(app)); - - Server = new TestServer(builder); - Client = Server.CreateClient(); - } - - protected virtual void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - // override this in your test fixture to set up service dependencies - } - - protected virtual void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - // override this in your test fixture to set up the test server pipeline - } - - protected void WhenICallTheMiddleware() - { - ResponseMessage = Client.GetAsync(Url).Result; - } - - protected void WhenICallTheMiddlewareWithTheRequestIdKey(string requestIdKey, string value) - { - Client.DefaultRequestHeaders.Add(requestIdKey, value); - ResponseMessage = Client.GetAsync(Url).Result; - } - - public void Dispose() - { - Client.Dispose(); - Server.Dispose(); - } - } -}