From c3cd181b90fb5d5353b886073b3b7c66c12c6bab Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 16 Apr 2017 19:35:59 +0100 Subject: [PATCH] added a new implementation that stores the ocelot config in consul kv store, had to change some major things and add cache settings as default --- src/Ocelot/Cache/IOcelotCache.cs | 1 + src/Ocelot/Cache/OcelotCacheManagerCache.cs | 12 + .../Creator/RateLimitOptionsCreator.cs | 2 +- .../Provider/IOcelotConfigurationProvider.cs | 5 +- .../Provider/OcelotConfigurationProvider.cs | 7 +- src/Ocelot/Configuration/QoSOptions.cs | 11 +- src/Ocelot/Configuration/RateLimitOptions.cs | 24 +- src/Ocelot/Configuration/RateLimitRule.cs | 25 ++ src/Ocelot/Configuration/ReRoute.cs | 22 +- .../ConsulOcelotConfigurationRepository.cs | 79 +++++ .../IOcelotConfigurationRepository.cs | 7 +- .../InMemoryOcelotConfigurationRepository.cs | 7 +- .../UnableToSetConfigInConsulError.cs | 12 + .../Setter/FileConfigurationSetter.cs | 2 +- .../ServiceCollectionExtensions.cs | 22 +- .../Finder/DownstreamRouteFinder.cs | 5 +- .../Finder/IDownstreamRouteFinder.cs | 5 +- .../DownstreamRouteFinderMiddleware.cs | 2 +- .../Middleware/ExceptionHandlerMiddleware.cs | 142 ++++----- src/Ocelot/Errors/OcelotErrorCode.cs | 3 +- .../Middleware/OcelotMiddlewareExtensions.cs | 17 +- .../Middleware/ClientRateLimitMiddleware.cs | 284 +++++++++--------- src/Ocelot/RateLimit/RateLimitCore.cs | 6 +- .../Requester/QoS/IQoSProviderFactory.cs | 6 +- .../ConfigurationInConsulTests.cs | 171 +++++++++++ test/Ocelot.AcceptanceTests/Steps.cs | 21 +- test/Ocelot.ManualTest/Startup.cs | 4 +- .../FileConfigurationCreatorTests.cs | 6 +- .../InMemoryConfigurationRepositoryTests.cs | 4 +- .../OcelotConfigurationProviderTests.cs | 4 +- .../RateLimitOptionsCreatorTests.cs | 4 +- .../DownstreamRouteFinderMiddlewareTests.cs | 2 +- .../DownstreamRouteFinderTests.cs | 4 +- .../ClientRateLimitMiddlewareTests.cs | 4 +- 34 files changed, 623 insertions(+), 309 deletions(-) create mode 100644 src/Ocelot/Configuration/RateLimitRule.cs create mode 100644 src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs create mode 100644 src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs create mode 100644 test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs diff --git a/src/Ocelot/Cache/IOcelotCache.cs b/src/Ocelot/Cache/IOcelotCache.cs index 1ceb220c..9ac26d8f 100644 --- a/src/Ocelot/Cache/IOcelotCache.cs +++ b/src/Ocelot/Cache/IOcelotCache.cs @@ -5,6 +5,7 @@ namespace Ocelot.Cache public interface IOcelotCache { void Add(string key, T value, TimeSpan ttl); + void AddAndDelete(string key, T value, TimeSpan ttl); T Get(string key); } } diff --git a/src/Ocelot/Cache/OcelotCacheManagerCache.cs b/src/Ocelot/Cache/OcelotCacheManagerCache.cs index 84bdc679..e92d678d 100644 --- a/src/Ocelot/Cache/OcelotCacheManagerCache.cs +++ b/src/Ocelot/Cache/OcelotCacheManagerCache.cs @@ -17,6 +17,18 @@ namespace Ocelot.Cache _cacheManager.Add(new CacheItem(key, value, ExpirationMode.Absolute, ttl)); } + public void AddAndDelete(string key, T value, TimeSpan ttl) + { + var exists = _cacheManager.Get(key); + + if (exists != null) + { + _cacheManager.Remove(key); + } + + _cacheManager.Add(new CacheItem(key, value, ExpirationMode.Absolute, ttl)); + } + public T Get(string key) { return _cacheManager.Get(key); diff --git a/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs b/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs index 68bf2a33..c9eedcbd 100644 --- a/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs @@ -21,7 +21,7 @@ namespace Ocelot.Configuration.Creator .WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage) .WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix) .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, - TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), + fileReRoute.RateLimitOptions.PeriodTimespan, fileReRoute.RateLimitOptions.Limit)) .Build(); } diff --git a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs index 30ded2e9..3256e44a 100644 --- a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs @@ -1,9 +1,10 @@ -using Ocelot.Responses; +using System.Threading.Tasks; +using Ocelot.Responses; namespace Ocelot.Configuration.Provider { public interface IOcelotConfigurationProvider { - Response Get(); + Task> Get(); } } diff --git a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs index e416ed8e..b03bb9af 100644 --- a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs @@ -1,4 +1,5 @@ -using Ocelot.Configuration.Repository; +using System.Threading.Tasks; +using Ocelot.Configuration.Repository; using Ocelot.Responses; namespace Ocelot.Configuration.Provider @@ -15,9 +16,9 @@ namespace Ocelot.Configuration.Provider _repo = repo; } - public Response Get() + public async Task> Get() { - var repoConfig = _repo.Get(); + var repoConfig = await _repo.Get(); if (repoConfig.IsError) { diff --git a/src/Ocelot/Configuration/QoSOptions.cs b/src/Ocelot/Configuration/QoSOptions.cs index 29145888..1c519632 100644 --- a/src/Ocelot/Configuration/QoSOptions.cs +++ b/src/Ocelot/Configuration/QoSOptions.cs @@ -1,5 +1,4 @@ -using System; -using Polly.Timeout; +using Polly.Timeout; namespace Ocelot.Configuration { @@ -12,17 +11,17 @@ namespace Ocelot.Configuration TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) { ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; - DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak); - TimeoutValue = TimeSpan.FromMilliseconds(timeoutValue); + DurationOfBreak = durationofBreak; + TimeoutValue = timeoutValue; TimeoutStrategy = timeoutStrategy; } public int ExceptionsAllowedBeforeBreaking { get; private set; } - public TimeSpan DurationOfBreak { get; private set; } + public int DurationOfBreak { get; private set; } - public TimeSpan TimeoutValue { get; private set; } + public int TimeoutValue { get; private set; } public TimeoutStrategy TimeoutStrategy { get; private set; } diff --git a/src/Ocelot/Configuration/RateLimitOptions.cs b/src/Ocelot/Configuration/RateLimitOptions.cs index 85a3bf78..fdf54d52 100644 --- a/src/Ocelot/Configuration/RateLimitOptions.cs +++ b/src/Ocelot/Configuration/RateLimitOptions.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -59,25 +58,4 @@ namespace Ocelot.Configuration /// public bool DisableRateLimitHeaders { get; private set; } } - - public class RateLimitRule - { - public RateLimitRule(string period, TimeSpan periodTimespan, long limit) - { - Period = period; - PeriodTimespan = periodTimespan; - Limit = limit; - } - - /// - /// Rate limit period as in 1s, 1m, 1h,1d - /// - public string Period { get; private set; } - - public TimeSpan PeriodTimespan { get; private set; } - /// - /// Maximum number of requests that a client can make in a defined period - /// - public long Limit { get; private set; } - } } diff --git a/src/Ocelot/Configuration/RateLimitRule.cs b/src/Ocelot/Configuration/RateLimitRule.cs new file mode 100644 index 00000000..d4acfa31 --- /dev/null +++ b/src/Ocelot/Configuration/RateLimitRule.cs @@ -0,0 +1,25 @@ +using System; + +namespace Ocelot.Configuration +{ + public class RateLimitRule + { + public RateLimitRule(string period, double periodTimespan, long limit) + { + Period = period; + PeriodTimespan = periodTimespan; + Limit = limit; + } + + /// + /// Rate limit period as in 1s, 1m, 1h,1d + /// + public string Period { get; private set; } + + public double PeriodTimespan { get; private set; } + /// + /// Maximum number of requests that a client can make in a defined period + /// + public long Limit { get; private set; } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 97bf8e39..bba8c925 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -7,12 +7,12 @@ namespace Ocelot.Configuration public class ReRoute { public ReRoute(PathTemplate downstreamPathTemplate, - PathTemplate upstreamTemplate, + PathTemplate upstreamPathTemplate, HttpMethod upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, - List configurationHeaderExtractorProperties, + List claimsToHeaders, List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, @@ -27,8 +27,8 @@ namespace Ocelot.Configuration string reRouteKey, ServiceProviderConfiguration serviceProviderConfiguraion, bool isQos, - QoSOptions qos, - bool enableRateLimit, + QoSOptions qosOptions, + bool enableEndpointRateLimiting, RateLimitOptions ratelimitOptions) { ReRouteKey = reRouteKey; @@ -37,7 +37,7 @@ namespace Ocelot.Configuration DownstreamHost = downstreamHost; DownstreamPort = downstreamPort; DownstreamPathTemplate = downstreamPathTemplate; - UpstreamPathTemplate = upstreamTemplate; + UpstreamPathTemplate = upstreamPathTemplate; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; IsAuthenticated = isAuthenticated; @@ -51,12 +51,12 @@ namespace Ocelot.Configuration ?? new List(); ClaimsToClaims = claimsToClaims ?? new List(); - ClaimsToHeaders = configurationHeaderExtractorProperties + ClaimsToHeaders = claimsToHeaders ?? new List(); - DownstreamScheme = downstreamScheme; + DownstreamScheme = downstreamScheme; IsQos = isQos; - QosOptions = qos; - EnableEndpointRateLimiting = enableRateLimit; + QosOptionsOptions = qosOptions; + EnableEndpointEndpointRateLimiting = enableEndpointRateLimiting; RateLimitOptions = ratelimitOptions; } @@ -77,12 +77,12 @@ namespace Ocelot.Configuration public CacheOptions FileCacheOptions { get; private set; } public string DownstreamScheme {get;private set;} public bool IsQos { get; private set; } - public QoSOptions QosOptions { get; private set; } + public QoSOptions QosOptionsOptions { get; private set; } public string LoadBalancer {get;private set;} public string DownstreamHost { get; private set; } public int DownstreamPort { get; private set; } public ServiceProviderConfiguration ServiceProviderConfiguraion { get; private set; } - public bool EnableEndpointRateLimiting { get; private set; } + public bool EnableEndpointEndpointRateLimiting { get; private set; } public RateLimitOptions RateLimitOptions { get; private set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs new file mode 100644 index 00000000..703d039b --- /dev/null +++ b/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs @@ -0,0 +1,79 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using Consul; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Ocelot.Responses; +using Ocelot.ServiceDiscovery; + +namespace Ocelot.Configuration.Repository +{ + public class ConsulOcelotConfigurationRepository : IOcelotConfigurationRepository + { + private readonly ConsulClient _consul; + private ConsulRegistryConfiguration _configuration; + private string _ocelotConfiguration = "OcelotConfiguration"; + private Cache.IOcelotCache _cache; + + public ConsulOcelotConfigurationRepository(ConsulRegistryConfiguration consulRegistryConfiguration, Cache.IOcelotCache cache) + { + var consulHost = string.IsNullOrEmpty(consulRegistryConfiguration?.HostName) ? "localhost" : consulRegistryConfiguration.HostName; + var consulPort = consulRegistryConfiguration?.Port ?? 8500; + _configuration = new ConsulRegistryConfiguration(consulHost, consulPort, consulRegistryConfiguration?.ServiceName); + _cache = cache; + _consul = new ConsulClient(config => + { + config.Address = new Uri($"http://{_configuration.HostName}:{_configuration.Port}"); + }); + } + + public async Task> Get() + { + var config = _cache.Get(_ocelotConfiguration); + + if (config != null) + { + return new OkResponse(config); + } + + var queryResult = await _consul.KV.Get(_ocelotConfiguration); + + if (queryResult.Response == null) + { + return new OkResponse(null); + } + + var bytes = queryResult.Response.Value; + + var json = Encoding.UTF8.GetString(bytes); + + var consulConfig = JsonConvert.DeserializeObject(json); + + return new OkResponse(consulConfig); + } + + public async Task AddOrReplace(IOcelotConfiguration ocelotConfiguration) + { + var json = JsonConvert.SerializeObject(ocelotConfiguration); + + var bytes = Encoding.UTF8.GetBytes(json); + + var kvPair = new KVPair(_ocelotConfiguration) + { + Value = bytes + }; + + var result = await _consul.KV.Put(kvPair); + + if (result.Response) + { + _cache.AddAndDelete(_ocelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3)); + + return new OkResponse(); + } + + return new ErrorResponse(new UnableToSetConfigInConsulError("Unable to set config in consul")); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs index 312b5553..4ee56d43 100644 --- a/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs @@ -1,10 +1,11 @@ -using Ocelot.Responses; +using System.Threading.Tasks; +using Ocelot.Responses; namespace Ocelot.Configuration.Repository { public interface IOcelotConfigurationRepository { - Response Get(); - Response AddOrReplace(IOcelotConfiguration ocelotConfiguration); + Task> Get(); + Task AddOrReplace(IOcelotConfiguration ocelotConfiguration); } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs index 04f34f8d..bb4c8e76 100644 --- a/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs @@ -1,4 +1,5 @@ -using Ocelot.Responses; +using System.Threading.Tasks; +using Ocelot.Responses; namespace Ocelot.Configuration.Repository { @@ -11,12 +12,12 @@ namespace Ocelot.Configuration.Repository private IOcelotConfiguration _ocelotConfiguration; - public Response Get() + public async Task> Get() { return new OkResponse(_ocelotConfiguration); } - public Response AddOrReplace(IOcelotConfiguration ocelotConfiguration) + public async Task AddOrReplace(IOcelotConfiguration ocelotConfiguration) { lock (LockObject) { diff --git a/src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs b/src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs new file mode 100644 index 00000000..60c68fe2 --- /dev/null +++ b/src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.Configuration.Repository +{ + public class UnableToSetConfigInConsulError : Error + { + public UnableToSetConfigInConsulError(string message) + : base(message, OcelotErrorCode.UnableToSetConfigInConsulError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs index 93dd4d85..163dd5f5 100644 --- a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs +++ b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs @@ -33,7 +33,7 @@ namespace Ocelot.Configuration.Setter if(!config.IsError) { - _configRepo.AddOrReplace(config.Data); + await _configRepo.AddOrReplace(config.Data); } return new ErrorResponse(config.Errors); diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 911003a8..e975dcb6 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -40,24 +40,32 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Reflection; +using Ocelot.Configuration; using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider; namespace Ocelot.DependencyInjection { public static class ServiceCollectionExtensions { - public static IServiceCollection AddOcelotOutputCaching(this IServiceCollection services, Action settings) + public static IServiceCollection AddOcelotStoreConfigurationInConsul(this IServiceCollection services, ConsulRegistryConfiguration consulConfig) { - var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); - var ocelotCacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); - services.TryAddSingleton>(cacheManagerOutputCache); - services.TryAddSingleton>(ocelotCacheManager); - + services.AddSingleton(consulConfig); + services.AddSingleton(); return services; } - public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot) + public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot, Action settings) { + var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); + var ocelotOutputCacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); + services.TryAddSingleton>(cacheManagerOutputCache); + services.TryAddSingleton>(ocelotOutputCacheManager); + + var ocelotConfigCacheManagerOutputCache = CacheFactory.Build("OcelotConfigurationCache", settings); + var ocelotConfigCacheManager = new OcelotCacheManagerCache(ocelotConfigCacheManagerOutputCache); + services.TryAddSingleton>(ocelotConfigCacheManagerOutputCache); + services.TryAddSingleton>(ocelotConfigCacheManager); + services.Configure(configurationRoot); services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index b0de27ff..82a5e7c5 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Ocelot.Configuration.Provider; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Errors; @@ -21,9 +22,9 @@ namespace Ocelot.DownstreamRouteFinder.Finder _urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; } - public Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod) + public async Task> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod) { - var configuration = _configProvider.Get(); + var configuration = await _configProvider.Get(); var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod.Method.ToLower(), upstreamHttpMethod.ToLower(), StringComparison.CurrentCultureIgnoreCase)); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs index e351ab2f..7ae3ff79 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs @@ -1,9 +1,10 @@ -using Ocelot.Responses; +using System.Threading.Tasks; +using Ocelot.Responses; namespace Ocelot.DownstreamRouteFinder.Finder { public interface IDownstreamRouteFinder { - Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod); + Task> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod); } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index a3a4776d..c107a2f5 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -36,7 +36,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware _logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); - var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method); + var downstreamRoute = await _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method); if (downstreamRoute.IsError) { diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index dc36fd94..afe5f508 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -1,73 +1,73 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; - -namespace Ocelot.Errors.Middleware -{ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; + +namespace Ocelot.Errors.Middleware +{ /// /// Catches all unhandled exceptions thrown by middleware, logs and returns a 500 - /// - public class ExceptionHandlerMiddleware - { - private readonly RequestDelegate _next; - private readonly IOcelotLogger _logger; - private readonly IRequestScopedDataRepository _requestScopedDataRepository; - - public ExceptionHandlerMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository) - { - _next = next; - _requestScopedDataRepository = requestScopedDataRepository; - _logger = loggerFactory.CreateLogger(); - } - - public async Task Invoke(HttpContext context) - { - try - { - _logger.LogDebug("ocelot pipeline started"); - - _logger.LogDebug("calling next middleware"); - - await _next.Invoke(context); - - _logger.LogDebug("succesfully called middleware"); - } - catch (Exception e) - { - _logger.LogDebug("error calling middleware"); - - var message = CreateMessage(context, e); - _logger.LogError(message, e); - SetInternalServerErrorOnResponse(context); - } - - _logger.LogDebug("ocelot pipeline finished"); - } - - private void SetInternalServerErrorOnResponse(HttpContext context) - { - context.Response.OnStarting(x => - { - context.Response.StatusCode = 500; - return Task.CompletedTask; - }, context); - } - - private string CreateMessage(HttpContext context, Exception e) - { - var message = - $"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}"; - - if (e.InnerException != null) - { - message = - $"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}"; - } - return $"{message} RequestId: {context.TraceIdentifier}"; - } - } -} + /// + public class ExceptionHandlerMiddleware + { + private readonly RequestDelegate _next; + private readonly IOcelotLogger _logger; + private readonly IRequestScopedDataRepository _requestScopedDataRepository; + + public ExceptionHandlerMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IRequestScopedDataRepository requestScopedDataRepository) + { + _next = next; + _requestScopedDataRepository = requestScopedDataRepository; + _logger = loggerFactory.CreateLogger(); + } + + public async Task Invoke(HttpContext context) + { + try + { + _logger.LogDebug("ocelot pipeline started"); + + _logger.LogDebug("calling next middleware"); + + await _next.Invoke(context); + + _logger.LogDebug("succesfully called middleware"); + } + catch (Exception e) + { + _logger.LogDebug("error calling middleware"); + + var message = CreateMessage(context, e); + _logger.LogError(message, e); + SetInternalServerErrorOnResponse(context); + } + + _logger.LogDebug("ocelot pipeline finished"); + } + + private void SetInternalServerErrorOnResponse(HttpContext context) + { + context.Response.OnStarting(x => + { + context.Response.StatusCode = 500; + return Task.CompletedTask; + }, context); + } + + private string CreateMessage(HttpContext context, Exception e) + { + var message = + $"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}"; + + if (e.InnerException != null) + { + message = + $"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}"; + } + return $"{message} RequestId: {context.TraceIdentifier}"; + } + } +} diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index 373ac441..c1c55dbb 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -27,6 +27,7 @@ UnableToFindServiceDiscoveryProviderError, UnableToFindLoadBalancerError, RequestTimedOutError, - UnableToFindQoSProviderError + UnableToFindQoSProviderError, + UnableToSetConfigInConsulError } } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index e51c0c88..457c2448 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -140,15 +140,20 @@ namespace Ocelot.Middleware var configSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider)); - - var config = await configSetter.Set(fileConfig.Value); - - if(config == null || config.IsError) + + var ocelotConfiguration = await configProvider.Get(); + + if (ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError) { - throw new Exception("Unable to start Ocelot: configuration was not set up correctly."); + var config = await configSetter.Set(fileConfig.Value); + + if (config == null || config.IsError) + { + throw new Exception("Unable to start Ocelot: configuration was not set up correctly."); + } } - var ocelotConfiguration = configProvider.Get(); + ocelotConfiguration = await configProvider.Get(); if(ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError) { diff --git a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs index 02a37e36..dffc6448 100644 --- a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs +++ b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs @@ -1,153 +1,153 @@ -using Ocelot.Middleware; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Ocelot.Infrastructure.RequestData; -using Microsoft.AspNetCore.Http; -using Ocelot.Logging; -using Ocelot.Configuration; - -namespace Ocelot.RateLimit.Middleware -{ - public class ClientRateLimitMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IOcelotLogger _logger; - private readonly IRateLimitCounterHandler _counterHandler; - private readonly ClientRateLimitProcessor _processor; - - public ClientRateLimitMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, - IRateLimitCounterHandler counterHandler) - : base(requestScopedDataRepository) - { - _next = next; - _logger = loggerFactory.CreateLogger(); - _counterHandler = counterHandler; - _processor = new ClientRateLimitProcessor(counterHandler); - } - - public async Task Invoke(HttpContext context) +using Ocelot.Middleware; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Infrastructure.RequestData; +using Microsoft.AspNetCore.Http; +using Ocelot.Logging; +using Ocelot.Configuration; + +namespace Ocelot.RateLimit.Middleware +{ + public class ClientRateLimitMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IOcelotLogger _logger; + private readonly IRateLimitCounterHandler _counterHandler; + private readonly ClientRateLimitProcessor _processor; + + public ClientRateLimitMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IRequestScopedDataRepository requestScopedDataRepository, + IRateLimitCounterHandler counterHandler) + : base(requestScopedDataRepository) { - _logger.TraceMiddlewareEntry(); - - var options = DownstreamRoute.ReRoute.RateLimitOptions; - // check if rate limiting is enabled - if (!DownstreamRoute.ReRoute.EnableEndpointRateLimiting) - { + _next = next; + _logger = loggerFactory.CreateLogger(); + _counterHandler = counterHandler; + _processor = new ClientRateLimitProcessor(counterHandler); + } + + public async Task Invoke(HttpContext context) + { + _logger.TraceMiddlewareEntry(); + + var options = DownstreamRoute.ReRoute.RateLimitOptions; + // check if rate limiting is enabled + if (!DownstreamRoute.ReRoute.EnableEndpointEndpointRateLimiting) + { _logger.LogDebug($"EndpointRateLimiting is not enabled for {DownstreamRoute.ReRoute.DownstreamPathTemplate}"); - _logger.TraceInvokeNext(); - await _next.Invoke(context); - _logger.TraceInvokeNextCompleted(); - _logger.TraceMiddlewareCompleted(); - return; - } - // compute identity from request - var identity = SetIdentity(context, options); - - // check white list - if (IsWhitelisted(identity, options)) - { + _logger.TraceInvokeNext(); + await _next.Invoke(context); + _logger.TraceInvokeNextCompleted(); + _logger.TraceMiddlewareCompleted(); + return; + } + // compute identity from request + var identity = SetIdentity(context, options); + + // check white list + if (IsWhitelisted(identity, options)) + { _logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate} is white listed from rate limiting"); _logger.TraceInvokeNext(); - await _next.Invoke(context); - _logger.TraceInvokeNextCompleted(); - _logger.TraceMiddlewareCompleted(); - return; - } - - var rule = options.RateLimitRule; - if (rule.Limit > 0) - { - // increment counter - var counter = _processor.ProcessRequest(identity, options); - - // check if limit is reached - if (counter.TotalRequests > rule.Limit) - { - //compute retry after value - var retryAfter = _processor.RetryAfterFrom(counter.Timestamp, rule); - - // log blocked request - LogBlockedRequest(context, identity, counter, rule); - - // break execution - await ReturnQuotaExceededResponse(context, options, retryAfter); - _logger.TraceMiddlewareCompleted(); - return; - } - } - //set X-Rate-Limit headers for the longest period - if (!options.DisableRateLimitHeaders) - { - var headers = _processor.GetRateLimitHeaders( context,identity, options); - context.Response.OnStarting(SetRateLimitHeaders, state: headers); + await _next.Invoke(context); + _logger.TraceInvokeNextCompleted(); + _logger.TraceMiddlewareCompleted(); + return; + } + + var rule = options.RateLimitRule; + if (rule.Limit > 0) + { + // increment counter + var counter = _processor.ProcessRequest(identity, options); + + // check if limit is reached + if (counter.TotalRequests > rule.Limit) + { + //compute retry after value + var retryAfter = _processor.RetryAfterFrom(counter.Timestamp, rule); + + // log blocked request + LogBlockedRequest(context, identity, counter, rule); + + // break execution + await ReturnQuotaExceededResponse(context, options, retryAfter); + _logger.TraceMiddlewareCompleted(); + return; + } + } + //set X-Rate-Limit headers for the longest period + if (!options.DisableRateLimitHeaders) + { + var headers = _processor.GetRateLimitHeaders( context,identity, options); + context.Response.OnStarting(SetRateLimitHeaders, state: headers); } _logger.TraceInvokeNext(); await _next.Invoke(context); _logger.TraceInvokeNextCompleted(); - _logger.TraceMiddlewareCompleted(); - } - - public virtual ClientRequestIdentity SetIdentity(HttpContext httpContext, RateLimitOptions option) - { - var clientId = "client"; - if (httpContext.Request.Headers.Keys.Contains(option.ClientIdHeader)) - { - clientId = httpContext.Request.Headers[option.ClientIdHeader].First(); - } - - return new ClientRequestIdentity( - clientId, - httpContext.Request.Path.ToString().ToLowerInvariant(), - httpContext.Request.Method.ToLowerInvariant() - ); - } - - public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option) - { - if (option.ClientWhitelist.Contains(requestIdentity.ClientId)) - { - return true; - } - return false; - } - - public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) - { - _logger.LogDebug($"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { DownstreamRoute.ReRoute.UpstreamPathTemplate }, TraceIdentifier {httpContext.TraceIdentifier}."); - } - - public virtual Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter) - { - var message = string.IsNullOrEmpty(option.QuotaExceededMessage) ? $"API calls quota exceeded! maximum admitted {option.RateLimitRule.Limit} per {option.RateLimitRule.Period}." : option.QuotaExceededMessage; - - if (!option.DisableRateLimitHeaders) - { - httpContext.Response.Headers["Retry-After"] = retryAfter; - } - - httpContext.Response.StatusCode = option.HttpStatusCode; - return httpContext.Response.WriteAsync(message); - } - - private Task SetRateLimitHeaders(object rateLimitHeaders) - { - var headers = (RateLimitHeaders)rateLimitHeaders; - - headers.Context.Response.Headers["X-Rate-Limit-Limit"] = headers.Limit; - headers.Context.Response.Headers["X-Rate-Limit-Remaining"] = headers.Remaining; - headers.Context.Response.Headers["X-Rate-Limit-Reset"] = headers.Reset; - - return Task.CompletedTask; - } - - } -} - - + _logger.TraceMiddlewareCompleted(); + } + + public virtual ClientRequestIdentity SetIdentity(HttpContext httpContext, RateLimitOptions option) + { + var clientId = "client"; + if (httpContext.Request.Headers.Keys.Contains(option.ClientIdHeader)) + { + clientId = httpContext.Request.Headers[option.ClientIdHeader].First(); + } + + return new ClientRequestIdentity( + clientId, + httpContext.Request.Path.ToString().ToLowerInvariant(), + httpContext.Request.Method.ToLowerInvariant() + ); + } + + public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + if (option.ClientWhitelist.Contains(requestIdentity.ClientId)) + { + return true; + } + return false; + } + + public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) + { + _logger.LogDebug($"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { DownstreamRoute.ReRoute.UpstreamPathTemplate }, TraceIdentifier {httpContext.TraceIdentifier}."); + } + + public virtual Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter) + { + var message = string.IsNullOrEmpty(option.QuotaExceededMessage) ? $"API calls quota exceeded! maximum admitted {option.RateLimitRule.Limit} per {option.RateLimitRule.Period}." : option.QuotaExceededMessage; + + if (!option.DisableRateLimitHeaders) + { + httpContext.Response.Headers["Retry-After"] = retryAfter; + } + + httpContext.Response.StatusCode = option.HttpStatusCode; + return httpContext.Response.WriteAsync(message); + } + + private Task SetRateLimitHeaders(object rateLimitHeaders) + { + var headers = (RateLimitHeaders)rateLimitHeaders; + + headers.Context.Response.Headers["X-Rate-Limit-Limit"] = headers.Limit; + headers.Context.Response.Headers["X-Rate-Limit-Remaining"] = headers.Remaining; + headers.Context.Response.Headers["X-Rate-Limit-Reset"] = headers.Reset; + + return Task.CompletedTask; + } + + } +} + + diff --git a/src/Ocelot/RateLimit/RateLimitCore.cs b/src/Ocelot/RateLimit/RateLimitCore.cs index 07627f8d..d8c91c8b 100644 --- a/src/Ocelot/RateLimit/RateLimitCore.cs +++ b/src/Ocelot/RateLimit/RateLimitCore.cs @@ -34,7 +34,7 @@ namespace Ocelot.RateLimit if (entry.HasValue) { // entry has not expired - if (entry.Value.Timestamp + rule.PeriodTimespan >= DateTime.UtcNow) + if (entry.Value.Timestamp + TimeSpan.FromSeconds(rule.PeriodTimespan) >= DateTime.UtcNow) { // increment request count var totalRequests = entry.Value.TotalRequests + 1; @@ -45,7 +45,7 @@ namespace Ocelot.RateLimit } } // stores: id (string) - timestamp (datetime) - total_requests (long) - _counterHandler.Set(counterId, counter, rule.PeriodTimespan); + _counterHandler.Set(counterId, counter, TimeSpan.FromSeconds(rule.PeriodTimespan)); } return counter; @@ -95,7 +95,7 @@ namespace Ocelot.RateLimit public string RetryAfterFrom(DateTime timestamp, RateLimitRule rule) { var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds); - var retryAfter = Convert.ToInt32(rule.PeriodTimespan.TotalSeconds); + var retryAfter = Convert.ToInt32(TimeSpan.FromSeconds(rule.PeriodTimespan).TotalSeconds); retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1; return retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture); } diff --git a/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs b/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs index d1f63ea1..feabfc09 100644 --- a/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs +++ b/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs @@ -57,15 +57,15 @@ namespace Ocelot.Requester.QoS { _logger = loggerFactory.CreateLogger(); - _timeoutPolicy = Policy.TimeoutAsync(reRoute.QosOptions.TimeoutValue, reRoute.QosOptions.TimeoutStrategy); + _timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(reRoute.QosOptionsOptions.TimeoutValue), reRoute.QosOptionsOptions.TimeoutStrategy); _circuitBreakerPolicy = Policy .Handle() .Or() .Or() .CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking: reRoute.QosOptions.ExceptionsAllowedBeforeBreaking, - durationOfBreak: reRoute.QosOptions.DurationOfBreak, + exceptionsAllowedBeforeBreaking: reRoute.QosOptionsOptions.ExceptionsAllowedBeforeBreaking, + durationOfBreak: TimeSpan.FromMilliseconds(reRoute.QosOptionsOptions.DurationOfBreak), onBreak: (ex, breakDelay) => { _logger.LogError( diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs new file mode 100644 index 00000000..b2dfdc80 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using Consul; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; +using Ocelot.Configuration; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; +using Ocelot.ServiceDiscovery; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class ConfigurationInConsul : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private IWebHost _fakeConsulBuilder; + private IOcelotConfiguration _config; + + public ConfigurationInConsul() + { + _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", + DownstreamHost = "localhost", + DownstreamPort = 51779, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = "Get", + + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Provider = "Consul", + Host = "localhost", + Port = 9500 + } + } + }; + + var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; + + var consulConfig = new ConsulRegistryConfiguration("localhost", 9500, "Ocelot"); + + 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(consulConfig)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + 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/OcelotConfiguration") + { + 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/OcelotConfiguration") + { + 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 => "OcelotConfiguration"; + public int Flags => 0; + public string Value { get; private set; } + public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e"; + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + 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.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 39b3465d..79a586ac 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -15,9 +15,11 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; using Ocelot.DependencyInjection; using Ocelot.ManualTest; using Ocelot.Middleware; +using Ocelot.ServiceDiscovery; using Shouldly; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; @@ -84,6 +86,22 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } + public void GivenOcelotIsRunningUsingConsulToStoreConfig(ConsulRegistryConfiguration consulConfig) + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddOcelotStoreConfigurationInConsul(consulConfig); + }); + + _ocelotServer = new TestServer(_webHostBuilder + .UseStartup()); + + _ocelotClient = _ocelotServer.CreateClient(); + } + internal void ThenTheResponseShouldBe(FileConfiguration expected) { var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); @@ -138,8 +156,7 @@ namespace Ocelot.AcceptanceTests .WithDictionaryHandle(); }; - s.AddOcelotOutputCaching(settings); - s.AddOcelot(configuration); + s.AddOcelot(configuration, settings); }) .ConfigureLogging(l => { diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index b0cd318c..aa0661b6 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -37,8 +37,8 @@ namespace Ocelot.ManualTest }) .WithDictionaryHandle(); }; - services.AddOcelotOutputCaching(settings); - services.AddOcelot(Configuration); + + services.AddOcelot(Configuration, settings); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index a636fe5f..5f8a37ad 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -690,10 +690,10 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheQosOptionsAre(QoSOptions qosOptions) { - _config.Data.ReRoutes[0].QosOptions.DurationOfBreak.ShouldBe(qosOptions.DurationOfBreak); + _config.Data.ReRoutes[0].QosOptionsOptions.DurationOfBreak.ShouldBe(qosOptions.DurationOfBreak); - _config.Data.ReRoutes[0].QosOptions.ExceptionsAllowedBeforeBreaking.ShouldBe(qosOptions.ExceptionsAllowedBeforeBreaking); - _config.Data.ReRoutes[0].QosOptions.TimeoutValue.ShouldBe(qosOptions.TimeoutValue); + _config.Data.ReRoutes[0].QosOptionsOptions.ExceptionsAllowedBeforeBreaking.ShouldBe(qosOptions.ExceptionsAllowedBeforeBreaking); + _config.Data.ReRoutes[0].QosOptionsOptions.TimeoutValue.ShouldBe(qosOptions.TimeoutValue); } } } diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index 8d28b112..1ef4cf6b 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -49,7 +49,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenIGetTheConfiguration() { - _getResult = _repo.Get(); + _getResult = _repo.Get().Result; } private void GivenThereIsASavedConfiguration() @@ -65,7 +65,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenIAddOrReplaceTheConfig() { - _result = _repo.AddOrReplace(_config); + _result = _repo.AddOrReplace(_config).Result; } private void ThenNoErrorsAreReturned() diff --git a/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs index 9be55087..430844fe 100644 --- a/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs @@ -53,12 +53,12 @@ namespace Ocelot.UnitTests.Configuration { _configurationRepository .Setup(x => x.Get()) - .Returns(config); + .ReturnsAsync(config); } private void WhenIGetTheConfig() { - _result = _ocelotConfigurationProvider.Get(); + _result = _ocelotConfigurationProvider.Get().Result; } private void TheFollowingIsReturned(Response expected) diff --git a/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs index 027de7d4..7d2234e2 100644 --- a/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs @@ -59,7 +59,7 @@ namespace Ocelot.UnitTests.Configuration .WithQuotaExceededMessage("QuotaExceededMessage") .WithRateLimitCounterPrefix("RateLimitCounterPrefix") .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, - TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), + fileReRoute.RateLimitOptions.PeriodTimespan, fileReRoute.RateLimitOptions.Limit)) .Build(); @@ -102,7 +102,7 @@ namespace Ocelot.UnitTests.Configuration _result.RateLimitCounterPrefix.ShouldBe(expected.RateLimitCounterPrefix); _result.RateLimitRule.Limit.ShouldBe(expected.RateLimitRule.Limit); _result.RateLimitRule.Period.ShouldBe(expected.RateLimitRule.Period); - _result.RateLimitRule.PeriodTimespan.Ticks.ShouldBe(expected.RateLimitRule.PeriodTimespan.Ticks); + TimeSpan.FromSeconds(_result.RateLimitRule.PeriodTimespan).Ticks.ShouldBe(TimeSpan.FromSeconds(expected.RateLimitRule.PeriodTimespan).Ticks); } } } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 90a35ad8..e7718f37 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -90,7 +90,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _downstreamRoute = new OkResponse(downstreamRoute); _downstreamRouteFinder .Setup(x => x.FindDownstreamRoute(It.IsAny(), It.IsAny())) - .Returns(_downstreamRoute); + .ReturnsAsync(_downstreamRoute); } public void Dispose() diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 49e9b01b..0d272c63 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -199,7 +199,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _reRoutesConfig = reRoutesConfig; _mockConfig .Setup(x => x.Get()) - .Returns(new OkResponse(new OcelotConfiguration(_reRoutesConfig, adminPath))); + .ReturnsAsync(new OkResponse(new OcelotConfiguration(_reRoutesConfig, adminPath))); } private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) @@ -209,7 +209,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void WhenICallTheFinder() { - _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod); + _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod).Result; } private void ThenTheFollowingIsReturned(DownstreamRoute expected) diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index 8b9b40ee..81be28cc 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -71,7 +71,7 @@ namespace Ocelot.UnitTests.RateLimit { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule("1s", TimeSpan.FromSeconds(100), 3), 429)) + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule("1s", 100, 3), 429)) .WithUpstreamHttpMethod("Get") .Build()); @@ -88,7 +88,7 @@ namespace Ocelot.UnitTests.RateLimit { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new RateLimitRule( "1s", TimeSpan.FromSeconds(100),3),429)) + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new RateLimitRule( "1s", 100,3),429)) .WithUpstreamHttpMethod("Get") .Build());