mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-20 17:42:50 +08:00
Made the file config poller use IHostedService, bit more generic, not… (#507)
* 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
This commit is contained in:
parent
1817564ea5
commit
0f2cf2d188
@ -1,84 +1,112 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Ocelot.Configuration.File;
|
using Newtonsoft.Json;
|
||||||
using Ocelot.Configuration.Setter;
|
using Ocelot.Configuration.Creator;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Configuration.Setter;
|
||||||
namespace Ocelot.Configuration.Repository
|
using Ocelot.Logging;
|
||||||
{
|
|
||||||
public class ConsulFileConfigurationPoller : IDisposable
|
namespace Ocelot.Configuration.Repository
|
||||||
{
|
{
|
||||||
private readonly IOcelotLogger _logger;
|
public class FileConfigurationPoller : IHostedService, IDisposable
|
||||||
private readonly IFileConfigurationRepository _repo;
|
{
|
||||||
private readonly IFileConfigurationSetter _setter;
|
private readonly IOcelotLogger _logger;
|
||||||
private string _previousAsJson;
|
private readonly IFileConfigurationRepository _repo;
|
||||||
private readonly Timer _timer;
|
private string _previousAsJson;
|
||||||
private bool _polling;
|
private Timer _timer;
|
||||||
private readonly IConsulPollerConfiguration _config;
|
private bool _polling;
|
||||||
|
private readonly IFileConfigurationPollerOptions _options;
|
||||||
public ConsulFileConfigurationPoller(
|
private readonly IInternalConfigurationRepository _internalConfigRepo;
|
||||||
IOcelotLoggerFactory factory,
|
private readonly IInternalConfigurationCreator _internalConfigCreator;
|
||||||
IFileConfigurationRepository repo,
|
|
||||||
IFileConfigurationSetter setter,
|
public FileConfigurationPoller(
|
||||||
IConsulPollerConfiguration config)
|
IOcelotLoggerFactory factory,
|
||||||
{
|
IFileConfigurationRepository repo,
|
||||||
_setter = setter;
|
IFileConfigurationPollerOptions options,
|
||||||
_config = config;
|
IInternalConfigurationRepository internalConfigRepo,
|
||||||
_logger = factory.CreateLogger<ConsulFileConfigurationPoller>();
|
IInternalConfigurationCreator internalConfigCreator)
|
||||||
_repo = repo;
|
{
|
||||||
_previousAsJson = "";
|
_internalConfigRepo = internalConfigRepo;
|
||||||
_timer = new Timer(async x =>
|
_internalConfigCreator = internalConfigCreator;
|
||||||
{
|
_options = options;
|
||||||
if(_polling)
|
_logger = factory.CreateLogger<FileConfigurationPoller>();
|
||||||
{
|
_repo = repo;
|
||||||
return;
|
_previousAsJson = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
_polling = true;
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
await Poll();
|
{
|
||||||
_polling = false;
|
_logger.LogInformation($"{nameof(FileConfigurationPoller)} is starting.");
|
||||||
}, null, _config.Delay, _config.Delay);
|
|
||||||
}
|
_timer = new Timer(async x =>
|
||||||
|
{
|
||||||
private async Task Poll()
|
if(_polling)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Started polling consul");
|
return;
|
||||||
|
}
|
||||||
var fileConfig = await _repo.Get();
|
|
||||||
|
_polling = true;
|
||||||
if(fileConfig.IsError)
|
await Poll();
|
||||||
{
|
_polling = false;
|
||||||
_logger.LogWarning($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}");
|
}, null, _options.Delay, _options.Delay);
|
||||||
return;
|
|
||||||
}
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
var asJson = ToJson(fileConfig.Data);
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
if(!fileConfig.IsError && asJson != _previousAsJson)
|
{
|
||||||
{
|
_logger.LogInformation($"{nameof(FileConfigurationPoller)} is stopping.");
|
||||||
await _setter.Set(fileConfig.Data);
|
|
||||||
_previousAsJson = asJson;
|
_timer?.Change(Timeout.Infinite, 0);
|
||||||
}
|
|
||||||
|
return Task.CompletedTask;
|
||||||
_logger.LogInformation("Finished polling consul");
|
}
|
||||||
}
|
|
||||||
|
private async Task Poll()
|
||||||
/// <summary>
|
{
|
||||||
/// We could do object comparison here but performance isnt really a problem. This might be an issue one day!
|
_logger.LogInformation("Started polling consul");
|
||||||
/// </summary>
|
|
||||||
/// <returns>hash of the config</returns>
|
var fileConfig = await _repo.Get();
|
||||||
private string ToJson(FileConfiguration config)
|
|
||||||
{
|
if(fileConfig.IsError)
|
||||||
var currentHash = JsonConvert.SerializeObject(config);
|
{
|
||||||
return currentHash;
|
_logger.LogWarning($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}");
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
public void Dispose()
|
|
||||||
{
|
var asJson = ToJson(fileConfig.Data);
|
||||||
_timer.Dispose();
|
|
||||||
}
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// We could do object comparison here but performance isnt really a problem. This might be an issue one day!
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>hash of the config</returns>
|
||||||
|
private string ToJson(FileConfiguration config)
|
||||||
|
{
|
||||||
|
var currentHash = JsonConvert.SerializeObject(config);
|
||||||
|
return currentHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_timer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
namespace Ocelot.Configuration.Repository
|
namespace Ocelot.Configuration.Repository
|
||||||
{
|
{
|
||||||
public interface IConsulPollerConfiguration
|
public interface IFileConfigurationPollerOptions
|
||||||
{
|
{
|
||||||
int Delay { get; }
|
int Delay { get; }
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
namespace Ocelot.Configuration.Repository
|
namespace Ocelot.Configuration.Repository
|
||||||
{
|
{
|
||||||
public class InMemoryConsulPollerConfiguration : IConsulPollerConfiguration
|
public class InMemoryFileConfigurationPollerOptions : IFileConfigurationPollerOptions
|
||||||
{
|
{
|
||||||
public int Delay => 1000;
|
public int Delay => 1000;
|
||||||
}
|
}
|
@ -8,7 +8,7 @@ namespace Ocelot.Configuration.Setter
|
|||||||
{
|
{
|
||||||
public class FileAndInternalConfigurationSetter : IFileConfigurationSetter
|
public class FileAndInternalConfigurationSetter : IFileConfigurationSetter
|
||||||
{
|
{
|
||||||
private readonly IInternalConfigurationRepository _configRepo;
|
private readonly IInternalConfigurationRepository internalConfigRepo;
|
||||||
private readonly IInternalConfigurationCreator _configCreator;
|
private readonly IInternalConfigurationCreator _configCreator;
|
||||||
private readonly IFileConfigurationRepository _repo;
|
private readonly IFileConfigurationRepository _repo;
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ namespace Ocelot.Configuration.Setter
|
|||||||
IInternalConfigurationCreator configCreator,
|
IInternalConfigurationCreator configCreator,
|
||||||
IFileConfigurationRepository repo)
|
IFileConfigurationRepository repo)
|
||||||
{
|
{
|
||||||
_configRepo = configRepo;
|
internalConfigRepo = configRepo;
|
||||||
_configCreator = configCreator;
|
_configCreator = configCreator;
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
}
|
}
|
||||||
@ -35,7 +35,7 @@ namespace Ocelot.Configuration.Setter
|
|||||||
|
|
||||||
if(!config.IsError)
|
if(!config.IsError)
|
||||||
{
|
{
|
||||||
_configRepo.AddOrReplace(config.Data);
|
internalConfigRepo.AddOrReplace(config.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ErrorResponse(config.Errors);
|
return new ErrorResponse(config.Errors);
|
||||||
|
@ -160,7 +160,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
|
|
||||||
// We add this here so that we can always inject something into the factory for IoC..
|
// We add this here so that we can always inject something into the factory for IoC..
|
||||||
_services.AddSingleton<IServiceTracer, FakeServiceTracer>();
|
_services.AddSingleton<IServiceTracer, FakeServiceTracer>();
|
||||||
_services.TryAddSingleton<IConsulPollerConfiguration, InMemoryConsulPollerConfiguration>();
|
_services.TryAddSingleton<IFileConfigurationPollerOptions, InMemoryFileConfigurationPollerOptions>();
|
||||||
_services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
|
_services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
|
||||||
_services.TryAddSingleton<IPlaceholders, Placeholders>();
|
_services.TryAddSingleton<IPlaceholders, Placeholders>();
|
||||||
_services.TryAddSingleton<IConsulClientFactory, ConsulClientFactory>();
|
_services.TryAddSingleton<IConsulClientFactory, ConsulClientFactory>();
|
||||||
@ -245,7 +245,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
|
|
||||||
public IOcelotBuilder AddStoreOcelotConfigurationInConsul()
|
public IOcelotBuilder AddStoreOcelotConfigurationInConsul()
|
||||||
{
|
{
|
||||||
_services.AddSingleton<ConsulFileConfigurationPoller>();
|
_services.AddHostedService<FileConfigurationPoller>();
|
||||||
_services.AddSingleton<IFileConfigurationRepository, ConsulFileConfigurationRepository>();
|
_services.AddSingleton<IFileConfigurationRepository, ConsulFileConfigurationRepository>();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -1,266 +1,263 @@
|
|||||||
namespace Ocelot.Middleware
|
namespace Ocelot.Middleware
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using DependencyInjection;
|
using DependencyInjection;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Configuration.Creator;
|
using Ocelot.Configuration.Creator;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Configuration.Repository;
|
using Ocelot.Configuration.Repository;
|
||||||
using Ocelot.Configuration.Setter;
|
using Ocelot.Configuration.Setter;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Rafty.Concensus;
|
using Rafty.Concensus;
|
||||||
using Rafty.Infrastructure;
|
using Rafty.Infrastructure;
|
||||||
using Ocelot.Middleware.Pipeline;
|
using Ocelot.Middleware.Pipeline;
|
||||||
using Pivotal.Discovery.Client;
|
using Pivotal.Discovery.Client;
|
||||||
using Rafty.Concensus.Node;
|
using Rafty.Concensus.Node;
|
||||||
|
|
||||||
public static class OcelotMiddlewareExtensions
|
public static class OcelotMiddlewareExtensions
|
||||||
{
|
{
|
||||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder)
|
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
await builder.UseOcelot(new OcelotPipelineConfiguration());
|
await builder.UseOcelot(new OcelotPipelineConfiguration());
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, Action<OcelotPipelineConfiguration> pipelineConfiguration)
|
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, Action<OcelotPipelineConfiguration> pipelineConfiguration)
|
||||||
{
|
{
|
||||||
var config = new OcelotPipelineConfiguration();
|
var config = new OcelotPipelineConfiguration();
|
||||||
pipelineConfiguration?.Invoke(config);
|
pipelineConfiguration?.Invoke(config);
|
||||||
return await builder.UseOcelot(config);
|
return await builder.UseOcelot(config);
|
||||||
}
|
}
|
||||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
|
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
|
||||||
{
|
{
|
||||||
var configuration = await CreateConfiguration(builder);
|
var configuration = await CreateConfiguration(builder);
|
||||||
|
|
||||||
CreateAdministrationArea(builder, configuration);
|
CreateAdministrationArea(builder, configuration);
|
||||||
|
|
||||||
if (UsingRafty(builder))
|
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<IInternalConfiguration> 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<FileConfiguration>)builder.ApplicationServices.GetService(typeof(IOptions<FileConfiguration>));
|
|
||||||
|
|
||||||
// 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.
|
SetUpRafty(builder);
|
||||||
await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo);
|
}
|
||||||
}
|
|
||||||
else if(AdministrationApiInUse(adminPath))
|
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<IInternalConfiguration> 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<FileConfiguration>)builder.ApplicationServices.GetService(typeof(IOptions<FileConfiguration>));
|
||||||
|
|
||||||
|
// 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
|
//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.
|
//admin api it works...boy this is getting a spit spags boll.
|
||||||
var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter));
|
var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter));
|
||||||
|
|
||||||
await SetFileConfig(fileConfigSetter, fileConfig);
|
await SetFileConfig(fileConfigSetter, fileConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetOcelotConfigAndReturn(internalConfigRepo);
|
return GetOcelotConfigAndReturn(internalConfigRepo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool AdministrationApiInUse(IAdministrationPath adminPath)
|
private static bool AdministrationApiInUse(IAdministrationPath adminPath)
|
||||||
{
|
{
|
||||||
return adminPath.GetType() != typeof(NullAdministrationPath);
|
return adminPath.GetType() != typeof(NullAdministrationPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task SetFileConfigInConsul(IApplicationBuilder builder,
|
private static async Task SetFileConfigInConsul(IApplicationBuilder builder,
|
||||||
IFileConfigurationRepository fileConfigRepo, IOptions<FileConfiguration> fileConfig,
|
IFileConfigurationRepository fileConfigRepo, IOptions<FileConfiguration> fileConfig,
|
||||||
IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo)
|
IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo)
|
||||||
{
|
{
|
||||||
// get the config from consul.
|
// get the config from consul.
|
||||||
var fileConfigFromConsul = await fileConfigRepo.Get();
|
var fileConfigFromConsul = await fileConfigRepo.Get();
|
||||||
|
|
||||||
if (IsError(fileConfigFromConsul))
|
if (IsError(fileConfigFromConsul))
|
||||||
{
|
{
|
||||||
ThrowToStopOcelotStarting(fileConfigFromConsul);
|
ThrowToStopOcelotStarting(fileConfigFromConsul);
|
||||||
}
|
}
|
||||||
else if (ConfigNotStoredInConsul(fileConfigFromConsul))
|
else if (ConfigNotStoredInConsul(fileConfigFromConsul))
|
||||||
{
|
{
|
||||||
//there was no config in consul set the file in config in consul
|
//there was no config in consul set the file in config in consul
|
||||||
await fileConfigRepo.Set(fileConfig.Value);
|
await fileConfigRepo.Set(fileConfig.Value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// create the internal config from consul data
|
// create the internal config from consul data
|
||||||
var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data);
|
var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data);
|
||||||
|
|
||||||
if (IsError(internalConfig))
|
if (IsError(internalConfig))
|
||||||
{
|
{
|
||||||
ThrowToStopOcelotStarting(internalConfig);
|
ThrowToStopOcelotStarting(internalConfig);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// add the internal config to the internal repo
|
// add the internal config to the internal repo
|
||||||
var response = internalConfigRepo.AddOrReplace(internalConfig.Data);
|
var response = internalConfigRepo.AddOrReplace(internalConfig.Data);
|
||||||
|
|
||||||
if (IsError(response))
|
if (IsError(response))
|
||||||
{
|
{
|
||||||
ThrowToStopOcelotStarting(response);
|
ThrowToStopOcelotStarting(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsError(internalConfig))
|
if (IsError(internalConfig))
|
||||||
{
|
{
|
||||||
ThrowToStopOcelotStarting(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<FileConfiguration> fileConfig)
|
||||||
}
|
{
|
||||||
|
var response = await fileConfigSetter.Set(fileConfig.Value);
|
||||||
private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions<FileConfiguration> fileConfig)
|
|
||||||
{
|
if (IsError(response))
|
||||||
var response = await fileConfigSetter.Set(fileConfig.Value);
|
{
|
||||||
|
ThrowToStopOcelotStarting(response);
|
||||||
if (IsError(response))
|
}
|
||||||
{
|
}
|
||||||
ThrowToStopOcelotStarting(response);
|
|
||||||
}
|
private static bool ConfigNotStoredInConsul(Responses.Response<FileConfiguration> fileConfigFromConsul)
|
||||||
}
|
{
|
||||||
|
return fileConfigFromConsul.Data == null;
|
||||||
private static bool ConfigNotStoredInConsul(Responses.Response<FileConfiguration> fileConfigFromConsul)
|
}
|
||||||
{
|
|
||||||
return fileConfigFromConsul.Data == null;
|
private static bool IsError(Response response)
|
||||||
}
|
{
|
||||||
|
return response == null || response.IsError;
|
||||||
private static bool IsError(Response response)
|
}
|
||||||
{
|
|
||||||
return response == null || response.IsError;
|
private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider)
|
||||||
}
|
{
|
||||||
|
var ocelotConfiguration = provider.Get();
|
||||||
private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider)
|
|
||||||
{
|
if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError)
|
||||||
var ocelotConfiguration = provider.Get();
|
{
|
||||||
|
ThrowToStopOcelotStarting(ocelotConfiguration);
|
||||||
if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError)
|
}
|
||||||
{
|
|
||||||
ThrowToStopOcelotStarting(ocelotConfiguration);
|
return ocelotConfiguration.Data;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 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 bool UsingConsul(IFileConfigurationRepository fileConfigRepo)
|
}
|
||||||
{
|
|
||||||
return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository);
|
private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration)
|
||||||
}
|
{
|
||||||
|
if (!string.IsNullOrEmpty(configuration.AdministrationPath))
|
||||||
private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration)
|
{
|
||||||
{
|
builder.Map(configuration.AdministrationPath, app =>
|
||||||
if (!string.IsNullOrEmpty(configuration.AdministrationPath))
|
{
|
||||||
{
|
//todo - hack so we know that we are using internal identity server
|
||||||
builder.Map(configuration.AdministrationPath, app =>
|
var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration));
|
||||||
{
|
if (identityServerConfiguration != null)
|
||||||
//todo - hack so we know that we are using internal identity server
|
{
|
||||||
var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration));
|
app.UseIdentityServer();
|
||||||
if (identityServerConfiguration != null)
|
}
|
||||||
{
|
|
||||||
app.UseIdentityServer();
|
app.UseAuthentication();
|
||||||
}
|
app.UseMvc();
|
||||||
|
});
|
||||||
app.UseAuthentication();
|
}
|
||||||
app.UseMvc();
|
}
|
||||||
});
|
|
||||||
}
|
private static void ConfigureDiagnosticListener(IApplicationBuilder builder)
|
||||||
}
|
{
|
||||||
|
var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment));
|
||||||
private static void ConfigureDiagnosticListener(IApplicationBuilder builder)
|
var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener));
|
||||||
{
|
var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener));
|
||||||
var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment));
|
diagnosticListener.SubscribeWithAdapter(listener);
|
||||||
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));
|
||||||
private static void OnShutdown(IApplicationBuilder app)
|
node.Stop();
|
||||||
{
|
}
|
||||||
var node = (INode)app.ApplicationServices.GetService(typeof(INode));
|
}
|
||||||
node.Stop();
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,390 +1,391 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using static Ocelot.Infrastructure.Wait;
|
using static Ocelot.Infrastructure.Wait;
|
||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
namespace Ocelot.AcceptanceTests
|
||||||
{
|
{
|
||||||
public class ConfigurationInConsulTests : IDisposable
|
public class ConfigurationInConsulTests : IDisposable
|
||||||
{
|
{
|
||||||
private IWebHost _builder;
|
private IWebHost _builder;
|
||||||
private readonly Steps _steps;
|
private readonly Steps _steps;
|
||||||
private IWebHost _fakeConsulBuilder;
|
private IWebHost _fakeConsulBuilder;
|
||||||
private FileConfiguration _config;
|
private FileConfiguration _config;
|
||||||
|
|
||||||
public ConfigurationInConsulTests()
|
public ConfigurationInConsulTests()
|
||||||
{
|
{
|
||||||
_steps = new Steps();
|
_steps = new Steps();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_response_200_with_simple_url()
|
public void should_return_response_200_with_simple_url()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/",
|
DownstreamPathTemplate = "/",
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 51779,
|
Port = 51779,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
{
|
{
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 9500
|
Port = 9500
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var fakeConsulServiceDiscoveryUrl = "http://localhost:9500";
|
var fakeConsulServiceDiscoveryUrl = "http://localhost:9500";
|
||||||
|
|
||||||
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
|
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
|
||||||
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura"))
|
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_response_200_with_simple_url_when_using_jsonserialized_cache()
|
public void should_return_response_200_with_simple_url_when_using_jsonserialized_cache()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/",
|
DownstreamPathTemplate = "/",
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 51779,
|
Port = 51779,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
{
|
{
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 9502
|
Port = 9502
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var fakeConsulServiceDiscoveryUrl = "http://localhost:9502";
|
var fakeConsulServiceDiscoveryUrl = "http://localhost:9502";
|
||||||
|
|
||||||
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
|
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
|
||||||
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura"))
|
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache())
|
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_load_configuration_out_of_consul()
|
public void should_load_configuration_out_of_consul()
|
||||||
{
|
{
|
||||||
var consulPort = 8500;
|
var consulPort = 8500;
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
var configuration = new FileConfiguration
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
{
|
||||||
{
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
{
|
||||||
{
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
||||||
Host = "localhost",
|
{
|
||||||
Port = consulPort
|
Host = "localhost",
|
||||||
}
|
Port = consulPort
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
|
||||||
|
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
||||||
var consulConfig = new FileConfiguration
|
|
||||||
{
|
var consulConfig = new FileConfiguration
|
||||||
ReRoutes = new List<FileReRoute>
|
{
|
||||||
{
|
ReRoutes = new List<FileReRoute>
|
||||||
new FileReRoute
|
{
|
||||||
{
|
new FileReRoute
|
||||||
DownstreamPathTemplate = "/status",
|
{
|
||||||
DownstreamScheme = "http",
|
DownstreamPathTemplate = "/status",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamScheme = "http",
|
||||||
{
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
new FileHostAndPort
|
{
|
||||||
{
|
new FileHostAndPort
|
||||||
Host = "localhost",
|
{
|
||||||
Port = 51779,
|
Host = "localhost",
|
||||||
}
|
Port = 51779,
|
||||||
},
|
}
|
||||||
UpstreamPathTemplate = "/cs/status",
|
},
|
||||||
UpstreamHttpMethod = new List<string> {"Get"}
|
UpstreamPathTemplate = "/cs/status",
|
||||||
}
|
UpstreamHttpMethod = new List<string> {"Get"}
|
||||||
},
|
}
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
},
|
||||||
{
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
{
|
||||||
{
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
||||||
Host = "localhost",
|
{
|
||||||
Port = consulPort
|
Host = "localhost",
|
||||||
}
|
Port = consulPort
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
this.Given(x => GivenTheConsulConfigurationIs(consulConfig))
|
|
||||||
.And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
|
this.Given(x => GivenTheConsulConfigurationIs(consulConfig))
|
||||||
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura"))
|
.And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura"))
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status"))
|
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status"))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.BDDfy();
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
}
|
.BDDfy();
|
||||||
|
}
|
||||||
[Fact]
|
|
||||||
public void should_load_configuration_out_of_consul_if_it_is_changed()
|
[Fact]
|
||||||
{
|
public void should_load_configuration_out_of_consul_if_it_is_changed()
|
||||||
var consulPort = 8506;
|
{
|
||||||
var configuration = new FileConfiguration
|
var consulPort = 8506;
|
||||||
{
|
var configuration = new FileConfiguration
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
{
|
||||||
{
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
{
|
||||||
{
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
||||||
Host = "localhost",
|
{
|
||||||
Port = consulPort
|
Host = "localhost",
|
||||||
}
|
Port = consulPort
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
|
||||||
|
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
||||||
var consulConfig = new FileConfiguration
|
|
||||||
{
|
var consulConfig = new FileConfiguration
|
||||||
ReRoutes = new List<FileReRoute>
|
{
|
||||||
{
|
ReRoutes = new List<FileReRoute>
|
||||||
new FileReRoute
|
{
|
||||||
{
|
new FileReRoute
|
||||||
DownstreamPathTemplate = "/status",
|
{
|
||||||
DownstreamScheme = "http",
|
DownstreamPathTemplate = "/status",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamScheme = "http",
|
||||||
{
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
new FileHostAndPort
|
{
|
||||||
{
|
new FileHostAndPort
|
||||||
Host = "localhost",
|
{
|
||||||
Port = 51780,
|
Host = "localhost",
|
||||||
}
|
Port = 51780,
|
||||||
},
|
}
|
||||||
UpstreamPathTemplate = "/cs/status",
|
},
|
||||||
UpstreamHttpMethod = new List<string> {"Get"}
|
UpstreamPathTemplate = "/cs/status",
|
||||||
}
|
UpstreamHttpMethod = new List<string> {"Get"}
|
||||||
},
|
}
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
},
|
||||||
{
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
{
|
||||||
{
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
||||||
Host = "localhost",
|
{
|
||||||
Port = consulPort
|
Host = "localhost",
|
||||||
}
|
Port = consulPort
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
var secondConsulConfig = new FileConfiguration
|
|
||||||
{
|
var secondConsulConfig = new FileConfiguration
|
||||||
ReRoutes = new List<FileReRoute>
|
{
|
||||||
{
|
ReRoutes = new List<FileReRoute>
|
||||||
new FileReRoute
|
{
|
||||||
{
|
new FileReRoute
|
||||||
DownstreamPathTemplate = "/status",
|
{
|
||||||
DownstreamScheme = "http",
|
DownstreamPathTemplate = "/status",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamScheme = "http",
|
||||||
{
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
new FileHostAndPort
|
{
|
||||||
{
|
new FileHostAndPort
|
||||||
Host = "localhost",
|
{
|
||||||
Port = 51780,
|
Host = "localhost",
|
||||||
}
|
Port = 51780,
|
||||||
},
|
}
|
||||||
UpstreamPathTemplate = "/cs/status/awesome",
|
},
|
||||||
UpstreamHttpMethod = new List<string> {"Get"}
|
UpstreamPathTemplate = "/cs/status/awesome",
|
||||||
}
|
UpstreamHttpMethod = new List<string> {"Get"}
|
||||||
},
|
}
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
},
|
||||||
{
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
{
|
||||||
{
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
||||||
Host = "localhost",
|
{
|
||||||
Port = consulPort
|
Host = "localhost",
|
||||||
}
|
Port = consulPort
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
this.Given(x => GivenTheConsulConfigurationIs(consulConfig))
|
|
||||||
.And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
|
this.Given(x => GivenTheConsulConfigurationIs(consulConfig))
|
||||||
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51780", "/status", 200, "Hello from Laura"))
|
.And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51780", "/status", 200, "Hello from Laura"))
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status"))
|
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
||||||
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status"))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.When(x => GivenTheConsulConfigurationIs(secondConsulConfig))
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.Then(x => ThenTheConfigIsUpdatedInOcelot())
|
.When(x => GivenTheConsulConfigurationIs(secondConsulConfig))
|
||||||
.BDDfy();
|
.Then(x => ThenTheConfigIsUpdatedInOcelot())
|
||||||
}
|
.BDDfy();
|
||||||
|
}
|
||||||
private void ThenTheConfigIsUpdatedInOcelot()
|
|
||||||
{
|
private void ThenTheConfigIsUpdatedInOcelot()
|
||||||
var result = WaitFor(20000).Until(() => {
|
{
|
||||||
try
|
var result = WaitFor(20000).Until(() => {
|
||||||
{
|
try
|
||||||
_steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome");
|
{
|
||||||
_steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK);
|
_steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome");
|
||||||
_steps.ThenTheResponseBodyShouldBe("Hello from Laura");
|
_steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK);
|
||||||
return true;
|
_steps.ThenTheResponseBodyShouldBe("Hello from Laura");
|
||||||
}
|
return true;
|
||||||
catch (Exception)
|
}
|
||||||
{
|
catch (Exception)
|
||||||
return false;
|
{
|
||||||
}
|
return false;
|
||||||
});
|
}
|
||||||
result.ShouldBeTrue();
|
});
|
||||||
}
|
result.ShouldBeTrue();
|
||||||
|
}
|
||||||
private void GivenTheConsulConfigurationIs(FileConfiguration config)
|
|
||||||
{
|
private void GivenTheConsulConfigurationIs(FileConfiguration config)
|
||||||
_config = config;
|
{
|
||||||
}
|
_config = config;
|
||||||
|
}
|
||||||
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url)
|
|
||||||
{
|
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url)
|
||||||
_fakeConsulBuilder = new WebHostBuilder()
|
{
|
||||||
.UseUrls(url)
|
_fakeConsulBuilder = new WebHostBuilder()
|
||||||
.UseKestrel()
|
.UseUrls(url)
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseKestrel()
|
||||||
.UseIISIntegration()
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
.UseUrls(url)
|
.UseIISIntegration()
|
||||||
.Configure(app =>
|
.UseUrls(url)
|
||||||
{
|
.Configure(app =>
|
||||||
app.Run(async context =>
|
{
|
||||||
{
|
app.Run(async context =>
|
||||||
if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/InternalConfiguration")
|
{
|
||||||
{
|
if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/InternalConfiguration")
|
||||||
var json = JsonConvert.SerializeObject(_config);
|
{
|
||||||
|
var json = JsonConvert.SerializeObject(_config);
|
||||||
var bytes = Encoding.UTF8.GetBytes(json);
|
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(json);
|
||||||
var base64 = Convert.ToBase64String(bytes);
|
|
||||||
|
var base64 = Convert.ToBase64String(bytes);
|
||||||
var kvp = new FakeConsulGetResponse(base64);
|
|
||||||
|
var kvp = new FakeConsulGetResponse(base64);
|
||||||
await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp });
|
|
||||||
}
|
await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp });
|
||||||
else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/InternalConfiguration")
|
}
|
||||||
{
|
else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/InternalConfiguration")
|
||||||
try
|
{
|
||||||
{
|
try
|
||||||
var reader = new StreamReader(context.Request.Body);
|
{
|
||||||
|
var reader = new StreamReader(context.Request.Body);
|
||||||
var json = reader.ReadToEnd();
|
|
||||||
|
var json = reader.ReadToEnd();
|
||||||
_config = JsonConvert.DeserializeObject<FileConfiguration>(json);
|
|
||||||
|
_config = JsonConvert.DeserializeObject<FileConfiguration>(json);
|
||||||
var response = JsonConvert.SerializeObject(true);
|
|
||||||
|
var response = JsonConvert.SerializeObject(true);
|
||||||
await context.Response.WriteAsync(response);
|
|
||||||
}
|
await context.Response.WriteAsync(response);
|
||||||
catch (Exception e)
|
}
|
||||||
{
|
catch (Exception e)
|
||||||
Console.WriteLine(e);
|
{
|
||||||
throw;
|
Console.WriteLine(e);
|
||||||
}
|
throw;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
})
|
});
|
||||||
.Build();
|
})
|
||||||
|
.Build();
|
||||||
_fakeConsulBuilder.Start();
|
|
||||||
}
|
_fakeConsulBuilder.Start();
|
||||||
|
}
|
||||||
public class FakeConsulGetResponse
|
|
||||||
{
|
public class FakeConsulGetResponse
|
||||||
public FakeConsulGetResponse(string value)
|
{
|
||||||
{
|
public FakeConsulGetResponse(string value)
|
||||||
Value = value;
|
{
|
||||||
}
|
Value = value;
|
||||||
|
}
|
||||||
public int CreateIndex => 100;
|
|
||||||
public int ModifyIndex => 200;
|
public int CreateIndex => 100;
|
||||||
public int LockIndex => 200;
|
public int ModifyIndex => 200;
|
||||||
public string Key => "InternalConfiguration";
|
public int LockIndex => 200;
|
||||||
public int Flags => 0;
|
public string Key => "InternalConfiguration";
|
||||||
public string Value { get; private set; }
|
public int Flags => 0;
|
||||||
public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e";
|
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)
|
|
||||||
{
|
private void GivenThereIsAServiceRunningOn(string url, string basePath, int statusCode, string responseBody)
|
||||||
_builder = new WebHostBuilder()
|
{
|
||||||
.UseUrls(url)
|
_builder = new WebHostBuilder()
|
||||||
.UseKestrel()
|
.UseUrls(url)
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseKestrel()
|
||||||
.UseIISIntegration()
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
.UseUrls(url)
|
.UseIISIntegration()
|
||||||
.Configure(app =>
|
.UseUrls(url)
|
||||||
{
|
.Configure(app =>
|
||||||
app.UsePathBase(basePath);
|
{
|
||||||
|
app.UsePathBase(basePath);
|
||||||
app.Run(async context =>
|
|
||||||
{
|
app.Run(async context =>
|
||||||
context.Response.StatusCode = statusCode;
|
{
|
||||||
await context.Response.WriteAsync(responseBody);
|
context.Response.StatusCode = statusCode;
|
||||||
});
|
await context.Response.WriteAsync(responseBody);
|
||||||
})
|
});
|
||||||
.Build();
|
})
|
||||||
|
.Build();
|
||||||
_builder.Start();
|
|
||||||
}
|
_builder.Start();
|
||||||
|
}
|
||||||
public void Dispose()
|
|
||||||
{
|
public void Dispose()
|
||||||
_builder?.Dispose();
|
{
|
||||||
_steps.Dispose();
|
_builder?.Dispose();
|
||||||
}
|
_steps.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -12,37 +12,39 @@ using TestStack.BDDfy;
|
|||||||
using Xunit;
|
using Xunit;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using static Ocelot.Infrastructure.Wait;
|
using static Ocelot.Infrastructure.Wait;
|
||||||
|
using Ocelot.Configuration.Creator;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.Configuration
|
namespace Ocelot.UnitTests.Configuration
|
||||||
{
|
{
|
||||||
public class ConsulFileConfigurationPollerTests : IDisposable
|
public class FileConfigurationPollerTests : IDisposable
|
||||||
{
|
{
|
||||||
private readonly ConsulFileConfigurationPoller _poller;
|
private readonly FileConfigurationPoller _poller;
|
||||||
private Mock<IOcelotLoggerFactory> _factory;
|
private Mock<IOcelotLoggerFactory> _factory;
|
||||||
private readonly Mock<IFileConfigurationRepository> _repo;
|
private readonly Mock<IFileConfigurationRepository> _repo;
|
||||||
private readonly Mock<IFileConfigurationSetter> _setter;
|
|
||||||
private readonly FileConfiguration _fileConfig;
|
private readonly FileConfiguration _fileConfig;
|
||||||
private Mock<IConsulPollerConfiguration> _config;
|
private Mock<IFileConfigurationPollerOptions> _config;
|
||||||
|
private readonly Mock<IInternalConfigurationRepository> _internalConfigRepo;
|
||||||
|
private readonly Mock<IInternalConfigurationCreator> _internalConfigCreator;
|
||||||
|
private IInternalConfiguration _internalConfig;
|
||||||
|
|
||||||
public ConsulFileConfigurationPollerTests()
|
public FileConfigurationPollerTests()
|
||||||
{
|
{
|
||||||
var logger = new Mock<IOcelotLogger>();
|
var logger = new Mock<IOcelotLogger>();
|
||||||
_factory = new Mock<IOcelotLoggerFactory>();
|
_factory = new Mock<IOcelotLoggerFactory>();
|
||||||
_factory.Setup(x => x.CreateLogger<ConsulFileConfigurationPoller>()).Returns(logger.Object);
|
_factory.Setup(x => x.CreateLogger<FileConfigurationPoller>()).Returns(logger.Object);
|
||||||
_repo = new Mock<IFileConfigurationRepository>();
|
_repo = new Mock<IFileConfigurationRepository>();
|
||||||
_setter = new Mock<IFileConfigurationSetter>();
|
|
||||||
_fileConfig = new FileConfiguration();
|
_fileConfig = new FileConfiguration();
|
||||||
_config = new Mock<IConsulPollerConfiguration>();
|
_config = new Mock<IFileConfigurationPollerOptions>();
|
||||||
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(_fileConfig));
|
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(_fileConfig));
|
||||||
_config.Setup(x => x.Delay).Returns(100);
|
_config.Setup(x => x.Delay).Returns(100);
|
||||||
_poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object, _config.Object);
|
_internalConfigRepo = new Mock<IInternalConfigurationRepository>();
|
||||||
|
_internalConfigCreator = new Mock<IInternalConfigurationCreator>();
|
||||||
|
_internalConfigCreator.Setup(x => x.Create(It.IsAny<FileConfiguration>())).ReturnsAsync(new OkResponse<IInternalConfiguration>(_internalConfig));
|
||||||
|
_poller = new FileConfigurationPoller(_factory.Object, _repo.Object, _config.Object, _internalConfigRepo.Object, _internalConfigCreator.Object);
|
||||||
|
_poller.StartAsync(new CancellationToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_poller.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_start()
|
public void should_start()
|
||||||
{
|
{
|
||||||
@ -141,10 +143,11 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
private void ThenTheSetterIsCalled(FileConfiguration fileConfig, int times)
|
private void ThenTheSetterIsCalled(FileConfiguration fileConfig, int times)
|
||||||
{
|
{
|
||||||
var result = WaitFor(2000).Until(() => {
|
var result = WaitFor(4000).Until(() => {
|
||||||
try
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
catch(Exception)
|
catch(Exception)
|
||||||
@ -157,10 +160,11 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
private void ThenTheSetterIsCalledAtLeast(FileConfiguration fileConfig, int times)
|
private void ThenTheSetterIsCalledAtLeast(FileConfiguration fileConfig, int times)
|
||||||
{
|
{
|
||||||
var result = WaitFor(2000).Until(() => {
|
var result = WaitFor(4000).Until(() => {
|
||||||
try
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
catch(Exception)
|
catch(Exception)
|
||||||
@ -170,5 +174,10 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
});
|
});
|
||||||
result.ShouldBeTrue();
|
result.ShouldBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_poller.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user