Merge pull request #85 from TomPallister/feature/config-in-consul

added a new implementation that stores the ocelot config in consul kv…
This commit is contained in:
Tom Pallister 2017-04-16 19:45:54 +01:00 committed by GitHub
commit 8d31b40c21
34 changed files with 623 additions and 309 deletions

View File

@ -5,6 +5,7 @@ namespace Ocelot.Cache
public interface IOcelotCache<T> public interface IOcelotCache<T>
{ {
void Add(string key, T value, TimeSpan ttl); void Add(string key, T value, TimeSpan ttl);
void AddAndDelete(string key, T value, TimeSpan ttl);
T Get(string key); T Get(string key);
} }
} }

View File

@ -17,6 +17,18 @@ namespace Ocelot.Cache
_cacheManager.Add(new CacheItem<T>(key, value, ExpirationMode.Absolute, ttl)); _cacheManager.Add(new CacheItem<T>(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<T>(key, value, ExpirationMode.Absolute, ttl));
}
public T Get(string key) public T Get(string key)
{ {
return _cacheManager.Get<T>(key); return _cacheManager.Get<T>(key);

View File

@ -21,7 +21,7 @@ namespace Ocelot.Configuration.Creator
.WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage) .WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage)
.WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix) .WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix)
.WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period,
TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), fileReRoute.RateLimitOptions.PeriodTimespan,
fileReRoute.RateLimitOptions.Limit)) fileReRoute.RateLimitOptions.Limit))
.Build(); .Build();
} }

View File

@ -1,9 +1,10 @@
using Ocelot.Responses; using System.Threading.Tasks;
using Ocelot.Responses;
namespace Ocelot.Configuration.Provider namespace Ocelot.Configuration.Provider
{ {
public interface IOcelotConfigurationProvider public interface IOcelotConfigurationProvider
{ {
Response<IOcelotConfiguration> Get(); Task<Response<IOcelotConfiguration>> Get();
} }
} }

View File

@ -1,4 +1,5 @@
using Ocelot.Configuration.Repository; using System.Threading.Tasks;
using Ocelot.Configuration.Repository;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Configuration.Provider namespace Ocelot.Configuration.Provider
@ -15,9 +16,9 @@ namespace Ocelot.Configuration.Provider
_repo = repo; _repo = repo;
} }
public Response<IOcelotConfiguration> Get() public async Task<Response<IOcelotConfiguration>> Get()
{ {
var repoConfig = _repo.Get(); var repoConfig = await _repo.Get();
if (repoConfig.IsError) if (repoConfig.IsError)
{ {

View File

@ -1,5 +1,4 @@
using System; using Polly.Timeout;
using Polly.Timeout;
namespace Ocelot.Configuration namespace Ocelot.Configuration
{ {
@ -12,17 +11,17 @@ namespace Ocelot.Configuration
TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic)
{ {
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak); DurationOfBreak = durationofBreak;
TimeoutValue = TimeSpan.FromMilliseconds(timeoutValue); TimeoutValue = timeoutValue;
TimeoutStrategy = timeoutStrategy; TimeoutStrategy = timeoutStrategy;
} }
public int ExceptionsAllowedBeforeBreaking { get; private set; } 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; } public TimeoutStrategy TimeoutStrategy { get; private set; }

View File

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -59,25 +58,4 @@ namespace Ocelot.Configuration
/// </summary> /// </summary>
public bool DisableRateLimitHeaders { get; private set; } public bool DisableRateLimitHeaders { get; private set; }
} }
public class RateLimitRule
{
public RateLimitRule(string period, TimeSpan periodTimespan, long limit)
{
Period = period;
PeriodTimespan = periodTimespan;
Limit = limit;
}
/// <summary>
/// Rate limit period as in 1s, 1m, 1h,1d
/// </summary>
public string Period { get; private set; }
public TimeSpan PeriodTimespan { get; private set; }
/// <summary>
/// Maximum number of requests that a client can make in a defined period
/// </summary>
public long Limit { get; private set; }
}
} }

View File

@ -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;
}
/// <summary>
/// Rate limit period as in 1s, 1m, 1h,1d
/// </summary>
public string Period { get; private set; }
public double PeriodTimespan { get; private set; }
/// <summary>
/// Maximum number of requests that a client can make in a defined period
/// </summary>
public long Limit { get; private set; }
}
}

View File

@ -7,12 +7,12 @@ namespace Ocelot.Configuration
public class ReRoute public class ReRoute
{ {
public ReRoute(PathTemplate downstreamPathTemplate, public ReRoute(PathTemplate downstreamPathTemplate,
PathTemplate upstreamTemplate, PathTemplate upstreamPathTemplate,
HttpMethod upstreamHttpMethod, HttpMethod upstreamHttpMethod,
string upstreamTemplatePattern, string upstreamTemplatePattern,
bool isAuthenticated, bool isAuthenticated,
AuthenticationOptions authenticationOptions, AuthenticationOptions authenticationOptions,
List<ClaimToThing> configurationHeaderExtractorProperties, List<ClaimToThing> claimsToHeaders,
List<ClaimToThing> claimsToClaims, List<ClaimToThing> claimsToClaims,
Dictionary<string, string> routeClaimsRequirement, Dictionary<string, string> routeClaimsRequirement,
bool isAuthorised, bool isAuthorised,
@ -27,8 +27,8 @@ namespace Ocelot.Configuration
string reRouteKey, string reRouteKey,
ServiceProviderConfiguration serviceProviderConfiguraion, ServiceProviderConfiguration serviceProviderConfiguraion,
bool isQos, bool isQos,
QoSOptions qos, QoSOptions qosOptions,
bool enableRateLimit, bool enableEndpointRateLimiting,
RateLimitOptions ratelimitOptions) RateLimitOptions ratelimitOptions)
{ {
ReRouteKey = reRouteKey; ReRouteKey = reRouteKey;
@ -37,7 +37,7 @@ namespace Ocelot.Configuration
DownstreamHost = downstreamHost; DownstreamHost = downstreamHost;
DownstreamPort = downstreamPort; DownstreamPort = downstreamPort;
DownstreamPathTemplate = downstreamPathTemplate; DownstreamPathTemplate = downstreamPathTemplate;
UpstreamPathTemplate = upstreamTemplate; UpstreamPathTemplate = upstreamPathTemplate;
UpstreamHttpMethod = upstreamHttpMethod; UpstreamHttpMethod = upstreamHttpMethod;
UpstreamTemplatePattern = upstreamTemplatePattern; UpstreamTemplatePattern = upstreamTemplatePattern;
IsAuthenticated = isAuthenticated; IsAuthenticated = isAuthenticated;
@ -51,12 +51,12 @@ namespace Ocelot.Configuration
?? new List<ClaimToThing>(); ?? new List<ClaimToThing>();
ClaimsToClaims = claimsToClaims ClaimsToClaims = claimsToClaims
?? new List<ClaimToThing>(); ?? new List<ClaimToThing>();
ClaimsToHeaders = configurationHeaderExtractorProperties ClaimsToHeaders = claimsToHeaders
?? new List<ClaimToThing>(); ?? new List<ClaimToThing>();
DownstreamScheme = downstreamScheme; DownstreamScheme = downstreamScheme;
IsQos = isQos; IsQos = isQos;
QosOptions = qos; QosOptionsOptions = qosOptions;
EnableEndpointRateLimiting = enableRateLimit; EnableEndpointEndpointRateLimiting = enableEndpointRateLimiting;
RateLimitOptions = ratelimitOptions; RateLimitOptions = ratelimitOptions;
} }
@ -77,12 +77,12 @@ namespace Ocelot.Configuration
public CacheOptions FileCacheOptions { get; private set; } public CacheOptions FileCacheOptions { get; private set; }
public string DownstreamScheme {get;private set;} public string DownstreamScheme {get;private set;}
public bool IsQos { 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 LoadBalancer {get;private set;}
public string DownstreamHost { get; private set; } public string DownstreamHost { get; private set; }
public int DownstreamPort { get; private set; } public int DownstreamPort { get; private set; }
public ServiceProviderConfiguration ServiceProviderConfiguraion { 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; } public RateLimitOptions RateLimitOptions { get; private set; }
} }
} }

View File

@ -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<IOcelotConfiguration> _cache;
public ConsulOcelotConfigurationRepository(ConsulRegistryConfiguration consulRegistryConfiguration, Cache.IOcelotCache<IOcelotConfiguration> 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<Response<IOcelotConfiguration>> Get()
{
var config = _cache.Get(_ocelotConfiguration);
if (config != null)
{
return new OkResponse<IOcelotConfiguration>(config);
}
var queryResult = await _consul.KV.Get(_ocelotConfiguration);
if (queryResult.Response == null)
{
return new OkResponse<IOcelotConfiguration>(null);
}
var bytes = queryResult.Response.Value;
var json = Encoding.UTF8.GetString(bytes);
var consulConfig = JsonConvert.DeserializeObject<OcelotConfiguration>(json);
return new OkResponse<IOcelotConfiguration>(consulConfig);
}
public async Task<Response> 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"));
}
}
}

View File

@ -1,10 +1,11 @@
using Ocelot.Responses; using System.Threading.Tasks;
using Ocelot.Responses;
namespace Ocelot.Configuration.Repository namespace Ocelot.Configuration.Repository
{ {
public interface IOcelotConfigurationRepository public interface IOcelotConfigurationRepository
{ {
Response<IOcelotConfiguration> Get(); Task<Response<IOcelotConfiguration>> Get();
Response AddOrReplace(IOcelotConfiguration ocelotConfiguration); Task<Response> AddOrReplace(IOcelotConfiguration ocelotConfiguration);
} }
} }

View File

@ -1,4 +1,5 @@
using Ocelot.Responses; using System.Threading.Tasks;
using Ocelot.Responses;
namespace Ocelot.Configuration.Repository namespace Ocelot.Configuration.Repository
{ {
@ -11,12 +12,12 @@ namespace Ocelot.Configuration.Repository
private IOcelotConfiguration _ocelotConfiguration; private IOcelotConfiguration _ocelotConfiguration;
public Response<IOcelotConfiguration> Get() public async Task<Response<IOcelotConfiguration>> Get()
{ {
return new OkResponse<IOcelotConfiguration>(_ocelotConfiguration); return new OkResponse<IOcelotConfiguration>(_ocelotConfiguration);
} }
public Response AddOrReplace(IOcelotConfiguration ocelotConfiguration) public async Task<Response> AddOrReplace(IOcelotConfiguration ocelotConfiguration)
{ {
lock (LockObject) lock (LockObject)
{ {

View File

@ -0,0 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.Configuration.Repository
{
public class UnableToSetConfigInConsulError : Error
{
public UnableToSetConfigInConsulError(string message)
: base(message, OcelotErrorCode.UnableToSetConfigInConsulError)
{
}
}
}

View File

@ -33,7 +33,7 @@ namespace Ocelot.Configuration.Setter
if(!config.IsError) if(!config.IsError)
{ {
_configRepo.AddOrReplace(config.Data); await _configRepo.AddOrReplace(config.Data);
} }
return new ErrorResponse(config.Errors); return new ErrorResponse(config.Errors);

View File

@ -40,24 +40,32 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Reflection; using System.Reflection;
using Ocelot.Configuration;
using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider; using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider;
namespace Ocelot.DependencyInjection namespace Ocelot.DependencyInjection
{ {
public static class ServiceCollectionExtensions public static class ServiceCollectionExtensions
{ {
public static IServiceCollection AddOcelotOutputCaching(this IServiceCollection services, Action<ConfigurationBuilderCachePart> settings) public static IServiceCollection AddOcelotStoreConfigurationInConsul(this IServiceCollection services, ConsulRegistryConfiguration consulConfig)
{ {
var cacheManagerOutputCache = CacheFactory.Build<HttpResponseMessage>("OcelotOutputCache", settings); services.AddSingleton<ConsulRegistryConfiguration>(consulConfig);
var ocelotCacheManager = new OcelotCacheManagerCache<HttpResponseMessage>(cacheManagerOutputCache); services.AddSingleton<IOcelotConfigurationRepository, ConsulOcelotConfigurationRepository>();
services.TryAddSingleton<ICacheManager<HttpResponseMessage>>(cacheManagerOutputCache);
services.TryAddSingleton<IOcelotCache<HttpResponseMessage>>(ocelotCacheManager);
return services; return services;
} }
public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot) public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot, Action<ConfigurationBuilderCachePart> settings)
{ {
var cacheManagerOutputCache = CacheFactory.Build<HttpResponseMessage>("OcelotOutputCache", settings);
var ocelotOutputCacheManager = new OcelotCacheManagerCache<HttpResponseMessage>(cacheManagerOutputCache);
services.TryAddSingleton<ICacheManager<HttpResponseMessage>>(cacheManagerOutputCache);
services.TryAddSingleton<IOcelotCache<HttpResponseMessage>>(ocelotOutputCacheManager);
var ocelotConfigCacheManagerOutputCache = CacheFactory.Build<IOcelotConfiguration>("OcelotConfigurationCache", settings);
var ocelotConfigCacheManager = new OcelotCacheManagerCache<IOcelotConfiguration>(ocelotConfigCacheManagerOutputCache);
services.TryAddSingleton<ICacheManager<IOcelotConfiguration>>(ocelotConfigCacheManagerOutputCache);
services.TryAddSingleton<IOcelotCache<IOcelotConfiguration>>(ocelotConfigCacheManager);
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

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Ocelot.Configuration.Provider; using Ocelot.Configuration.Provider;
using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Errors; using Ocelot.Errors;
@ -21,9 +22,9 @@ namespace Ocelot.DownstreamRouteFinder.Finder
_urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; _urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder;
} }
public Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod) public async Task<Response<DownstreamRoute>> 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)); var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod.Method.ToLower(), upstreamHttpMethod.ToLower(), StringComparison.CurrentCultureIgnoreCase));

View File

@ -1,9 +1,10 @@
using Ocelot.Responses; using System.Threading.Tasks;
using Ocelot.Responses;
namespace Ocelot.DownstreamRouteFinder.Finder namespace Ocelot.DownstreamRouteFinder.Finder
{ {
public interface IDownstreamRouteFinder public interface IDownstreamRouteFinder
{ {
Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod); Task<Response<DownstreamRoute>> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod);
} }
} }

View File

@ -36,7 +36,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
_logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); _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) if (downstreamRoute.IsError)
{ {

View File

@ -27,6 +27,7 @@
UnableToFindServiceDiscoveryProviderError, UnableToFindServiceDiscoveryProviderError,
UnableToFindLoadBalancerError, UnableToFindLoadBalancerError,
RequestTimedOutError, RequestTimedOutError,
UnableToFindQoSProviderError UnableToFindQoSProviderError,
UnableToSetConfigInConsulError
} }
} }

View File

@ -141,14 +141,19 @@ namespace Ocelot.Middleware
var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider)); var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider));
var config = await configSetter.Set(fileConfig.Value); var ocelotConfiguration = await configProvider.Get();
if(config == null || config.IsError) 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) if(ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError)
{ {

View File

@ -35,7 +35,7 @@ namespace Ocelot.RateLimit.Middleware
var options = DownstreamRoute.ReRoute.RateLimitOptions; var options = DownstreamRoute.ReRoute.RateLimitOptions;
// check if rate limiting is enabled // check if rate limiting is enabled
if (!DownstreamRoute.ReRoute.EnableEndpointRateLimiting) if (!DownstreamRoute.ReRoute.EnableEndpointEndpointRateLimiting)
{ {
_logger.LogDebug($"EndpointRateLimiting is not enabled for {DownstreamRoute.ReRoute.DownstreamPathTemplate}"); _logger.LogDebug($"EndpointRateLimiting is not enabled for {DownstreamRoute.ReRoute.DownstreamPathTemplate}");

View File

@ -34,7 +34,7 @@ namespace Ocelot.RateLimit
if (entry.HasValue) if (entry.HasValue)
{ {
// entry has not expired // 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 // increment request count
var totalRequests = entry.Value.TotalRequests + 1; var totalRequests = entry.Value.TotalRequests + 1;
@ -45,7 +45,7 @@ namespace Ocelot.RateLimit
} }
} }
// stores: id (string) - timestamp (datetime) - total_requests (long) // stores: id (string) - timestamp (datetime) - total_requests (long)
_counterHandler.Set(counterId, counter, rule.PeriodTimespan); _counterHandler.Set(counterId, counter, TimeSpan.FromSeconds(rule.PeriodTimespan));
} }
return counter; return counter;
@ -95,7 +95,7 @@ namespace Ocelot.RateLimit
public string RetryAfterFrom(DateTime timestamp, RateLimitRule rule) public string RetryAfterFrom(DateTime timestamp, RateLimitRule rule)
{ {
var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds); 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; retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1;
return retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture); return retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture);
} }

View File

@ -57,15 +57,15 @@ namespace Ocelot.Requester.QoS
{ {
_logger = loggerFactory.CreateLogger<PollyQoSProvider>(); _logger = loggerFactory.CreateLogger<PollyQoSProvider>();
_timeoutPolicy = Policy.TimeoutAsync(reRoute.QosOptions.TimeoutValue, reRoute.QosOptions.TimeoutStrategy); _timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(reRoute.QosOptionsOptions.TimeoutValue), reRoute.QosOptionsOptions.TimeoutStrategy);
_circuitBreakerPolicy = Policy _circuitBreakerPolicy = Policy
.Handle<HttpRequestException>() .Handle<HttpRequestException>()
.Or<TimeoutRejectedException>() .Or<TimeoutRejectedException>()
.Or<TimeoutException>() .Or<TimeoutException>()
.CircuitBreakerAsync( .CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: reRoute.QosOptions.ExceptionsAllowedBeforeBreaking, exceptionsAllowedBeforeBreaking: reRoute.QosOptionsOptions.ExceptionsAllowedBeforeBreaking,
durationOfBreak: reRoute.QosOptions.DurationOfBreak, durationOfBreak: TimeSpan.FromMilliseconds(reRoute.QosOptionsOptions.DurationOfBreak),
onBreak: (ex, breakDelay) => onBreak: (ex, breakDelay) =>
{ {
_logger.LogError( _logger.LogError(

View File

@ -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<FileReRoute>
{
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<OcelotConfiguration>(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();
}
}
}

View File

@ -15,9 +15,11 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Configuration.Repository;
using Ocelot.DependencyInjection; using Ocelot.DependencyInjection;
using Ocelot.ManualTest; using Ocelot.ManualTest;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.ServiceDiscovery;
using Shouldly; using Shouldly;
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
@ -84,6 +86,22 @@ namespace Ocelot.AcceptanceTests
_ocelotClient = _ocelotServer.CreateClient(); _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<Startup>());
_ocelotClient = _ocelotServer.CreateClient();
}
internal void ThenTheResponseShouldBe(FileConfiguration expected) internal void ThenTheResponseShouldBe(FileConfiguration expected)
{ {
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result); var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
@ -138,8 +156,7 @@ namespace Ocelot.AcceptanceTests
.WithDictionaryHandle(); .WithDictionaryHandle();
}; };
s.AddOcelotOutputCaching(settings); s.AddOcelot(configuration, settings);
s.AddOcelot(configuration);
}) })
.ConfigureLogging(l => .ConfigureLogging(l =>
{ {

View File

@ -37,8 +37,8 @@ namespace Ocelot.ManualTest
}) })
.WithDictionaryHandle(); .WithDictionaryHandle();
}; };
services.AddOcelotOutputCaching(settings);
services.AddOcelot(Configuration); services.AddOcelot(Configuration, settings);
} }
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)

View File

@ -690,10 +690,10 @@ namespace Ocelot.UnitTests.Configuration
private void ThenTheQosOptionsAre(QoSOptions qosOptions) 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].QosOptionsOptions.ExceptionsAllowedBeforeBreaking.ShouldBe(qosOptions.ExceptionsAllowedBeforeBreaking);
_config.Data.ReRoutes[0].QosOptions.TimeoutValue.ShouldBe(qosOptions.TimeoutValue); _config.Data.ReRoutes[0].QosOptionsOptions.TimeoutValue.ShouldBe(qosOptions.TimeoutValue);
} }
} }
} }

View File

@ -49,7 +49,7 @@ namespace Ocelot.UnitTests.Configuration
private void WhenIGetTheConfiguration() private void WhenIGetTheConfiguration()
{ {
_getResult = _repo.Get(); _getResult = _repo.Get().Result;
} }
private void GivenThereIsASavedConfiguration() private void GivenThereIsASavedConfiguration()
@ -65,7 +65,7 @@ namespace Ocelot.UnitTests.Configuration
private void WhenIAddOrReplaceTheConfig() private void WhenIAddOrReplaceTheConfig()
{ {
_result = _repo.AddOrReplace(_config); _result = _repo.AddOrReplace(_config).Result;
} }
private void ThenNoErrorsAreReturned() private void ThenNoErrorsAreReturned()

View File

@ -53,12 +53,12 @@ namespace Ocelot.UnitTests.Configuration
{ {
_configurationRepository _configurationRepository
.Setup(x => x.Get()) .Setup(x => x.Get())
.Returns(config); .ReturnsAsync(config);
} }
private void WhenIGetTheConfig() private void WhenIGetTheConfig()
{ {
_result = _ocelotConfigurationProvider.Get(); _result = _ocelotConfigurationProvider.Get().Result;
} }
private void TheFollowingIsReturned(Response<IOcelotConfiguration> expected) private void TheFollowingIsReturned(Response<IOcelotConfiguration> expected)

View File

@ -59,7 +59,7 @@ namespace Ocelot.UnitTests.Configuration
.WithQuotaExceededMessage("QuotaExceededMessage") .WithQuotaExceededMessage("QuotaExceededMessage")
.WithRateLimitCounterPrefix("RateLimitCounterPrefix") .WithRateLimitCounterPrefix("RateLimitCounterPrefix")
.WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period,
TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), fileReRoute.RateLimitOptions.PeriodTimespan,
fileReRoute.RateLimitOptions.Limit)) fileReRoute.RateLimitOptions.Limit))
.Build(); .Build();
@ -102,7 +102,7 @@ namespace Ocelot.UnitTests.Configuration
_result.RateLimitCounterPrefix.ShouldBe(expected.RateLimitCounterPrefix); _result.RateLimitCounterPrefix.ShouldBe(expected.RateLimitCounterPrefix);
_result.RateLimitRule.Limit.ShouldBe(expected.RateLimitRule.Limit); _result.RateLimitRule.Limit.ShouldBe(expected.RateLimitRule.Limit);
_result.RateLimitRule.Period.ShouldBe(expected.RateLimitRule.Period); _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);
} }
} }
} }

View File

@ -90,7 +90,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute); _downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
_downstreamRouteFinder _downstreamRouteFinder
.Setup(x => x.FindDownstreamRoute(It.IsAny<string>(), It.IsAny<string>())) .Setup(x => x.FindDownstreamRoute(It.IsAny<string>(), It.IsAny<string>()))
.Returns(_downstreamRoute); .ReturnsAsync(_downstreamRoute);
} }
public void Dispose() public void Dispose()

View File

@ -199,7 +199,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
_reRoutesConfig = reRoutesConfig; _reRoutesConfig = reRoutesConfig;
_mockConfig _mockConfig
.Setup(x => x.Get()) .Setup(x => x.Get())
.Returns(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(_reRoutesConfig, adminPath))); .ReturnsAsync(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(_reRoutesConfig, adminPath)));
} }
private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath)
@ -209,7 +209,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
private void WhenICallTheFinder() private void WhenICallTheFinder()
{ {
_result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod); _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod).Result;
} }
private void ThenTheFollowingIsReturned(DownstreamRoute expected) private void ThenTheFollowingIsReturned(DownstreamRoute expected)

View File

@ -71,7 +71,7 @@ namespace Ocelot.UnitTests.RateLimit
{ {
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(), var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions(
new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List<string>(), false, "", "", new Ocelot.Configuration.RateLimitRule("1s", TimeSpan.FromSeconds(100), 3), 429)) new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List<string>(), false, "", "", new Ocelot.Configuration.RateLimitRule("1s", 100, 3), 429))
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.Build()); .Build());
@ -88,7 +88,7 @@ namespace Ocelot.UnitTests.RateLimit
{ {
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(), var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions(
new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List<string>() { "ocelotclient2" }, false, "", "", new RateLimitRule( "1s", TimeSpan.FromSeconds(100),3),429)) new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List<string>() { "ocelotclient2" }, false, "", "", new RateLimitRule( "1s", 100,3),429))
.WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get")
.Build()); .Build());