Feature/hacking consul file config (#157)

* moving things around to see if I can get consul to store fileconfiguration rather than ocelotconfiguration

* more refactoring to see if we can get a test for the feature

* acceptance test passing for updating in consul..need to sort object comparison out

* fixed the failing tests
This commit is contained in:
Tom Pallister 2017-11-17 17:58:39 +00:00 committed by GitHub
parent d377482013
commit 68242102d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 478 additions and 62 deletions

View File

@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Configuration.Repository; using Ocelot.Configuration.Repository;
@ -16,9 +17,9 @@ namespace Ocelot.Configuration.Provider
_repo = repo; _repo = repo;
} }
public Response<FileConfiguration> Get() public async Task<Response<FileConfiguration>> Get()
{ {
var fileConfig = _repo.Get(); var fileConfig = await _repo.Get();
return new OkResponse<FileConfiguration>(fileConfig.Data); return new OkResponse<FileConfiguration>(fileConfig.Data);
} }
} }

View File

@ -1,3 +1,4 @@
using System.Threading.Tasks;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Responses; using Ocelot.Responses;
@ -5,6 +6,6 @@ namespace Ocelot.Configuration.Provider
{ {
public interface IFileConfigurationProvider public interface IFileConfigurationProvider
{ {
Response<FileConfiguration> Get(); Task<Response<FileConfiguration>> Get();
} }
} }

View File

@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Configuration.File;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Configuration.Provider namespace Ocelot.Configuration.Provider

View File

@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Repository; using Ocelot.Configuration.Repository;
using Ocelot.Responses; using Ocelot.Responses;
@ -9,16 +10,16 @@ namespace Ocelot.Configuration.Provider
/// </summary> /// </summary>
public class OcelotConfigurationProvider : IOcelotConfigurationProvider public class OcelotConfigurationProvider : IOcelotConfigurationProvider
{ {
private readonly IOcelotConfigurationRepository _repo; private readonly IOcelotConfigurationRepository _config;
public OcelotConfigurationProvider(IOcelotConfigurationRepository repo) public OcelotConfigurationProvider(IOcelotConfigurationRepository repo)
{ {
_repo = repo; _config = repo;
} }
public async Task<Response<IOcelotConfiguration>> Get() public async Task<Response<IOcelotConfiguration>> Get()
{ {
var repoConfig = await _repo.Get(); var repoConfig = await _config.Get();
if (repoConfig.IsError) if (repoConfig.IsError)
{ {

View File

@ -0,0 +1,80 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Setter;
using Ocelot.Logging;
namespace Ocelot.Configuration.Repository
{
public class ConsulFileConfigurationPoller : IDisposable
{
private IOcelotLogger _logger;
private IFileConfigurationRepository _repo;
private IFileConfigurationSetter _setter;
private string _previousAsJson;
private Timer _timer;
private bool _polling;
public ConsulFileConfigurationPoller(IOcelotLoggerFactory factory, IFileConfigurationRepository repo, IFileConfigurationSetter setter)
{
_setter = setter;
_logger = factory.CreateLogger<ConsulFileConfigurationPoller>();
_repo = repo;
_previousAsJson = "";
_timer = new Timer(async x =>
{
if(_polling)
{
return;
}
_polling = true;
await Poll();
_polling = false;
}, null, 0, 1000);
}
private async Task Poll()
{
_logger.LogDebug("Started polling consul");
var fileConfig = await _repo.Get();
if(fileConfig.IsError)
{
_logger.LogDebug($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}");
return;
}
var asJson = ToJson(fileConfig.Data);
if(!fileConfig.IsError && asJson != _previousAsJson)
{
await _setter.Set(fileConfig.Data);
_previousAsJson = asJson;
}
_logger.LogDebug("Finished polling consul");
}
/// <summary>
/// We could do object comparison here but performance isnt really a problem. This might be an issue one day!
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
private string ToJson(FileConfiguration config)
{
var currentHash = JsonConvert.SerializeObject(config);
return currentHash;
}
public void Dispose()
{
_timer.Dispose();
}
}
}

View File

@ -3,19 +3,20 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Consul; using Consul;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
namespace Ocelot.Configuration.Repository namespace Ocelot.Configuration.Repository
{ {
public class ConsulOcelotConfigurationRepository : IOcelotConfigurationRepository
public class ConsulFileConfigurationRepository : IFileConfigurationRepository
{ {
private readonly ConsulClient _consul; private readonly ConsulClient _consul;
private string _ocelotConfiguration = "OcelotConfiguration"; private string _ocelotConfiguration = "OcelotConfiguration";
private readonly Cache.IOcelotCache<IOcelotConfiguration> _cache; private readonly Cache.IOcelotCache<FileConfiguration> _cache;
public ConsulFileConfigurationRepository(Cache.IOcelotCache<FileConfiguration> cache, ServiceProviderConfiguration serviceProviderConfig)
public ConsulOcelotConfigurationRepository(Cache.IOcelotCache<IOcelotConfiguration> cache, ServiceProviderConfiguration serviceProviderConfig)
{ {
var consulHost = string.IsNullOrEmpty(serviceProviderConfig?.ServiceProviderHost) ? "localhost" : serviceProviderConfig?.ServiceProviderHost; var consulHost = string.IsNullOrEmpty(serviceProviderConfig?.ServiceProviderHost) ? "localhost" : serviceProviderConfig?.ServiceProviderHost;
var consulPort = serviceProviderConfig?.ServiceProviderPort ?? 8500; var consulPort = serviceProviderConfig?.ServiceProviderPort ?? 8500;
@ -27,32 +28,32 @@ namespace Ocelot.Configuration.Repository
}); });
} }
public async Task<Response<IOcelotConfiguration>> Get() public async Task<Response<FileConfiguration>> Get()
{ {
var config = _cache.Get(_ocelotConfiguration, _ocelotConfiguration); var config = _cache.Get(_ocelotConfiguration, _ocelotConfiguration);
if (config != null) if (config != null)
{ {
return new OkResponse<IOcelotConfiguration>(config); return new OkResponse<FileConfiguration>(config);
} }
var queryResult = await _consul.KV.Get(_ocelotConfiguration); var queryResult = await _consul.KV.Get(_ocelotConfiguration);
if (queryResult.Response == null) if (queryResult.Response == null)
{ {
return new OkResponse<IOcelotConfiguration>(null); return new OkResponse<FileConfiguration>(null);
} }
var bytes = queryResult.Response.Value; var bytes = queryResult.Response.Value;
var json = Encoding.UTF8.GetString(bytes); var json = Encoding.UTF8.GetString(bytes);
var consulConfig = JsonConvert.DeserializeObject<OcelotConfiguration>(json); var consulConfig = JsonConvert.DeserializeObject<FileConfiguration>(json);
return new OkResponse<IOcelotConfiguration>(consulConfig); return new OkResponse<FileConfiguration>(consulConfig);
} }
public async Task<Response> AddOrReplace(IOcelotConfiguration ocelotConfiguration) public async Task<Response> Set(FileConfiguration ocelotConfiguration)
{ {
var json = JsonConvert.SerializeObject(ocelotConfiguration); var json = JsonConvert.SerializeObject(ocelotConfiguration);
@ -72,7 +73,7 @@ namespace Ocelot.Configuration.Repository
return new OkResponse(); return new OkResponse();
} }
return new ErrorResponse(new UnableToSetConfigInConsulError("Unable to set config in consul")); return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}"));
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Responses; using Ocelot.Responses;
@ -8,7 +9,7 @@ namespace Ocelot.Configuration.Repository
public class FileConfigurationRepository : IFileConfigurationRepository public class FileConfigurationRepository : IFileConfigurationRepository
{ {
private static readonly object _lock = new object(); private static readonly object _lock = new object();
public Response<FileConfiguration> Get() public async Task<Response<FileConfiguration>> Get()
{ {
var configFilePath = $"{AppContext.BaseDirectory}/configuration.json"; var configFilePath = $"{AppContext.BaseDirectory}/configuration.json";
string json = string.Empty; string json = string.Empty;
@ -20,7 +21,7 @@ namespace Ocelot.Configuration.Repository
return new OkResponse<FileConfiguration>(fileConfiguration); return new OkResponse<FileConfiguration>(fileConfiguration);
} }
public Response Set(FileConfiguration fileConfiguration) public async Task<Response> Set(FileConfiguration fileConfiguration)
{ {
var configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; var configurationPath = $"{AppContext.BaseDirectory}/configuration.json";

View File

@ -1,3 +1,4 @@
using System.Threading.Tasks;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Responses; using Ocelot.Responses;
@ -5,7 +6,7 @@ namespace Ocelot.Configuration.Repository
{ {
public interface IFileConfigurationRepository public interface IFileConfigurationRepository
{ {
Response<FileConfiguration> Get(); Task<Response<FileConfiguration>> Get();
Response Set(FileConfiguration fileConfiguration); Task<Response> Set(FileConfiguration fileConfiguration);
} }
} }

View File

@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Configuration.File;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Configuration.Repository namespace Ocelot.Configuration.Repository

View File

@ -22,7 +22,7 @@ namespace Ocelot.Configuration.Setter
public async Task<Response> Set(FileConfiguration fileConfig) public async Task<Response> Set(FileConfiguration fileConfig)
{ {
var response = _repo.Set(fileConfig); var response = await _repo.Set(fileConfig);
if(response.IsError) if(response.IsError)
{ {

View File

@ -21,9 +21,9 @@ namespace Ocelot.Controllers
} }
[HttpGet] [HttpGet]
public IActionResult Get() public async Task<IActionResult> Get()
{ {
var response = _configGetter.Get(); var response = await _configGetter.Get();
if(response.IsError) if(response.IsError)
{ {

View File

@ -63,7 +63,8 @@ namespace Ocelot.DependencyInjection
.Build(); .Build();
services.AddSingleton<ServiceProviderConfiguration>(config); services.AddSingleton<ServiceProviderConfiguration>(config);
services.AddSingleton<IOcelotConfigurationRepository, ConsulOcelotConfigurationRepository>(); services.AddSingleton<ConsulFileConfigurationPoller>();
services.AddSingleton<IFileConfigurationRepository, ConsulFileConfigurationRepository>();
return services; return services;
} }
@ -90,6 +91,11 @@ namespace Ocelot.DependencyInjection
services.TryAddSingleton<ICacheManager<IOcelotConfiguration>>(ocelotConfigCacheManagerOutputCache); services.TryAddSingleton<ICacheManager<IOcelotConfiguration>>(ocelotConfigCacheManagerOutputCache);
services.TryAddSingleton<IOcelotCache<IOcelotConfiguration>>(ocelotConfigCacheManager); services.TryAddSingleton<IOcelotCache<IOcelotConfiguration>>(ocelotConfigCacheManager);
var fileConfigCacheManagerOutputCache = CacheFactory.Build<FileConfiguration>("FileConfigurationCache", settings);
var fileConfigCacheManager = new OcelotCacheManagerCache<FileConfiguration>(fileConfigCacheManagerOutputCache);
services.TryAddSingleton<ICacheManager<FileConfiguration>>(fileConfigCacheManagerOutputCache);
services.TryAddSingleton<IOcelotCache<FileConfiguration>>(fileConfigCacheManager);
services.Configure<FileConfiguration>(configurationRoot); services.Configure<FileConfiguration>(configurationRoot);
services.TryAddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>(); services.TryAddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
services.TryAddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>(); services.TryAddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();

View File

@ -35,6 +35,14 @@ namespace Ocelot.Infrastructure.RequestData
{ {
object obj; object obj;
if(_httpContextAccessor.HttpContext == null || _httpContextAccessor.HttpContext.Items == null)
{
return new ErrorResponse<T>(new List<Error>
{
new CannotFindDataError($"Unable to find data for key: {key} because HttpContext or HttpContext.Items is null")
});
}
if(_httpContextAccessor.HttpContext.Items.TryGetValue(key, out obj)) if(_httpContextAccessor.HttpContext.Items.TryGetValue(key, out obj))
{ {
var data = (T) obj; var data = (T) obj;

View File

@ -29,10 +29,13 @@ namespace Ocelot.Middleware
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Configuration.Provider; using Ocelot.Configuration.Provider;
using Ocelot.Configuration.Repository;
using Ocelot.Configuration.Setter; using Ocelot.Configuration.Setter;
using Ocelot.LoadBalancer.Middleware; using Ocelot.LoadBalancer.Middleware;
using Ocelot.Responses;
public static class OcelotMiddlewareExtensions public static class OcelotMiddlewareExtensions
{ {
@ -56,7 +59,9 @@ namespace Ocelot.Middleware
/// <returns></returns> /// <returns></returns>
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration)
{ {
await CreateAdministrationArea(builder); var configuration = await CreateConfiguration(builder);
await CreateAdministrationArea(builder, configuration);
ConfigureDiagnosticListener(builder); ConfigureDiagnosticListener(builder);
@ -155,7 +160,34 @@ namespace Ocelot.Middleware
if (ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError) if (ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError)
{ {
var config = await configSetter.Set(fileConfig.Value); Response config = null;
var fileConfigRepo = builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository));
if (fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository))
{
var consulFileConfigRepo = (ConsulFileConfigurationRepository) fileConfigRepo;
var ocelotConfigurationRepository =
(IOcelotConfigurationRepository) builder.ApplicationServices.GetService(
typeof(IOcelotConfigurationRepository));
var ocelotConfigurationCreator =
(IOcelotConfigurationCreator) builder.ApplicationServices.GetService(
typeof(IOcelotConfigurationCreator));
var fileConfigFromConsul = await consulFileConfigRepo.Get();
if (fileConfigFromConsul.Data == null)
{
config = await configSetter.Set(fileConfig.Value);
}
else
{
var ocelotConfig = await ocelotConfigurationCreator.Create(fileConfigFromConsul.Data);
config = await ocelotConfigurationRepository.AddOrReplace(ocelotConfig.Data);
var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller));
}
}
else
{
config = await configSetter.Set(fileConfig.Value);
}
if (config == null || config.IsError) if (config == null || config.IsError)
{ {
@ -173,10 +205,8 @@ namespace Ocelot.Middleware
return ocelotConfiguration.Data; return ocelotConfiguration.Data;
} }
private static async Task CreateAdministrationArea(IApplicationBuilder builder) private static async Task CreateAdministrationArea(IApplicationBuilder builder, IOcelotConfiguration configuration)
{ {
var configuration = await CreateConfiguration(builder);
var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration)); var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration));
if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null) if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null)

View File

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -20,7 +22,7 @@ namespace Ocelot.AcceptanceTests
private IWebHost _builder; private IWebHost _builder;
private readonly Steps _steps; private readonly Steps _steps;
private IWebHost _fakeConsulBuilder; private IWebHost _fakeConsulBuilder;
private IOcelotConfiguration _config; private FileConfiguration _config;
public ConfigurationInConsul() public ConfigurationInConsul()
{ {
@ -67,7 +69,7 @@ namespace Ocelot.AcceptanceTests
} }
[Fact] [Fact]
public void should_fix_issue_142() public void should_load_configuration_out_of_consul()
{ {
var consulPort = 8500; var consulPort = 8500;
var configuration = new FileConfiguration var configuration = new FileConfiguration
@ -84,28 +86,31 @@ namespace Ocelot.AcceptanceTests
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var serviceProviderConfig = new ServiceProviderConfigurationBuilder() var consulConfig = new FileConfiguration
.WithServiceDiscoveryProviderHost("localhost") {
.WithServiceDiscoveryProviderPort(consulPort) ReRoutes = new List<FileReRoute>
.Build(); {
new FileReRoute
{
DownstreamPathTemplate = "/status",
DownstreamScheme = "http",
DownstreamHost = "localhost",
DownstreamPort = 51779,
UpstreamPathTemplate = "/cs/status",
UpstreamHttpMethod = new List<string> {"Get"}
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort
}
}
};
var reRoute = new ReRouteBuilder() this.Given(x => GivenTheConsulConfigurationIs(consulConfig))
.WithDownstreamPathTemplate("/status")
.WithUpstreamTemplatePattern("^(?i)/cs/status/$")
.WithDownstreamScheme("http")
.WithDownstreamHost("localhost")
.WithDownstreamPort(51779)
.WithUpstreamPathTemplate("/cs/status")
.WithUpstreamHttpMethod(new List<string> {"Get"})
.WithReRouteKey("/cs/status|Get")
.WithHttpHandlerOptions(new HttpHandlerOptions(true, false))
.Build();
var reRoutes = new List<ReRoute> { reRoute };
var config = new OcelotConfiguration(reRoutes, null, serviceProviderConfig);
this.Given(x => GivenTheConsulConfigurationIs(config))
.And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura")) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
@ -116,7 +121,95 @@ namespace Ocelot.AcceptanceTests
.BDDfy(); .BDDfy();
} }
private void GivenTheConsulConfigurationIs(OcelotConfiguration config)
[Fact]
public void should_load_configuration_out_of_consul_if_it_is_changed()
{
var consulPort = 8506;
var configuration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort
}
}
};
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var consulConfig = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/status",
DownstreamScheme = "http",
DownstreamHost = "localhost",
DownstreamPort = 51779,
UpstreamPathTemplate = "/cs/status",
UpstreamHttpMethod = new List<string> {"Get"}
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort
}
}
};
var secondConsulConfig = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/status",
DownstreamScheme = "http",
DownstreamHost = "localhost",
DownstreamPort = 51779,
UpstreamPathTemplate = "/cs/status/awesome",
UpstreamHttpMethod = new List<string> {"Get"}
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort
}
}
};
this.Given(x => GivenTheConsulConfigurationIs(consulConfig))
.And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => GivenTheConsulConfigurationIs(secondConsulConfig))
.And(x => GivenIWaitForTheConfigToReplicateToOcelot())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
private void GivenIWaitForTheConfigToReplicateToOcelot()
{
Thread.Sleep(10000);
}
private void GivenTheConsulConfigurationIs(FileConfiguration config)
{ {
_config = config; _config = config;
} }
@ -154,7 +247,7 @@ namespace Ocelot.AcceptanceTests
var json = reader.ReadToEnd(); var json = reader.ReadToEnd();
_config = JsonConvert.DeserializeObject<OcelotConfiguration>(json); _config = JsonConvert.DeserializeObject<FileConfiguration>(json);
var response = JsonConvert.SerializeObject(true); var response = JsonConvert.SerializeObject(true);

View File

@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Moq;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Repository;
using Ocelot.Configuration.Setter;
using Ocelot.Logging;
using Ocelot.Responses;
using TestStack.BDDfy;
using Xunit;
using Shouldly;
using static Ocelot.UnitTests.Wait;
namespace Ocelot.UnitTests.Configuration
{
public class ConsulFileConfigurationPollerTests : IDisposable
{
private ConsulFileConfigurationPoller _poller;
private Mock<IOcelotLoggerFactory> _factory;
private Mock<IFileConfigurationRepository> _repo;
private Mock<IFileConfigurationSetter> _setter;
private FileConfiguration _fileConfig;
public ConsulFileConfigurationPollerTests()
{
var logger = new Mock<IOcelotLogger>();
_factory = new Mock<IOcelotLoggerFactory>();
_factory.Setup(x => x.CreateLogger<ConsulFileConfigurationPoller>()).Returns(logger.Object);
_repo = new Mock<IFileConfigurationRepository>();
_setter = new Mock<IFileConfigurationSetter>();
_fileConfig = new FileConfiguration();
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(_fileConfig));
_poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object);
}
public void Dispose()
{
_poller.Dispose();
}
[Fact]
public void should_start()
{
this.Given(x => ThenTheSetterIsCalled(_fileConfig))
.BDDfy();
}
[Fact]
public void should_call_setter_when_gets_new_config()
{
var newConfig = new FileConfiguration {
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamHost = "test"
}
}
};
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig))
.Then(x => ThenTheSetterIsCalled(newConfig))
.BDDfy();
}
private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig)
{
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(newConfig));
}
private void ThenTheSetterIsCalled(FileConfiguration fileConfig)
{
var result = WaitFor(2000).Until(() => {
try
{
_setter.Verify(x => x.Set(fileConfig), Times.Once);
return true;
}
catch(Exception ex)
{
return false;
}
});
result.ShouldBeTrue();
}
}
}

View File

@ -45,12 +45,12 @@ namespace Ocelot.UnitTests.Configuration
_fileConfiguration = fileConfiguration; _fileConfiguration = fileConfiguration;
_repo _repo
.Setup(x => x.Get()) .Setup(x => x.Get())
.Returns(new OkResponse<FileConfiguration>(fileConfiguration)); .ReturnsAsync(new OkResponse<FileConfiguration>(fileConfiguration));
} }
private void WhenIGetTheReRoutes() private void WhenIGetTheReRoutes()
{ {
_result = _provider.Get().Data; _result = _provider.Get().Result.Data;
} }
private void ThenTheRepoIsCalledCorrectly() private void ThenTheRepoIsCalledCorrectly()

View File

@ -100,7 +100,7 @@ namespace Ocelot.UnitTests.Configuration
private void WhenISetTheConfiguration() private void WhenISetTheConfiguration()
{ {
_repo.Set(_fileConfiguration); _repo.Set(_fileConfiguration);
_result = _repo.Get().Data; _result = _repo.Get().Result.Data;
} }
private void ThenTheConfigurationIsStoredAs(FileConfiguration expected) private void ThenTheConfigurationIsStoredAs(FileConfiguration expected)
@ -135,7 +135,7 @@ namespace Ocelot.UnitTests.Configuration
private void WhenIGetTheReRoutes() private void WhenIGetTheReRoutes()
{ {
_result = _repo.Get().Data; _result = _repo.Get().Result.Data;
} }
private void ThenTheFollowingIsReturned(FileConfiguration expected) private void ThenTheFollowingIsReturned(FileConfiguration expected)

View File

@ -78,7 +78,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
_repo _repo
.Setup(x => x.Set(It.IsAny<FileConfiguration>())) .Setup(x => x.Set(It.IsAny<FileConfiguration>()))
.Returns(response); .ReturnsAsync(response);
} }
private void ThenAnErrorResponseIsReturned() private void ThenAnErrorResponseIsReturned()

View File

@ -107,12 +107,12 @@ namespace Ocelot.UnitTests.Controllers
{ {
_configGetter _configGetter
.Setup(x => x.Get()) .Setup(x => x.Get())
.Returns(fileConfiguration); .ReturnsAsync(fileConfiguration);
} }
private void WhenIGetTheFileConfiguration() private void WhenIGetTheFileConfiguration()
{ {
_result = _controller.Get(); _result = _controller.Get().Result;
} }
private void TheTheGetFileConfigurationIsCalledCorrectly() private void TheTheGetFileConfigurationIsCalledCorrectly()

View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.DependencyInjection;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.DependencyInjection
{
public class ServiceCollectionExtensionTests
{
private Exception _ex;
[Fact]
public void should_set_up_services()
{
this.When(x => WhenISetUpOcelotServices())
.Then(x => ThenAnExceptionIsntThrown())
.BDDfy();
}
private void WhenISetUpOcelotServices()
{
try
{
IWebHostBuilder builder = new WebHostBuilder();
IConfigurationRoot configRoot = new ConfigurationRoot(new List<IConfigurationProvider>());
IServiceCollection services = new ServiceCollection();
services.AddSingleton(builder);
services.AddOcelot(configRoot);
}
catch (Exception e)
{
_ex = e;
}
}
private void ThenAnExceptionIsntThrown()
{
_ex.ShouldBeNull();
}
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Diagnostics;
namespace Ocelot.UnitTests
{
public class Wait
{
public static Waiter WaitFor(int milliSeconds)
{
return new Waiter(milliSeconds);
}
}
public class Waiter
{
private readonly int _milliSeconds;
public Waiter(int milliSeconds)
{
_milliSeconds = milliSeconds;
}
public bool Until(Func<bool> condition)
{
var stopwatch = Stopwatch.StartNew();
var passed = false;
while (stopwatch.ElapsedMilliseconds < _milliSeconds)
{
if (condition.Invoke())
{
passed = true;
break;
}
}
return passed;
}
public bool Until<T>(Func<bool> condition)
{
var stopwatch = Stopwatch.StartNew();
var passed = false;
while (stopwatch.ElapsedMilliseconds < _milliSeconds)
{
if (condition.Invoke())
{
passed = true;
break;
}
}
return passed;
}
}
}