From 0f2cf2d188f1404d3a84973a3b8b4db5a4176e18 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 29 Jul 2018 18:23:49 +0100 Subject: [PATCH] =?UTF-8?q?Made=20the=20file=20config=20poller=20use=20IHo?= =?UTF-8?q?stedService,=20bit=20more=20generic,=20not=E2=80=A6=20(#507)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Made the file config poller use IHostedService, bit more generic, not just need to provide the correct implementations of the repo services and it will poll anything..this means we can open up redis for #458 * removed comments --- ...onPoller.cs => FileConfigurationPoller.cs} | 196 +++-- .../Repository/IConsulPollerConfiguration.cs | 2 +- ...InMemoryFileConfigurationPollerOptions.cs} | 2 +- .../FileAndInternalConfigurationSetter.cs | 6 +- .../DependencyInjection/OcelotBuilder.cs | 4 +- .../Middleware/OcelotMiddlewareExtensions.cs | 523 ++++++------ .../ConfigurationInConsulTests.cs | 781 +++++++++--------- ...sts.cs => FileConfigurationPollerTests.cs} | 47 +- 8 files changed, 798 insertions(+), 763 deletions(-) rename src/Ocelot/Configuration/Repository/{ConsulFileConfigurationPoller.cs => FileConfigurationPoller.cs} (55%) rename src/Ocelot/Configuration/Repository/{InMemoryConsulPollerConfiguration.cs => InMemoryFileConfigurationPollerOptions.cs} (50%) rename test/Ocelot.UnitTests/Configuration/{ConsulFileConfigurationPollerTests.cs => FileConfigurationPollerTests.cs} (73%) diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs b/src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs similarity index 55% rename from src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs rename to src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs index 8dabeaf28..74bbf0022 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs +++ b/src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs @@ -1,84 +1,112 @@ -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 readonly IOcelotLogger _logger; - private readonly IFileConfigurationRepository _repo; - private readonly IFileConfigurationSetter _setter; - private string _previousAsJson; - private readonly Timer _timer; - private bool _polling; - private readonly IConsulPollerConfiguration _config; - - public ConsulFileConfigurationPoller( - IOcelotLoggerFactory factory, - IFileConfigurationRepository repo, - IFileConfigurationSetter setter, - IConsulPollerConfiguration config) - { - _setter = setter; - _config = config; - _logger = factory.CreateLogger(); - _repo = repo; - _previousAsJson = ""; - _timer = new Timer(async x => - { - if(_polling) - { - return; - } - - _polling = true; - await Poll(); - _polling = false; - }, null, _config.Delay, _config.Delay); - } - - private async Task Poll() - { - _logger.LogInformation("Started polling consul"); - - var fileConfig = await _repo.Get(); - - if(fileConfig.IsError) - { - _logger.LogWarning($"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.LogInformation("Finished polling consul"); - } - - /// - /// 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); - return currentHash; - } - - public void Dispose() - { - _timer.Dispose(); - } - } -} +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Newtonsoft.Json; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Setter; +using Ocelot.Logging; + +namespace Ocelot.Configuration.Repository +{ + public class FileConfigurationPoller : IHostedService, IDisposable + { + private readonly IOcelotLogger _logger; + private readonly IFileConfigurationRepository _repo; + private string _previousAsJson; + private Timer _timer; + private bool _polling; + private readonly IFileConfigurationPollerOptions _options; + private readonly IInternalConfigurationRepository _internalConfigRepo; + private readonly IInternalConfigurationCreator _internalConfigCreator; + + public FileConfigurationPoller( + IOcelotLoggerFactory factory, + IFileConfigurationRepository repo, + IFileConfigurationPollerOptions options, + IInternalConfigurationRepository internalConfigRepo, + IInternalConfigurationCreator internalConfigCreator) + { + _internalConfigRepo = internalConfigRepo; + _internalConfigCreator = internalConfigCreator; + _options = options; + _logger = factory.CreateLogger(); + _repo = repo; + _previousAsJson = ""; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation($"{nameof(FileConfigurationPoller)} is starting."); + + _timer = new Timer(async x => + { + if(_polling) + { + return; + } + + _polling = true; + await Poll(); + _polling = false; + }, null, _options.Delay, _options.Delay); + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogInformation($"{nameof(FileConfigurationPoller)} is stopping."); + + _timer?.Change(Timeout.Infinite, 0); + + return Task.CompletedTask; + } + + private async Task Poll() + { + _logger.LogInformation("Started polling consul"); + + var fileConfig = await _repo.Get(); + + if(fileConfig.IsError) + { + _logger.LogWarning($"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) + { + var config = await _internalConfigCreator.Create(fileConfig.Data); + + if(!config.IsError) + { + _internalConfigRepo.AddOrReplace(config.Data); + } + + _previousAsJson = asJson; + } + + _logger.LogInformation("Finished polling consul"); + } + + /// + /// 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); + return currentHash; + } + + public void Dispose() + { + _timer.Dispose(); + } + } +} diff --git a/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs b/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs index d1f1430d0..70fc9bd4f 100644 --- a/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs +++ b/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs @@ -1,6 +1,6 @@ namespace Ocelot.Configuration.Repository { - public interface IConsulPollerConfiguration + public interface IFileConfigurationPollerOptions { int Delay { get; } } diff --git a/src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs b/src/Ocelot/Configuration/Repository/InMemoryFileConfigurationPollerOptions.cs similarity index 50% rename from src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs rename to src/Ocelot/Configuration/Repository/InMemoryFileConfigurationPollerOptions.cs index 9e411f76f..f5ebe1863 100644 --- a/src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs +++ b/src/Ocelot/Configuration/Repository/InMemoryFileConfigurationPollerOptions.cs @@ -1,6 +1,6 @@ namespace Ocelot.Configuration.Repository { - public class InMemoryConsulPollerConfiguration : IConsulPollerConfiguration + public class InMemoryFileConfigurationPollerOptions : IFileConfigurationPollerOptions { public int Delay => 1000; } diff --git a/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs index 6821553e6..a5549b292 100644 --- a/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs +++ b/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs @@ -8,7 +8,7 @@ namespace Ocelot.Configuration.Setter { public class FileAndInternalConfigurationSetter : IFileConfigurationSetter { - private readonly IInternalConfigurationRepository _configRepo; + private readonly IInternalConfigurationRepository internalConfigRepo; private readonly IInternalConfigurationCreator _configCreator; private readonly IFileConfigurationRepository _repo; @@ -17,7 +17,7 @@ public FileAndInternalConfigurationSetter( IInternalConfigurationCreator configCreator, IFileConfigurationRepository repo) { - _configRepo = configRepo; + internalConfigRepo = configRepo; _configCreator = configCreator; _repo = repo; } @@ -35,7 +35,7 @@ public async Task Set(FileConfiguration fileConfig) if(!config.IsError) { - _configRepo.AddOrReplace(config.Data); + internalConfigRepo.AddOrReplace(config.Data); } return new ErrorResponse(config.Errors); diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 72064a9dd..07e623588 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -160,7 +160,7 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo // We add this here so that we can always inject something into the factory for IoC.. _services.AddSingleton(); - _services.TryAddSingleton(); + _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); @@ -245,7 +245,7 @@ public IOcelotBuilder AddOpenTracing(Action settings) public IOcelotBuilder AddStoreOcelotConfigurationInConsul() { - _services.AddSingleton(); + _services.AddHostedService(); _services.AddSingleton(); return this; } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index fecf749f5..d5421b12d 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -1,266 +1,263 @@ -namespace Ocelot.Middleware -{ - using System; - using System.Linq; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.Options; - using System.Diagnostics; - using DependencyInjection; - using Microsoft.AspNetCore.Builder; - using Ocelot.Configuration; - using Ocelot.Configuration.Creator; - using Ocelot.Configuration.File; - using Ocelot.Configuration.Repository; - using Ocelot.Configuration.Setter; - using Ocelot.Responses; - using Ocelot.Logging; - using Rafty.Concensus; - using Rafty.Infrastructure; - using Ocelot.Middleware.Pipeline; - using Pivotal.Discovery.Client; - using Rafty.Concensus.Node; - - public static class OcelotMiddlewareExtensions - { - public static async Task UseOcelot(this IApplicationBuilder builder) - { - await builder.UseOcelot(new OcelotPipelineConfiguration()); - - return builder; - } - - public static async Task UseOcelot(this IApplicationBuilder builder, Action pipelineConfiguration) - { - var config = new OcelotPipelineConfiguration(); - pipelineConfiguration?.Invoke(config); - return await builder.UseOcelot(config); - } - public static async Task UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) - { - var configuration = await CreateConfiguration(builder); - - CreateAdministrationArea(builder, configuration); - - if (UsingRafty(builder)) - { - SetUpRafty(builder); - } - - if (UsingEurekaServiceDiscoveryProvider(configuration)) - { - builder.UseDiscoveryClient(); - } - - ConfigureDiagnosticListener(builder); - - var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); - - pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration); - - var firstDelegate = pipelineBuilder.Build(); - - /* - inject first delegate into first piece of asp.net middleware..maybe not like this - then because we are updating the http context in ocelot it comes out correct for - rest of asp.net.. - */ - - builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware"; - - builder.Use(async (context, task) => - { - var downstreamContext = new DownstreamContext(context); - await firstDelegate.Invoke(downstreamContext); - }); - - return builder; - } - - private static bool UsingEurekaServiceDiscoveryProvider(IInternalConfiguration configuration) - { - return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "eureka"; - } - - private static bool UsingRafty(IApplicationBuilder builder) - { - var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode; - if (possible != null) - { - return true; - } - - return false; - } - - private static void SetUpRafty(IApplicationBuilder builder) - { - var applicationLifetime = (IApplicationLifetime)builder.ApplicationServices.GetService(typeof(IApplicationLifetime)); - applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder)); - var node = (INode)builder.ApplicationServices.GetService(typeof(INode)); - var nodeId = (NodeId)builder.ApplicationServices.GetService(typeof(NodeId)); - node.Start(nodeId); - } - - private static async Task CreateConfiguration(IApplicationBuilder builder) - { - // make configuration from file system? - // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this - var fileConfig = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); - - // now create the config - var internalConfigCreator = (IInternalConfigurationCreator)builder.ApplicationServices.GetService(typeof(IInternalConfigurationCreator)); - var internalConfig = await internalConfigCreator.Create(fileConfig.Value); - - // now save it in memory - var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository)); - internalConfigRepo.AddOrReplace(internalConfig.Data); - - var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); - - var adminPath = (IAdministrationPath)builder.ApplicationServices.GetService(typeof(IAdministrationPath)); - - if (UsingConsul(fileConfigRepo)) +namespace Ocelot.Middleware +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Options; + using System.Diagnostics; + using DependencyInjection; + using Microsoft.AspNetCore.Builder; + using Ocelot.Configuration; + using Ocelot.Configuration.Creator; + using Ocelot.Configuration.File; + using Ocelot.Configuration.Repository; + using Ocelot.Configuration.Setter; + using Ocelot.Responses; + using Ocelot.Logging; + using Rafty.Concensus; + using Rafty.Infrastructure; + using Ocelot.Middleware.Pipeline; + using Pivotal.Discovery.Client; + using Rafty.Concensus.Node; + + public static class OcelotMiddlewareExtensions + { + public static async Task UseOcelot(this IApplicationBuilder builder) + { + await builder.UseOcelot(new OcelotPipelineConfiguration()); + + return builder; + } + + public static async Task UseOcelot(this IApplicationBuilder builder, Action pipelineConfiguration) + { + var config = new OcelotPipelineConfiguration(); + pipelineConfiguration?.Invoke(config); + return await builder.UseOcelot(config); + } + public static async Task UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) + { + var configuration = await CreateConfiguration(builder); + + CreateAdministrationArea(builder, configuration); + + if (UsingRafty(builder)) { - //Lots of jazz happens in here..check it out if you are using consul to store your config. - await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo); - } - else if(AdministrationApiInUse(adminPath)) + SetUpRafty(builder); + } + + if (UsingEurekaServiceDiscoveryProvider(configuration)) + { + builder.UseDiscoveryClient(); + } + + ConfigureDiagnosticListener(builder); + + var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); + + pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration); + + var firstDelegate = pipelineBuilder.Build(); + + /* + inject first delegate into first piece of asp.net middleware..maybe not like this + then because we are updating the http context in ocelot it comes out correct for + rest of asp.net.. + */ + + builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware"; + + builder.Use(async (context, task) => + { + var downstreamContext = new DownstreamContext(context); + await firstDelegate.Invoke(downstreamContext); + }); + + return builder; + } + + private static bool UsingEurekaServiceDiscoveryProvider(IInternalConfiguration configuration) + { + return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "eureka"; + } + + private static bool UsingRafty(IApplicationBuilder builder) + { + var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode; + if (possible != null) + { + return true; + } + + return false; + } + + private static void SetUpRafty(IApplicationBuilder builder) + { + var applicationLifetime = (IApplicationLifetime)builder.ApplicationServices.GetService(typeof(IApplicationLifetime)); + applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder)); + var node = (INode)builder.ApplicationServices.GetService(typeof(INode)); + var nodeId = (NodeId)builder.ApplicationServices.GetService(typeof(NodeId)); + node.Start(nodeId); + } + + private static async Task CreateConfiguration(IApplicationBuilder builder) + { + // make configuration from file system? + // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this + var fileConfig = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); + + // now create the config + var internalConfigCreator = (IInternalConfigurationCreator)builder.ApplicationServices.GetService(typeof(IInternalConfigurationCreator)); + var internalConfig = await internalConfigCreator.Create(fileConfig.Value); + + // now save it in memory + var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository)); + internalConfigRepo.AddOrReplace(internalConfig.Data); + + var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); + + var adminPath = (IAdministrationPath)builder.ApplicationServices.GetService(typeof(IAdministrationPath)); + + if (UsingConsul(fileConfigRepo)) + { + //Lots of jazz happens in here..check it out if you are using consul to store your config. + await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo); + } + else if(AdministrationApiInUse(adminPath)) { //We have to make sure the file config is set for the ocelot.env.json and ocelot.json so that if we pull it from the - //admin api it works...boy this is getting a spit spags boll. - var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); - - await SetFileConfig(fileConfigSetter, fileConfig); - } - - return GetOcelotConfigAndReturn(internalConfigRepo); - } - - private static bool AdministrationApiInUse(IAdministrationPath adminPath) - { - return adminPath.GetType() != typeof(NullAdministrationPath); - } - - private static async Task SetFileConfigInConsul(IApplicationBuilder builder, - IFileConfigurationRepository fileConfigRepo, IOptions fileConfig, - IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo) - { - // get the config from consul. - var fileConfigFromConsul = await fileConfigRepo.Get(); - - if (IsError(fileConfigFromConsul)) - { - ThrowToStopOcelotStarting(fileConfigFromConsul); - } - else if (ConfigNotStoredInConsul(fileConfigFromConsul)) - { - //there was no config in consul set the file in config in consul - await fileConfigRepo.Set(fileConfig.Value); - } - else - { - // create the internal config from consul data - var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data); - - if (IsError(internalConfig)) - { - ThrowToStopOcelotStarting(internalConfig); - } - else - { - // add the internal config to the internal repo - var response = internalConfigRepo.AddOrReplace(internalConfig.Data); - - if (IsError(response)) - { - ThrowToStopOcelotStarting(response); - } - } - - if (IsError(internalConfig)) - { - ThrowToStopOcelotStarting(internalConfig); - } - } - - //todo - this starts the poller if it has been registered...please this is so bad. - var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); - } - - private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions fileConfig) - { - var response = await fileConfigSetter.Set(fileConfig.Value); - - if (IsError(response)) - { - ThrowToStopOcelotStarting(response); - } - } - - private static bool ConfigNotStoredInConsul(Responses.Response fileConfigFromConsul) - { - return fileConfigFromConsul.Data == null; - } - - private static bool IsError(Response response) - { - return response == null || response.IsError; - } - - private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider) - { - var ocelotConfiguration = provider.Get(); - - if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError) - { - ThrowToStopOcelotStarting(ocelotConfiguration); - } - - return ocelotConfiguration.Data; - } - - private static void ThrowToStopOcelotStarting(Response config) - { - throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}"); - } - - private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo) - { - return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository); - } - - private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration) - { - if (!string.IsNullOrEmpty(configuration.AdministrationPath)) - { - builder.Map(configuration.AdministrationPath, app => - { - //todo - hack so we know that we are using internal identity server - var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration)); - if (identityServerConfiguration != null) - { - app.UseIdentityServer(); - } - - app.UseAuthentication(); - app.UseMvc(); - }); - } - } - - private static void ConfigureDiagnosticListener(IApplicationBuilder builder) - { - var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment)); - var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener)); - var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener)); - diagnosticListener.SubscribeWithAdapter(listener); - } - - private static void OnShutdown(IApplicationBuilder app) - { - var node = (INode)app.ApplicationServices.GetService(typeof(INode)); - node.Stop(); - } - } -} + //admin api it works...boy this is getting a spit spags boll. + var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); + + await SetFileConfig(fileConfigSetter, fileConfig); + } + + return GetOcelotConfigAndReturn(internalConfigRepo); + } + + private static bool AdministrationApiInUse(IAdministrationPath adminPath) + { + return adminPath.GetType() != typeof(NullAdministrationPath); + } + + private static async Task SetFileConfigInConsul(IApplicationBuilder builder, + IFileConfigurationRepository fileConfigRepo, IOptions fileConfig, + IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo) + { + // get the config from consul. + var fileConfigFromConsul = await fileConfigRepo.Get(); + + if (IsError(fileConfigFromConsul)) + { + ThrowToStopOcelotStarting(fileConfigFromConsul); + } + else if (ConfigNotStoredInConsul(fileConfigFromConsul)) + { + //there was no config in consul set the file in config in consul + await fileConfigRepo.Set(fileConfig.Value); + } + else + { + // create the internal config from consul data + var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data); + + if (IsError(internalConfig)) + { + ThrowToStopOcelotStarting(internalConfig); + } + else + { + // add the internal config to the internal repo + var response = internalConfigRepo.AddOrReplace(internalConfig.Data); + + if (IsError(response)) + { + ThrowToStopOcelotStarting(response); + } + } + + if (IsError(internalConfig)) + { + ThrowToStopOcelotStarting(internalConfig); + } + } + } + + private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions fileConfig) + { + var response = await fileConfigSetter.Set(fileConfig.Value); + + if (IsError(response)) + { + ThrowToStopOcelotStarting(response); + } + } + + private static bool ConfigNotStoredInConsul(Responses.Response fileConfigFromConsul) + { + return fileConfigFromConsul.Data == null; + } + + private static bool IsError(Response response) + { + return response == null || response.IsError; + } + + private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider) + { + var ocelotConfiguration = provider.Get(); + + if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError) + { + ThrowToStopOcelotStarting(ocelotConfiguration); + } + + return ocelotConfiguration.Data; + } + + private static void ThrowToStopOcelotStarting(Response config) + { + throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}"); + } + + private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo) + { + return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository); + } + + private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration) + { + if (!string.IsNullOrEmpty(configuration.AdministrationPath)) + { + builder.Map(configuration.AdministrationPath, app => + { + //todo - hack so we know that we are using internal identity server + var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration)); + if (identityServerConfiguration != null) + { + app.UseIdentityServer(); + } + + app.UseAuthentication(); + app.UseMvc(); + }); + } + } + + private static void ConfigureDiagnosticListener(IApplicationBuilder builder) + { + var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment)); + var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener)); + var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener)); + diagnosticListener.SubscribeWithAdapter(listener); + } + + private static void OnShutdown(IApplicationBuilder app) + { + var node = (INode)app.ApplicationServices.GetService(typeof(INode)); + node.Stop(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index 5a5cce822..af99888dc 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -1,390 +1,391 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Text; -using Microsoft.AspNetCore.Builder; -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 -{ - public class ConfigurationInConsulTests : IDisposable - { - private IWebHost _builder; - private readonly Steps _steps; - private IWebHost _fakeConsulBuilder; - private FileConfiguration _config; - - public ConfigurationInConsulTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_return_response_200_with_simple_url() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51779, - } - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = 9500 - } - } - }; - - var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_simple_url_when_using_jsonserialized_cache() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51779, - } - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = 9502 - } - } - }; - - var fakeConsulServiceDiscoveryUrl = "http://localhost:9502"; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_load_configuration_out_of_consul() - { - var consulPort = 8500; - 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", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51779, - } - }, - UpstreamPathTemplate = "/cs/status", - 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()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [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", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51780, - } - }, - 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", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51780, - } - }, - 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:51780", "/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")) - .When(x => GivenTheConsulConfigurationIs(secondConsulConfig)) - .Then(x => ThenTheConfigIsUpdatedInOcelot()) - .BDDfy(); - } - - private void ThenTheConfigIsUpdatedInOcelot() - { - 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) - { - _config = config; - } - - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) - { - _fakeConsulBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") - { - var json = JsonConvert.SerializeObject(_config); - - var bytes = Encoding.UTF8.GetBytes(json); - - var base64 = Convert.ToBase64String(bytes); - - var kvp = new FakeConsulGetResponse(base64); - - await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp }); - } - else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") - { - try - { - var reader = new StreamReader(context.Request.Body); - - var json = reader.ReadToEnd(); - - _config = JsonConvert.DeserializeObject(json); - - var response = JsonConvert.SerializeObject(true); - - await context.Response.WriteAsync(response); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } - } - }); - }) - .Build(); - - _fakeConsulBuilder.Start(); - } - - public class FakeConsulGetResponse - { - public FakeConsulGetResponse(string value) - { - Value = value; - } - - public int CreateIndex => 100; - public int ModifyIndex => 200; - public int LockIndex => 200; - public string Key => "InternalConfiguration"; - public int Flags => 0; - public string Value { get; private set; } - public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e"; - } - - private void GivenThereIsAServiceRunningOn(string url, string basePath, int statusCode, string responseBody) - { - _builder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.UsePathBase(basePath); - - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _builder.Start(); - } - - public void Dispose() - { - _builder?.Dispose(); - _steps.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Text; +using Microsoft.AspNetCore.Builder; +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 +{ + public class ConfigurationInConsulTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private IWebHost _fakeConsulBuilder; + private FileConfiguration _config; + + public ConfigurationInConsulTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_return_response_200_with_simple_url() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51779, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = 9500 + } + } + }; + + var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; + + this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url_when_using_jsonserialized_cache() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51779, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = 9502 + } + } + }; + + var fakeConsulServiceDiscoveryUrl = "http://localhost:9502"; + + this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_load_configuration_out_of_consul() + { + var consulPort = 8500; + + 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", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51779, + } + }, + UpstreamPathTemplate = "/cs/status", + 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()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [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", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51780, + } + }, + 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", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51780, + } + }, + 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:51780", "/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")) + .When(x => GivenTheConsulConfigurationIs(secondConsulConfig)) + .Then(x => ThenTheConfigIsUpdatedInOcelot()) + .BDDfy(); + } + + private void ThenTheConfigIsUpdatedInOcelot() + { + 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) + { + _config = config; + } + + private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) + { + _fakeConsulBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") + { + var json = JsonConvert.SerializeObject(_config); + + var bytes = Encoding.UTF8.GetBytes(json); + + var base64 = Convert.ToBase64String(bytes); + + var kvp = new FakeConsulGetResponse(base64); + + await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp }); + } + else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") + { + try + { + var reader = new StreamReader(context.Request.Body); + + var json = reader.ReadToEnd(); + + _config = JsonConvert.DeserializeObject(json); + + var response = JsonConvert.SerializeObject(true); + + await context.Response.WriteAsync(response); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + }); + }) + .Build(); + + _fakeConsulBuilder.Start(); + } + + public class FakeConsulGetResponse + { + public FakeConsulGetResponse(string value) + { + Value = value; + } + + public int CreateIndex => 100; + public int ModifyIndex => 200; + public int LockIndex => 200; + public string Key => "InternalConfiguration"; + public int Flags => 0; + public string Value { get; private set; } + public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e"; + } + + private void GivenThereIsAServiceRunningOn(string url, string basePath, int statusCode, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.UsePathBase(basePath); + + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _builder.Start(); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationPollerTests.cs similarity index 73% rename from test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs rename to test/Ocelot.UnitTests/Configuration/FileConfigurationPollerTests.cs index 17fc604d8..18e8ddb63 100644 --- a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationPollerTests.cs @@ -12,37 +12,39 @@ using Xunit; using Shouldly; using static Ocelot.Infrastructure.Wait; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration; namespace Ocelot.UnitTests.Configuration { - public class ConsulFileConfigurationPollerTests : IDisposable + public class FileConfigurationPollerTests : IDisposable { - private readonly ConsulFileConfigurationPoller _poller; + private readonly FileConfigurationPoller _poller; private Mock _factory; private readonly Mock _repo; - private readonly Mock _setter; private readonly FileConfiguration _fileConfig; - private Mock _config; + private Mock _config; + private readonly Mock _internalConfigRepo; + private readonly Mock _internalConfigCreator; + private IInternalConfiguration _internalConfig; - public ConsulFileConfigurationPollerTests() + public FileConfigurationPollerTests() { var logger = new Mock(); _factory = new Mock(); - _factory.Setup(x => x.CreateLogger()).Returns(logger.Object); + _factory.Setup(x => x.CreateLogger()).Returns(logger.Object); _repo = new Mock(); - _setter = new Mock(); _fileConfig = new FileConfiguration(); - _config = new Mock(); + _config = new Mock(); _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse(_fileConfig)); _config.Setup(x => x.Delay).Returns(100); - _poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object, _config.Object); + _internalConfigRepo = new Mock(); + _internalConfigCreator = new Mock(); + _internalConfigCreator.Setup(x => x.Create(It.IsAny())).ReturnsAsync(new OkResponse(_internalConfig)); + _poller = new FileConfigurationPoller(_factory.Object, _repo.Object, _config.Object, _internalConfigRepo.Object, _internalConfigCreator.Object); + _poller.StartAsync(new CancellationToken()); } - - public void Dispose() - { - _poller.Dispose(); - } - + [Fact] public void should_start() { @@ -141,10 +143,11 @@ private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig, int del private void ThenTheSetterIsCalled(FileConfiguration fileConfig, int times) { - var result = WaitFor(2000).Until(() => { + var result = WaitFor(4000).Until(() => { try { - _setter.Verify(x => x.Set(fileConfig), Times.Exactly(times)); + _internalConfigRepo.Verify(x => x.AddOrReplace(_internalConfig), Times.Exactly(times)); + _internalConfigCreator.Verify(x => x.Create(fileConfig), Times.Exactly(times)); return true; } catch(Exception) @@ -157,10 +160,11 @@ private void ThenTheSetterIsCalled(FileConfiguration fileConfig, int times) private void ThenTheSetterIsCalledAtLeast(FileConfiguration fileConfig, int times) { - var result = WaitFor(2000).Until(() => { + var result = WaitFor(4000).Until(() => { try { - _setter.Verify(x => x.Set(fileConfig), Times.AtLeast(times)); + _internalConfigRepo.Verify(x => x.AddOrReplace(_internalConfig), Times.AtLeast(times)); + _internalConfigCreator.Verify(x => x.Create(fileConfig), Times.AtLeast(times)); return true; } catch(Exception) @@ -170,5 +174,10 @@ private void ThenTheSetterIsCalledAtLeast(FileConfiguration fileConfig, int time }); result.ShouldBeTrue(); } + + public void Dispose() + { + _poller.Dispose(); + } } }