diff --git a/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs index 5f6dbe08..17f46531 100644 --- a/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Threading.Tasks; using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.Configuration.Repository; @@ -16,9 +17,9 @@ namespace Ocelot.Configuration.Provider _repo = repo; } - public Response Get() + public async Task> Get() { - var fileConfig = _repo.Get(); + var fileConfig = await _repo.Get(); return new OkResponse(fileConfig.Data); } } diff --git a/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs index 3a4dca14..39afedba 100644 --- a/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using Ocelot.Configuration.File; using Ocelot.Responses; @@ -5,6 +6,6 @@ namespace Ocelot.Configuration.Provider { public interface IFileConfigurationProvider { - Response Get(); + Task> Get(); } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs index 3256e44a..9b89bcea 100644 --- a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Ocelot.Configuration.File; using Ocelot.Responses; namespace Ocelot.Configuration.Provider diff --git a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs index b03bb9af..31ecb8ac 100644 --- a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Ocelot.Configuration.File; using Ocelot.Configuration.Repository; using Ocelot.Responses; @@ -9,16 +10,16 @@ namespace Ocelot.Configuration.Provider /// public class OcelotConfigurationProvider : IOcelotConfigurationProvider { - private readonly IOcelotConfigurationRepository _repo; + private readonly IOcelotConfigurationRepository _config; public OcelotConfigurationProvider(IOcelotConfigurationRepository repo) { - _repo = repo; + _config = repo; } public async Task> Get() { - var repoConfig = await _repo.Get(); + var repoConfig = await _config.Get(); if (repoConfig.IsError) { diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs new file mode 100644 index 00000000..50314802 --- /dev/null +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs @@ -0,0 +1,80 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Setter; +using Ocelot.Logging; + +namespace Ocelot.Configuration.Repository +{ + public class ConsulFileConfigurationPoller : IDisposable + { + private IOcelotLogger _logger; + private IFileConfigurationRepository _repo; + private IFileConfigurationSetter _setter; + private string _previousAsJson; + private Timer _timer; + private bool _polling; + + public ConsulFileConfigurationPoller(IOcelotLoggerFactory factory, IFileConfigurationRepository repo, IFileConfigurationSetter setter) + { + _setter = setter; + _logger = factory.CreateLogger(); + _repo = repo; + _previousAsJson = ""; + _timer = new Timer(async x => + { + if(_polling) + { + return; + } + + _polling = true; + await Poll(); + _polling = false; + + }, null, 0, 1000); + } + + private async Task Poll() + { + _logger.LogDebug("Started polling consul"); + + var fileConfig = await _repo.Get(); + + if(fileConfig.IsError) + { + _logger.LogDebug($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}"); + return; + } + + var asJson = ToJson(fileConfig.Data); + + if(!fileConfig.IsError && asJson != _previousAsJson) + { + await _setter.Set(fileConfig.Data); + _previousAsJson = asJson; + } + + _logger.LogDebug("Finished polling consul"); + } + + /// + /// We could do object comparison here but performance isnt really a problem. This might be an issue one day! + /// + /// + /// + private string ToJson(FileConfiguration config) + { + var currentHash = JsonConvert.SerializeObject(config); + return currentHash; + } + + public void Dispose() + { + _timer.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs similarity index 68% rename from src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs rename to src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs index c7de98d2..b89de3d8 100644 --- a/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs @@ -3,19 +3,20 @@ using System.Text; using System.Threading.Tasks; using Consul; using Newtonsoft.Json; +using Ocelot.Configuration.File; using Ocelot.Responses; using Ocelot.ServiceDiscovery; namespace Ocelot.Configuration.Repository { - public class ConsulOcelotConfigurationRepository : IOcelotConfigurationRepository + + public class ConsulFileConfigurationRepository : IFileConfigurationRepository { private readonly ConsulClient _consul; private string _ocelotConfiguration = "OcelotConfiguration"; - private readonly Cache.IOcelotCache _cache; + private readonly Cache.IOcelotCache _cache; - - public ConsulOcelotConfigurationRepository(Cache.IOcelotCache cache, ServiceProviderConfiguration serviceProviderConfig) + public ConsulFileConfigurationRepository(Cache.IOcelotCache cache, ServiceProviderConfiguration serviceProviderConfig) { var consulHost = string.IsNullOrEmpty(serviceProviderConfig?.ServiceProviderHost) ? "localhost" : serviceProviderConfig?.ServiceProviderHost; var consulPort = serviceProviderConfig?.ServiceProviderPort ?? 8500; @@ -27,32 +28,32 @@ namespace Ocelot.Configuration.Repository }); } - public async Task> Get() + public async Task> Get() { var config = _cache.Get(_ocelotConfiguration, _ocelotConfiguration); if (config != null) { - return new OkResponse(config); + return new OkResponse(config); } var queryResult = await _consul.KV.Get(_ocelotConfiguration); if (queryResult.Response == null) { - return new OkResponse(null); + return new OkResponse(null); } var bytes = queryResult.Response.Value; var json = Encoding.UTF8.GetString(bytes); - var consulConfig = JsonConvert.DeserializeObject(json); + var consulConfig = JsonConvert.DeserializeObject(json); - return new OkResponse(consulConfig); + return new OkResponse(consulConfig); } - public async Task AddOrReplace(IOcelotConfiguration ocelotConfiguration) + public async Task Set(FileConfiguration ocelotConfiguration) { var json = JsonConvert.SerializeObject(ocelotConfiguration); @@ -72,7 +73,7 @@ namespace Ocelot.Configuration.Repository return new OkResponse(); } - return new ErrorResponse(new UnableToSetConfigInConsulError("Unable to set config in consul")); + return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}")); } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs index f32dcaec..c7614c30 100644 --- a/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.Responses; @@ -8,7 +9,7 @@ namespace Ocelot.Configuration.Repository public class FileConfigurationRepository : IFileConfigurationRepository { private static readonly object _lock = new object(); - public Response Get() + public async Task> Get() { var configFilePath = $"{AppContext.BaseDirectory}/configuration.json"; string json = string.Empty; @@ -20,7 +21,7 @@ namespace Ocelot.Configuration.Repository return new OkResponse(fileConfiguration); } - public Response Set(FileConfiguration fileConfiguration) + public async Task Set(FileConfiguration fileConfiguration) { var configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; diff --git a/src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs index 26726b46..60d428fb 100644 --- a/src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using Ocelot.Configuration.File; using Ocelot.Responses; @@ -5,7 +6,7 @@ namespace Ocelot.Configuration.Repository { public interface IFileConfigurationRepository { - Response Get(); - Response Set(FileConfiguration fileConfiguration); + Task> Get(); + Task Set(FileConfiguration fileConfiguration); } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs index 4ee56d43..ff720464 100644 --- a/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Ocelot.Configuration.File; using Ocelot.Responses; namespace Ocelot.Configuration.Repository diff --git a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs index 163dd5f5..453bb92f 100644 --- a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs +++ b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs @@ -22,7 +22,7 @@ namespace Ocelot.Configuration.Setter public async Task Set(FileConfiguration fileConfig) { - var response = _repo.Set(fileConfig); + var response = await _repo.Set(fileConfig); if(response.IsError) { diff --git a/src/Ocelot/Controllers/FileConfigurationController.cs b/src/Ocelot/Controllers/FileConfigurationController.cs index 5e36c64b..c0ba43ea 100644 --- a/src/Ocelot/Controllers/FileConfigurationController.cs +++ b/src/Ocelot/Controllers/FileConfigurationController.cs @@ -21,9 +21,9 @@ namespace Ocelot.Controllers } [HttpGet] - public IActionResult Get() + public async Task Get() { - var response = _configGetter.Get(); + var response = await _configGetter.Get(); if(response.IsError) { diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 32e29c04..2d2a90a5 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -63,7 +63,8 @@ namespace Ocelot.DependencyInjection .Build(); services.AddSingleton(config); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); return services; } @@ -90,6 +91,11 @@ namespace Ocelot.DependencyInjection services.TryAddSingleton>(ocelotConfigCacheManagerOutputCache); services.TryAddSingleton>(ocelotConfigCacheManager); + var fileConfigCacheManagerOutputCache = CacheFactory.Build("FileConfigurationCache", settings); + var fileConfigCacheManager = new OcelotCacheManagerCache(fileConfigCacheManagerOutputCache); + services.TryAddSingleton>(fileConfigCacheManagerOutputCache); + services.TryAddSingleton>(fileConfigCacheManager); + services.Configure(configurationRoot); services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs b/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs index 0f15ca7e..b589fb47 100644 --- a/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs +++ b/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs @@ -35,6 +35,14 @@ namespace Ocelot.Infrastructure.RequestData { object obj; + if(_httpContextAccessor.HttpContext == null || _httpContextAccessor.HttpContext.Items == null) + { + return new ErrorResponse(new List + { + new CannotFindDataError($"Unable to find data for key: {key} because HttpContext or HttpContext.Items is null") + }); + } + if(_httpContextAccessor.HttpContext.Items.TryGetValue(key, out obj)) { var data = (T) obj; diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index cef4307b..c89a5b4e 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -29,10 +29,13 @@ namespace Ocelot.Middleware using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Ocelot.Configuration; + using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; using Ocelot.Configuration.Provider; + using Ocelot.Configuration.Repository; using Ocelot.Configuration.Setter; using Ocelot.LoadBalancer.Middleware; + using Ocelot.Responses; public static class OcelotMiddlewareExtensions { @@ -56,7 +59,9 @@ namespace Ocelot.Middleware /// public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) { - await CreateAdministrationArea(builder); + var configuration = await CreateConfiguration(builder); + + await CreateAdministrationArea(builder, configuration); ConfigureDiagnosticListener(builder); @@ -155,7 +160,34 @@ namespace Ocelot.Middleware if (ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError) { - var config = await configSetter.Set(fileConfig.Value); + Response config = null; + var fileConfigRepo = builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); + if (fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository)) + { + var consulFileConfigRepo = (ConsulFileConfigurationRepository) fileConfigRepo; + var ocelotConfigurationRepository = + (IOcelotConfigurationRepository) builder.ApplicationServices.GetService( + typeof(IOcelotConfigurationRepository)); + var ocelotConfigurationCreator = + (IOcelotConfigurationCreator) builder.ApplicationServices.GetService( + typeof(IOcelotConfigurationCreator)); + + var fileConfigFromConsul = await consulFileConfigRepo.Get(); + if (fileConfigFromConsul.Data == null) + { + config = await configSetter.Set(fileConfig.Value); + } + else + { + var ocelotConfig = await ocelotConfigurationCreator.Create(fileConfigFromConsul.Data); + config = await ocelotConfigurationRepository.AddOrReplace(ocelotConfig.Data); + var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); + } + } + else + { + config = await configSetter.Set(fileConfig.Value); + } if (config == null || config.IsError) { @@ -173,10 +205,8 @@ namespace Ocelot.Middleware return ocelotConfiguration.Data; } - private static async Task CreateAdministrationArea(IApplicationBuilder builder) + private static async Task CreateAdministrationArea(IApplicationBuilder builder, IOcelotConfiguration configuration) { - var configuration = await CreateConfiguration(builder); - var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration)); if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null) diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index aca07a3c..239e1035 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Net; using System.Text; +using System.Threading; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -20,7 +22,7 @@ namespace Ocelot.AcceptanceTests private IWebHost _builder; private readonly Steps _steps; private IWebHost _fakeConsulBuilder; - private IOcelotConfiguration _config; + private FileConfiguration _config; public ConfigurationInConsul() { @@ -67,7 +69,7 @@ namespace Ocelot.AcceptanceTests } [Fact] - public void should_fix_issue_142() + public void should_load_configuration_out_of_consul() { var consulPort = 8500; var configuration = new FileConfiguration @@ -84,28 +86,31 @@ namespace Ocelot.AcceptanceTests var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceProviderConfig = new ServiceProviderConfigurationBuilder() - .WithServiceDiscoveryProviderHost("localhost") - .WithServiceDiscoveryProviderPort(consulPort) - .Build(); + var consulConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/status", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51779, + UpstreamPathTemplate = "/cs/status", + UpstreamHttpMethod = new List {"Get"} + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; - var reRoute = new ReRouteBuilder() - .WithDownstreamPathTemplate("/status") - .WithUpstreamTemplatePattern("^(?i)/cs/status/$") - .WithDownstreamScheme("http") - .WithDownstreamHost("localhost") - .WithDownstreamPort(51779) - .WithUpstreamPathTemplate("/cs/status") - .WithUpstreamHttpMethod(new List {"Get"}) - .WithReRouteKey("/cs/status|Get") - .WithHttpHandlerOptions(new HttpHandlerOptions(true, false)) - .Build(); - - var reRoutes = new List { reRoute }; - - var config = new OcelotConfiguration(reRoutes, null, serviceProviderConfig); - - this.Given(x => GivenTheConsulConfigurationIs(config)) + this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) @@ -116,7 +121,95 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } - private void GivenTheConsulConfigurationIs(OcelotConfiguration config) + + [Fact] + public void should_load_configuration_out_of_consul_if_it_is_changed() + { + var consulPort = 8506; + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; + + var consulConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/status", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51779, + UpstreamPathTemplate = "/cs/status", + UpstreamHttpMethod = new List {"Get"} + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + var secondConsulConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/status", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51779, + UpstreamPathTemplate = "/cs/status/awesome", + UpstreamHttpMethod = new List {"Get"} + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) + .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) + .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")) + .BDDfy(); + } + + private void GivenIWaitForTheConfigToReplicateToOcelot() + { + Thread.Sleep(10000); + } + + private void GivenTheConsulConfigurationIs(FileConfiguration config) { _config = config; } @@ -154,7 +247,7 @@ namespace Ocelot.AcceptanceTests var json = reader.ReadToEnd(); - _config = JsonConvert.DeserializeObject(json); + _config = JsonConvert.DeserializeObject(json); var response = JsonConvert.SerializeObject(true); diff --git a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs b/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs new file mode 100644 index 00000000..5569448d --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Moq; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; +using Ocelot.Configuration.Setter; +using Ocelot.Logging; +using Ocelot.Responses; +using TestStack.BDDfy; +using Xunit; +using Shouldly; +using static Ocelot.UnitTests.Wait; + + +namespace Ocelot.UnitTests.Configuration +{ + public class ConsulFileConfigurationPollerTests : IDisposable + { + private ConsulFileConfigurationPoller _poller; + private Mock _factory; + private Mock _repo; + private Mock _setter; + private FileConfiguration _fileConfig; + + public ConsulFileConfigurationPollerTests() + { + var logger = new Mock(); + _factory = new Mock(); + _factory.Setup(x => x.CreateLogger()).Returns(logger.Object); + _repo = new Mock(); + _setter = new Mock(); + _fileConfig = new FileConfiguration(); + _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse(_fileConfig)); + _poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object); + } + public void Dispose() + { + _poller.Dispose(); + } + + [Fact] + public void should_start() + { + this.Given(x => ThenTheSetterIsCalled(_fileConfig)) + .BDDfy(); + } + + [Fact] + public void should_call_setter_when_gets_new_config() + { + + var newConfig = new FileConfiguration { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHost = "test" + } + } + }; + + this.Given(x => WhenTheConfigIsChangedInConsul(newConfig)) + .Then(x => ThenTheSetterIsCalled(newConfig)) + .BDDfy(); + } + + private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig) + { + _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse(newConfig)); + } + + private void ThenTheSetterIsCalled(FileConfiguration fileConfig) + { + var result = WaitFor(2000).Until(() => { + try + { + _setter.Verify(x => x.Set(fileConfig), Times.Once); + return true; + } + catch(Exception ex) + { + return false; + } + }); + result.ShouldBeTrue(); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs index a1ea4af7..39b3125f 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs @@ -45,12 +45,12 @@ namespace Ocelot.UnitTests.Configuration _fileConfiguration = fileConfiguration; _repo .Setup(x => x.Get()) - .Returns(new OkResponse(fileConfiguration)); + .ReturnsAsync(new OkResponse(fileConfiguration)); } private void WhenIGetTheReRoutes() { - _result = _provider.Get().Data; + _result = _provider.Get().Result.Data; } private void ThenTheRepoIsCalledCorrectly() diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs index 33e95910..13a078b2 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs @@ -100,7 +100,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenISetTheConfiguration() { _repo.Set(_fileConfiguration); - _result = _repo.Get().Data; + _result = _repo.Get().Result.Data; } private void ThenTheConfigurationIsStoredAs(FileConfiguration expected) @@ -135,7 +135,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenIGetTheReRoutes() { - _result = _repo.Get().Data; + _result = _repo.Get().Result.Data; } private void ThenTheFollowingIsReturned(FileConfiguration expected) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs index 50ab0bc8..14c71b49 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs @@ -78,7 +78,7 @@ namespace Ocelot.UnitTests.Configuration { _repo .Setup(x => x.Set(It.IsAny())) - .Returns(response); + .ReturnsAsync(response); } private void ThenAnErrorResponseIsReturned() diff --git a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs index 5e041e7e..c54c5ba2 100644 --- a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -107,12 +107,12 @@ namespace Ocelot.UnitTests.Controllers { _configGetter .Setup(x => x.Get()) - .Returns(fileConfiguration); + .ReturnsAsync(fileConfiguration); } private void WhenIGetTheFileConfiguration() { - _result = _controller.Get(); + _result = _controller.Get().Result; } private void TheTheGetFileConfigurationIsCalledCorrectly() diff --git a/test/Ocelot.UnitTests/DependencyInjection/ServiceCollectionExtensionTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ServiceCollectionExtensionTests.cs new file mode 100644 index 00000000..a1eca21f --- /dev/null +++ b/test/Ocelot.UnitTests/DependencyInjection/ServiceCollectionExtensionTests.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.DependencyInjection; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DependencyInjection +{ + public class ServiceCollectionExtensionTests + { + private Exception _ex; + + [Fact] + public void should_set_up_services() + { + this.When(x => WhenISetUpOcelotServices()) + .Then(x => ThenAnExceptionIsntThrown()) + .BDDfy(); + } + + private void WhenISetUpOcelotServices() + { + try + { + IWebHostBuilder builder = new WebHostBuilder(); + IConfigurationRoot configRoot = new ConfigurationRoot(new List()); + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(builder); + services.AddOcelot(configRoot); + } + catch (Exception e) + { + _ex = e; + } + } + + private void ThenAnExceptionIsntThrown() + { + _ex.ShouldBeNull(); + } + } +} diff --git a/test/Ocelot.UnitTests/Waiter.cs b/test/Ocelot.UnitTests/Waiter.cs new file mode 100644 index 00000000..d76a0109 --- /dev/null +++ b/test/Ocelot.UnitTests/Waiter.cs @@ -0,0 +1,55 @@ +using System; +using System.Diagnostics; + +namespace Ocelot.UnitTests +{ + public class Wait + { + public static Waiter WaitFor(int milliSeconds) + { + return new Waiter(milliSeconds); + } + } + + 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; + } + } +} \ No newline at end of file