Merge pull request #45 from ThreeMammals/develop

merge newest code
This commit is contained in:
geffzhang 2018-05-13 11:22:10 +08:00 committed by GitHub
commit 4a0d355446
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 2810 additions and 2507 deletions

View File

@ -55,8 +55,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio
## How to install ## How to install
Ocelot is designed to work with ASP.NET core only and is currently Ocelot is designed to work with ASP.NET Core only and it targets `netstandard2.0`. This means it can be used anywhere `.NET Standard 2.0` is supported, including `.NET Core 2.0` and `.NET Framework 4.6.1` and up. [This](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) documentation may prove helpful when working out if Ocelot would be suitable for you.
built to netcoreapp2.0 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you.
Install Ocelot and it's dependencies using NuGet. Install Ocelot and it's dependencies using NuGet.

View File

@ -146,6 +146,25 @@ I guess it means if you want to use Ocelot to its fullest you take on Consul as
This feature has a 3 second ttl cache before making a new request to your local consul agent. This feature has a 3 second ttl cache before making a new request to your local consul agent.
Configuration Key
-----------------
If you are using Consul for configuration (or other providers in the future) you might want to key your configurations so you can have multiple configurations :) This feature was requested in `issue 346 <https://github.com/ThreeMammals/Ocelot/issues/346>`_! In order to specify the key you need to set the ConfigurationKey property in the ServiceDiscoveryProvider section of the configuration json file e.g.
.. code-block:: json
"GlobalConfiguration": {
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 9500,
"ConfigurationKey": "Oceolot_A"
}
}
In this example Ocelot will use Oceolot_A as the key for your configuration when looking it up in Consul.
If you do not set the ConfigurationKey Ocelot will use the string InternalConfiguration as the key.
Follow Redirects / Use CookieContainer Follow Redirects / Use CookieContainer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -2,14 +2,14 @@ Getting Started
=============== ===============
Ocelot is designed to work with .NET Core only and is currently Ocelot is designed to work with .NET Core only and is currently
built to netcoreapp2.0 `this <https://docs.microsoft.com/en-us/dotnet/articles/standard/library>`_ documentation may prove helpful when working out if Ocelot would be suitable for you. built to netstandard2.0 `this <https://docs.microsoft.com/en-us/dotnet/articles/standard/library>`_ documentation may prove helpful when working out if Ocelot would be suitable for you.
.NET Core 2.0 .NET Core 2.0
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
**Install NuGet package** **Install NuGet package**
Install Ocelot and it's dependencies using nuget. You will need to create a netcoreapp2.0 project and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections Install Ocelot and it's dependencies using nuget. You will need to create a netstandard2.0 project and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections
to get up and running. to get up and running.
``Install-Package Ocelot`` ``Install-Package Ocelot``

View File

@ -6,6 +6,7 @@ namespace Ocelot.Configuration.Builder
private int _serviceDiscoveryProviderPort; private int _serviceDiscoveryProviderPort;
private string _type; private string _type;
private string _token; private string _token;
private string _configurationKey;
public ServiceProviderConfigurationBuilder WithHost(string serviceDiscoveryProviderHost) public ServiceProviderConfigurationBuilder WithHost(string serviceDiscoveryProviderHost)
{ {
@ -31,9 +32,15 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public ServiceProviderConfigurationBuilder WithConfigurationKey(string configurationKey)
{
_configurationKey = configurationKey;
return this;
}
public ServiceProviderConfiguration Build() public ServiceProviderConfiguration Build()
{ {
return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort, _token); return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort, _token, _configurationKey);
} }
} }
} }

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Infrastructure; using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Responses; using Ocelot.Responses;

View File

@ -15,6 +15,7 @@ namespace Ocelot.Configuration.Creator
.WithPort(serviceProviderPort) .WithPort(serviceProviderPort)
.WithType(globalConfiguration?.ServiceDiscoveryProvider?.Type) .WithType(globalConfiguration?.ServiceDiscoveryProvider?.Type)
.WithToken(globalConfiguration?.ServiceDiscoveryProvider?.Token) .WithToken(globalConfiguration?.ServiceDiscoveryProvider?.Token)
.WithConfigurationKey(globalConfiguration?.ServiceDiscoveryProvider?.ConfigurationKey)
.Build(); .Build();
} }
} }

View File

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using Ocelot.Infrastructure.Extensions;
namespace Ocelot.Configuration.File namespace Ocelot.Configuration.File
{ {

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Infrastructure.Extensions;
namespace Ocelot.Configuration.File namespace Ocelot.Configuration.File
{ {

View File

@ -6,5 +6,6 @@ namespace Ocelot.Configuration.File
public int Port { get; set; } public int Port { get; set; }
public string Type { get; set; } public string Type { get; set; }
public string Token { get; set; } public string Token { get; set; }
public string ConfigurationKey { get; set; }
} }
} }

View File

@ -42,28 +42,35 @@ namespace Ocelot.Configuration
[HttpPost] [HttpPost]
public async Task<IActionResult> Post([FromBody]FileConfiguration fileConfiguration) public async Task<IActionResult> Post([FromBody]FileConfiguration fileConfiguration)
{ {
//todo - this code is a bit shit sort it out.. try
var test = _provider.GetService(typeof(INode));
if (test != null)
{ {
var node = (INode)test; //todo - this code is a bit shit sort it out..
var result = node.Accept(new UpdateFileConfiguration(fileConfiguration)); var test = _provider.GetService(typeof(INode));
if (result.GetType() == typeof(Rafty.Concensus.ErrorResponse<UpdateFileConfiguration>)) if (test != null)
{ {
return new BadRequestObjectResult("There was a problem. This error message sucks raise an issue in GitHub."); var node = (INode)test;
var result = await node.Accept(new UpdateFileConfiguration(fileConfiguration));
if (result.GetType() == typeof(Rafty.Concensus.ErrorResponse<UpdateFileConfiguration>))
{
return new BadRequestObjectResult("There was a problem. This error message sucks raise an issue in GitHub.");
}
return new OkObjectResult(result.Command.Configuration);
} }
return new OkObjectResult(result.Command.Configuration); var response = await _setter.Set(fileConfiguration);
if (response.IsError)
{
return new BadRequestObjectResult(response.Errors);
}
return new OkObjectResult(fileConfiguration);
} }
catch(Exception e)
var response = await _setter.Set(fileConfiguration);
if (response.IsError)
{ {
return new BadRequestObjectResult(response.Errors); return new BadRequestObjectResult($"{e.Message}:{e.StackTrace}");
} }
return new OkObjectResult(fileConfiguration);
} }
} }
} }

View File

@ -40,7 +40,7 @@ namespace Ocelot.Configuration.Repository
_polling = true; _polling = true;
await Poll(); await Poll();
_polling = false; _polling = false;
}, null, 0, _config.Delay); }, null, _config.Delay, _config.Delay);
} }
private async Task Poll() private async Task Poll()

View File

@ -14,7 +14,7 @@ namespace Ocelot.Configuration.Repository
public class ConsulFileConfigurationRepository : IFileConfigurationRepository public class ConsulFileConfigurationRepository : IFileConfigurationRepository
{ {
private readonly IConsulClient _consul; private readonly IConsulClient _consul;
private const string OcelotConfiguration = "InternalConfiguration"; private readonly string _configurationKey;
private readonly Cache.IOcelotCache<FileConfiguration> _cache; private readonly Cache.IOcelotCache<FileConfiguration> _cache;
private readonly IOcelotLogger _logger; private readonly IOcelotLogger _logger;
@ -29,6 +29,7 @@ namespace Ocelot.Configuration.Repository
var internalConfig = repo.Get(); var internalConfig = repo.Get();
_configurationKey = "InternalConfiguration";
var consulHost = "localhost"; var consulHost = "localhost";
var consulPort = 8500; var consulPort = 8500;
string token = null; string token = null;
@ -38,23 +39,25 @@ namespace Ocelot.Configuration.Repository
consulHost = string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration?.Host) ? consulHost : internalConfig.Data.ServiceProviderConfiguration?.Host; consulHost = string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration?.Host) ? consulHost : internalConfig.Data.ServiceProviderConfiguration?.Host;
consulPort = internalConfig.Data.ServiceProviderConfiguration?.Port ?? consulPort; consulPort = internalConfig.Data.ServiceProviderConfiguration?.Port ?? consulPort;
token = internalConfig.Data.ServiceProviderConfiguration?.Token; token = internalConfig.Data.ServiceProviderConfiguration?.Token;
_configurationKey = !string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration?.ConfigurationKey) ?
internalConfig.Data.ServiceProviderConfiguration?.ConfigurationKey : _configurationKey;
} }
var config = new ConsulRegistryConfiguration(consulHost, consulPort, OcelotConfiguration, token); var config = new ConsulRegistryConfiguration(consulHost, consulPort, _configurationKey, token);
_consul = factory.Get(config); _consul = factory.Get(config);
} }
public async Task<Response<FileConfiguration>> Get() public async Task<Response<FileConfiguration>> Get()
{ {
var config = _cache.Get(OcelotConfiguration, OcelotConfiguration); var config = _cache.Get(_configurationKey, _configurationKey);
if (config != null) if (config != null)
{ {
return new OkResponse<FileConfiguration>(config); return new OkResponse<FileConfiguration>(config);
} }
var queryResult = await _consul.KV.Get(OcelotConfiguration); var queryResult = await _consul.KV.Get(_configurationKey);
if (queryResult.Response == null) if (queryResult.Response == null)
{ {
@ -76,7 +79,7 @@ namespace Ocelot.Configuration.Repository
var bytes = Encoding.UTF8.GetBytes(json); var bytes = Encoding.UTF8.GetBytes(json);
var kvPair = new KVPair(OcelotConfiguration) var kvPair = new KVPair(_configurationKey)
{ {
Value = bytes Value = bytes
}; };
@ -85,7 +88,7 @@ namespace Ocelot.Configuration.Repository
if (result.Response) if (result.Response)
{ {
_cache.AddAndDelete(OcelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3), OcelotConfiguration); _cache.AddAndDelete(_configurationKey, ocelotConfiguration, TimeSpan.FromSeconds(3), _configurationKey);
return new OkResponse(); return new OkResponse();
} }

View File

@ -2,8 +2,9 @@
{ {
public class ServiceProviderConfiguration public class ServiceProviderConfiguration
{ {
public ServiceProviderConfiguration(string type, string host, int port, string token) public ServiceProviderConfiguration(string type, string host, int port, string token, string configurationKey)
{ {
ConfigurationKey = configurationKey;
Host = host; Host = host;
Port = port; Port = port;
Token = token; Token = token;
@ -14,5 +15,6 @@
public int Port { get; } public int Port { get; }
public string Type { get; } public string Type { get; }
public string Token { get; } public string Token { get; }
public string ConfigurationKey { get; }
} }
} }

View File

@ -3,6 +3,7 @@ namespace Ocelot.Headers
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Infrastructure; using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;

View File

@ -0,0 +1,15 @@
namespace Ocelot.Infrastructure
{
internal class DelayedMessage<T>
{
public DelayedMessage(T message, int delay)
{
Delay = delay;
Message = message;
}
public T Message { get; set; }
public int Delay { get; set; }
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ocelot.Infrastructure.Extensions
{
/// <summary>
/// Trivial implementations of methods present in .NET Core 2 but not supported on .NET Standard 2.0.
/// </summary>
internal static class NetCoreSupportExtensions
{
internal static void AppendJoin<T>(this StringBuilder builder, char separator, IEnumerable<T> values)
{
builder.Append(string.Join(separator.ToString(), values));
}
internal static string[] Split(this string input, string separator, StringSplitOptions options = StringSplitOptions.None)
{
return input.Split(new[] { separator }, options);
}
internal static bool StartsWith(this string input, char value)
{
return input.StartsWith(value.ToString());
}
internal static bool EndsWith(this string input, char value)
{
return input.EndsWith(value.ToString());
}
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace Ocelot.Infrastructure
{
public interface IBus<T>
{
void Subscribe(Action<T> action);
void Publish(T message, int delay);
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Ocelot.Infrastructure
{
public class InMemoryBus<T> : IBus<T>
{
private readonly BlockingCollection<DelayedMessage<T>> _queue;
private readonly List<Action<T>> _subscriptions;
private Thread _processing;
public InMemoryBus()
{
_queue = new BlockingCollection<DelayedMessage<T>>();
_subscriptions = new List<Action<T>>();
_processing = new Thread(async () => await Process());
_processing.Start();
}
public void Subscribe(Action<T> action)
{
_subscriptions.Add(action);
}
public void Publish(T message, int delay)
{
var delayed = new DelayedMessage<T>(message, delay);
_queue.Add(delayed);
}
private async Task Process()
{
foreach(var delayedMessage in _queue.GetConsumingEnumerable())
{
await Task.Delay(delayedMessage.Delay);
foreach (var subscription in _subscriptions)
{
subscription(delayedMessage.Message);
}
}
}
}
}

View File

@ -3,61 +3,64 @@ namespace Ocelot.LoadBalancer.LoadBalancers
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Infrastructure;
using Ocelot.Middleware; using Ocelot.Middleware;
using Responses; using Responses;
using Values; using Values;
public class CookieStickySessions : ILoadBalancer, IDisposable public class CookieStickySessions : ILoadBalancer
{ {
private readonly int _expiryInMs; private readonly int _keyExpiryInMs;
private readonly string _key; private readonly string _key;
private readonly ILoadBalancer _loadBalancer; private readonly ILoadBalancer _loadBalancer;
private readonly ConcurrentDictionary<string, StickySession> _stored; private readonly ConcurrentDictionary<string, StickySession> _stored;
private readonly Timer _timer; private readonly IBus<StickySession> _bus;
private bool _expiring; private readonly object _lock = new object();
public CookieStickySessions(ILoadBalancer loadBalancer, string key, int expiryInMs) public CookieStickySessions(ILoadBalancer loadBalancer, string key, int keyExpiryInMs, IBus<StickySession> bus)
{ {
_bus = bus;
_key = key; _key = key;
_expiryInMs = expiryInMs; _keyExpiryInMs = keyExpiryInMs;
_loadBalancer = loadBalancer; _loadBalancer = loadBalancer;
_stored = new ConcurrentDictionary<string, StickySession>(); _stored = new ConcurrentDictionary<string, StickySession>();
_timer = new Timer(x => _bus.Subscribe(ss =>
{ {
if (_expiring) //todo - get test coverage for this.
if (_stored.TryGetValue(ss.Key, out var stickySession))
{ {
return; lock (_lock)
{
if (stickySession.Expiry < DateTime.UtcNow)
{
_stored.TryRemove(stickySession.Key, out _);
_loadBalancer.Release(stickySession.HostAndPort);
}
}
} }
});
_expiring = true;
Expire();
_expiring = false;
}, null, 0, 50);
}
public void Dispose()
{
_timer?.Dispose();
} }
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context) public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context)
{ {
var value = context.HttpContext.Request.Cookies[_key]; var key = context.HttpContext.Request.Cookies[_key];
if (!string.IsNullOrEmpty(value) && _stored.ContainsKey(value)) lock (_lock)
{ {
var cached = _stored[value]; if (!string.IsNullOrEmpty(key) && _stored.ContainsKey(key))
{
var cached = _stored[key];
var updated = new StickySession(cached.HostAndPort, DateTime.UtcNow.AddMilliseconds(_expiryInMs)); var updated = new StickySession(cached.HostAndPort, DateTime.UtcNow.AddMilliseconds(_keyExpiryInMs), key);
_stored[value] = updated; _stored[key] = updated;
return new OkResponse<ServiceHostAndPort>(updated.HostAndPort); _bus.Publish(updated, _keyExpiryInMs);
return new OkResponse<ServiceHostAndPort>(updated.HostAndPort);
}
} }
var next = await _loadBalancer.Lease(context); var next = await _loadBalancer.Lease(context);
@ -67,9 +70,14 @@ namespace Ocelot.LoadBalancer.LoadBalancers
return new ErrorResponse<ServiceHostAndPort>(next.Errors); return new ErrorResponse<ServiceHostAndPort>(next.Errors);
} }
if (!string.IsNullOrEmpty(value) && !_stored.ContainsKey(value)) lock (_lock)
{ {
_stored[value] = new StickySession(next.Data, DateTime.UtcNow.AddMilliseconds(_expiryInMs)); if (!string.IsNullOrEmpty(key) && !_stored.ContainsKey(key))
{
var ss = new StickySession(next.Data, DateTime.UtcNow.AddMilliseconds(_keyExpiryInMs), key);
_stored[key] = ss;
_bus.Publish(ss, _keyExpiryInMs);
}
} }
return new OkResponse<ServiceHostAndPort>(next.Data); return new OkResponse<ServiceHostAndPort>(next.Data);
@ -78,16 +86,5 @@ namespace Ocelot.LoadBalancer.LoadBalancers
public void Release(ServiceHostAndPort hostAndPort) public void Release(ServiceHostAndPort hostAndPort)
{ {
} }
private void Expire()
{
var expired = _stored.Where(x => x.Value.Expiry < DateTime.UtcNow);
foreach (var expire in expired)
{
_stored.Remove(expire.Key, out _);
_loadBalancer.Release(expire.Value.HostAndPort);
}
}
} }
} }

View File

@ -1,5 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Infrastructure;
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
namespace Ocelot.LoadBalancer.LoadBalancers namespace Ocelot.LoadBalancer.LoadBalancers
@ -25,7 +26,8 @@ namespace Ocelot.LoadBalancer.LoadBalancers
return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName); return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName);
case nameof(CookieStickySessions): case nameof(CookieStickySessions):
var loadBalancer = new RoundRobin(async () => await serviceProvider.Get()); var loadBalancer = new RoundRobin(async () => await serviceProvider.Get());
return new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs); var bus = new InMemoryBus<StickySession>();
return new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs, bus);
default: default:
return new NoLoadBalancer(await serviceProvider.Get()); return new NoLoadBalancer(await serviceProvider.Get());
} }

View File

@ -5,14 +5,17 @@ namespace Ocelot.LoadBalancer.LoadBalancers
{ {
public class StickySession public class StickySession
{ {
public StickySession(ServiceHostAndPort hostAndPort, DateTime expiry) public StickySession(ServiceHostAndPort hostAndPort, DateTime expiry, string key)
{ {
HostAndPort = hostAndPort; HostAndPort = hostAndPort;
Expiry = expiry; Expiry = expiry;
Key = key;
} }
public ServiceHostAndPort HostAndPort { get; } public ServiceHostAndPort HostAndPort { get; }
public DateTime Expiry { get; } public DateTime Expiry { get; }
public string Key {get;}
} }
} }

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion> <RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion> <NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
<NoPackageAnalysis>true</NoPackageAnalysis> <NoPackageAnalysis>true</NoPackageAnalysis>
@ -25,18 +25,26 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" /> <PackageReference Include="Butterfly.Client" Version="0.0.8" />
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8">
<NoWarn>NU1701</NoWarn>
</PackageReference>
<PackageReference Include="FluentValidation" Version="7.5.2" /> <PackageReference Include="FluentValidation" Version="7.5.2" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.4.0" /> <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" /> <PackageReference Include="Microsoft.AspNetCore" Version="2.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" /> <PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="2.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" /> <PackageReference Include="Microsoft.Data.SQLite" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.0.1">
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" /> <NoWarn>NU1701</NoWarn>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2"> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
@ -45,9 +53,9 @@
<PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.2" /> <PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.2" />
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.2" /> <PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.2" />
<PackageReference Include="Consul" Version="0.7.2.4" /> <PackageReference Include="Consul" Version="0.7.2.4" />
<PackageReference Include="Polly" Version="5.8.0" /> <PackageReference Include="Polly" Version="6.0.1" />
<PackageReference Include="Pivotal.Discovery.Client" Version="1.1.0" /> <PackageReference Include="Pivotal.Discovery.Client" Version="1.1.0" />
<PackageReference Include="IdentityServer4" Version="2.1.3" /> <PackageReference Include="IdentityServer4" Version="2.2.0" />
<PackageReference Include="Rafty" Version="0.4.2" /> <PackageReference Include="Rafty" Version="0.4.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

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 Rafty.FiniteStateMachine; using Rafty.FiniteStateMachine;
using Rafty.Infrastructure; using Rafty.Infrastructure;
@ -17,7 +18,7 @@ namespace Ocelot.Raft
_id = nodeId.Id.Replace("/","").Replace(":",""); _id = nodeId.Id.Replace("/","").Replace(":","");
} }
public void Handle(LogEntry log) public Task Handle(LogEntry log)
{ {
try try
{ {
@ -28,6 +29,8 @@ namespace Ocelot.Raft
{ {
Console.WriteLine(exception); Console.WriteLine(exception);
} }
return Task.CompletedTask;
} }
} }
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Middleware; using Ocelot.Middleware;
@ -33,50 +34,46 @@ namespace Ocelot.Raft
_baseSchemeUrlAndPort = finder.Find(); _baseSchemeUrlAndPort = finder.Find();
} }
public string Id {get; private set;} public string Id { get; }
public RequestVoteResponse Request(RequestVote requestVote) public async Task<RequestVoteResponse> Request(RequestVote requestVote)
{ {
if(_token == null) if(_token == null)
{ {
SetToken(); await SetToken();
} }
var json = JsonConvert.SerializeObject(requestVote, _jsonSerializerSettings); var json = JsonConvert.SerializeObject(requestVote, _jsonSerializerSettings);
var content = new StringContent(json); var content = new StringContent(json);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content).GetAwaiter().GetResult(); var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content);
if(response.IsSuccessStatusCode) if(response.IsSuccessStatusCode)
{ {
return JsonConvert.DeserializeObject<RequestVoteResponse>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings); return JsonConvert.DeserializeObject<RequestVoteResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
}
else
{
return new RequestVoteResponse(false, requestVote.Term);
} }
return new RequestVoteResponse(false, requestVote.Term);
} }
public AppendEntriesResponse Request(AppendEntries appendEntries) public async Task<AppendEntriesResponse> Request(AppendEntries appendEntries)
{ {
try try
{ {
if(_token == null) if(_token == null)
{ {
SetToken(); await SetToken();
} }
var json = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings); var json = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings);
var content = new StringContent(json); var content = new StringContent(json);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content).GetAwaiter().GetResult(); var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content);
if(response.IsSuccessStatusCode) if(response.IsSuccessStatusCode)
{ {
return JsonConvert.DeserializeObject<AppendEntriesResponse>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(),_jsonSerializerSettings); return JsonConvert.DeserializeObject<AppendEntriesResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
}
else
{
return new AppendEntriesResponse(appendEntries.Term, false);
} }
return new AppendEntriesResponse(appendEntries.Term, false);
} }
catch(Exception ex) catch(Exception ex)
{ {
@ -85,29 +82,31 @@ namespace Ocelot.Raft
} }
} }
public Response<T> Request<T>(T command) public async Task<Response<T>> Request<T>(T command)
where T : ICommand where T : ICommand
{ {
Console.WriteLine("SENDING REQUEST....");
if(_token == null) if(_token == null)
{ {
SetToken(); await SetToken();
} }
var json = JsonConvert.SerializeObject(command, _jsonSerializerSettings); var json = JsonConvert.SerializeObject(command, _jsonSerializerSettings);
var content = new StringContent(json); var content = new StringContent(json);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content).GetAwaiter().GetResult(); var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content);
if(response.IsSuccessStatusCode) if(response.IsSuccessStatusCode)
{ {
return JsonConvert.DeserializeObject<OkResponse<T>>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings); Console.WriteLine("REQUEST OK....");
} var okResponse = JsonConvert.DeserializeObject<OkResponse<ICommand>>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
else return new OkResponse<T>((T)okResponse.Command);
{
return new ErrorResponse<T>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), command);
} }
Console.WriteLine("REQUEST NOT OK....");
return new ErrorResponse<T>(await response.Content.ReadAsStringAsync(), command);
} }
private void SetToken() private async Task SetToken()
{ {
var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token"; var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token";
var formData = new List<KeyValuePair<string, string>> var formData = new List<KeyValuePair<string, string>>
@ -118,8 +117,8 @@ namespace Ocelot.Raft
new KeyValuePair<string, string>("grant_type", "client_credentials") new KeyValuePair<string, string>("grant_type", "client_credentials")
}; };
var content = new FormUrlEncodedContent(formData); var content = new FormUrlEncodedContent(formData);
var response = _httpClient.PostAsync(tokenUrl, content).GetAwaiter().GetResult(); var response = await _httpClient.PostAsync(tokenUrl, content);
var responseContent = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); var responseContent = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent); _token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken); _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken);

View File

@ -1,3 +1,4 @@
using System.Threading.Tasks;
using Ocelot.Configuration.Setter; using Ocelot.Configuration.Setter;
using Rafty.FiniteStateMachine; using Rafty.FiniteStateMachine;
using Rafty.Log; using Rafty.Log;
@ -14,12 +15,12 @@ namespace Ocelot.Raft
_setter = setter; _setter = setter;
} }
public void Handle(LogEntry log) public async Task Handle(LogEntry log)
{ {
//todo - handle an error //todo - handle an error
//hack it to just cast as at the moment we know this is the only command :P //hack it to just cast as at the moment we know this is the only command :P
var hack = (UpdateFileConfiguration)log.CommandData; var hack = (UpdateFileConfiguration)log.CommandData;
_setter.Set(hack.Configuration).GetAwaiter().GetResult(); await _setter.Set(hack.Configuration);
} }
} }
} }

View File

@ -20,9 +20,9 @@ namespace Ocelot.Raft
public class RaftController : Controller public class RaftController : Controller
{ {
private readonly INode _node; private readonly INode _node;
private IOcelotLogger _logger; private readonly IOcelotLogger _logger;
private string _baseSchemeUrlAndPort; private readonly string _baseSchemeUrlAndPort;
private JsonSerializerSettings _jsonSerialiserSettings; private readonly JsonSerializerSettings _jsonSerialiserSettings;
public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IBaseUrlFinder finder) public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IBaseUrlFinder finder)
{ {
@ -45,7 +45,7 @@ namespace Ocelot.Raft
_logger.LogDebug($"{_baseSchemeUrlAndPort}/appendentries called, my state is {_node.State.GetType().FullName}"); _logger.LogDebug($"{_baseSchemeUrlAndPort}/appendentries called, my state is {_node.State.GetType().FullName}");
var appendEntriesResponse = _node.Handle(appendEntries); var appendEntriesResponse = await _node.Handle(appendEntries);
return new OkObjectResult(appendEntriesResponse); return new OkObjectResult(appendEntriesResponse);
} }
@ -62,7 +62,7 @@ namespace Ocelot.Raft
_logger.LogDebug($"{_baseSchemeUrlAndPort}/requestvote called, my state is {_node.State.GetType().FullName}"); _logger.LogDebug($"{_baseSchemeUrlAndPort}/requestvote called, my state is {_node.State.GetType().FullName}");
var requestVoteResponse = _node.Handle(requestVote); var requestVoteResponse = await _node.Handle(requestVote);
return new OkObjectResult(requestVoteResponse); return new OkObjectResult(requestVoteResponse);
} }
@ -81,7 +81,7 @@ namespace Ocelot.Raft
_logger.LogDebug($"{_baseSchemeUrlAndPort}/command called, my state is {_node.State.GetType().FullName}"); _logger.LogDebug($"{_baseSchemeUrlAndPort}/command called, my state is {_node.State.GetType().FullName}");
var commandResponse = _node.Accept(command); var commandResponse = await _node.Accept(command);
json = JsonConvert.SerializeObject(commandResponse, _jsonSerialiserSettings); json = JsonConvert.SerializeObject(commandResponse, _jsonSerialiserSettings);
@ -91,7 +91,7 @@ namespace Ocelot.Raft
catch(Exception e) catch(Exception e)
{ {
_logger.LogError($"THERE WAS A PROBLEM ON NODE {_node.State.CurrentState.Id}", e); _logger.LogError($"THERE WAS A PROBLEM ON NODE {_node.State.CurrentState.Id}", e);
throw e; throw;
} }
} }
} }

View File

@ -5,9 +5,11 @@ using Newtonsoft.Json;
using System; using System;
using Rafty.Infrastructure; using Rafty.Infrastructure;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
namespace Ocelot.Raft namespace Ocelot.Raft
{ {
//todo - use async await
[ExcludeFromCoverage] [ExcludeFromCoverage]
public class SqlLiteLog : ILog public class SqlLiteLog : ILog
{ {
@ -40,89 +42,80 @@ namespace Ocelot.Raft
} }
} }
public int LastLogIndex public Task<int> LastLogIndex()
{ {
get lock(_lock)
{ {
lock(_lock) var result = 1;
using(var connection = new SqliteConnection($"Data Source={_path};"))
{ {
var result = 1; connection.Open();
using(var connection = new SqliteConnection($"Data Source={_path};")) var sql = @"select id from logs order by id desc limit 1";
using(var command = new SqliteCommand(sql, connection))
{ {
connection.Open(); var index = Convert.ToInt32(command.ExecuteScalar());
var sql = @"select id from logs order by id desc limit 1"; if(index > result)
using(var command = new SqliteCommand(sql, connection))
{ {
var index = Convert.ToInt32(command.ExecuteScalar()); result = index;
if(index > result)
{
result = index;
}
} }
} }
return result;
} }
return Task.FromResult(result);
} }
} }
public long LastLogTerm public Task<long> LastLogTerm ()
{ {
get lock(_lock)
{ {
lock(_lock) long result = 0;
using(var connection = new SqliteConnection($"Data Source={_path};"))
{ {
long result = 0; connection.Open();
using(var connection = new SqliteConnection($"Data Source={_path};")) var sql = @"select data from logs order by id desc limit 1";
using(var command = new SqliteCommand(sql, connection))
{ {
connection.Open(); var data = Convert.ToString(command.ExecuteScalar());
var sql = @"select data from logs order by id desc limit 1"; var jsonSerializerSettings = new JsonSerializerSettings() {
using(var command = new SqliteCommand(sql, connection)) TypeNameHandling = TypeNameHandling.All
};
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
if(log != null && log.Term > result)
{ {
var data = Convert.ToString(command.ExecuteScalar()); result = log.Term;
var jsonSerializerSettings = new JsonSerializerSettings() {
TypeNameHandling = TypeNameHandling.All
};
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
if(log != null && log.Term > result)
{
result = log.Term;
}
} }
} }
return result;
} }
return Task.FromResult(result);
} }
} }
public int Count public Task<int> Count ()
{ {
get lock(_lock)
{ {
lock(_lock) var result = 0;
using(var connection = new SqliteConnection($"Data Source={_path};"))
{ {
var result = 0; connection.Open();
using(var connection = new SqliteConnection($"Data Source={_path};")) var sql = @"select count(id) from logs";
using(var command = new SqliteCommand(sql, connection))
{ {
connection.Open(); var index = Convert.ToInt32(command.ExecuteScalar());
var sql = @"select count(id) from logs"; if(index > result)
using(var command = new SqliteCommand(sql, connection))
{ {
var index = Convert.ToInt32(command.ExecuteScalar()); result = index;
if(index > result)
{
result = index;
}
} }
} }
return result;
} }
return Task.FromResult(result);
} }
} }
public int Apply(LogEntry log) public Task<int> Apply(LogEntry log)
{ {
lock(_lock) lock(_lock)
{ {
@ -145,13 +138,13 @@ namespace Ocelot.Raft
using(var command = new SqliteCommand(sql, connection)) using(var command = new SqliteCommand(sql, connection))
{ {
var result = command.ExecuteScalar(); var result = command.ExecuteScalar();
return Convert.ToInt32(result); return Task.FromResult(Convert.ToInt32(result));
} }
} }
} }
} }
public void DeleteConflictsFromThisLog(int index, LogEntry logEntry) public Task DeleteConflictsFromThisLog(int index, LogEntry logEntry)
{ {
lock(_lock) lock(_lock)
{ {
@ -174,15 +167,17 @@ namespace Ocelot.Raft
var deleteSql = $"delete from logs where id >= {index};"; var deleteSql = $"delete from logs where id >= {index};";
using(var deleteCommand = new SqliteCommand(deleteSql, connection)) using(var deleteCommand = new SqliteCommand(deleteSql, connection))
{ {
var result = deleteCommand.ExecuteNonQuery(); var result = deleteCommand.ExecuteNonQuery();
} }
} }
} }
} }
} }
return Task.CompletedTask;
} }
public LogEntry Get(int index) public Task<LogEntry> Get(int index)
{ {
lock(_lock) lock(_lock)
{ {
@ -199,13 +194,13 @@ namespace Ocelot.Raft
TypeNameHandling = TypeNameHandling.All TypeNameHandling = TypeNameHandling.All
}; };
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings); var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
return log; return Task.FromResult(log);
} }
} }
} }
} }
public System.Collections.Generic.List<(int index, LogEntry logEntry)> GetFrom(int index) public Task<List<(int index, LogEntry logEntry)>> GetFrom(int index)
{ {
lock(_lock) lock(_lock)
{ {
@ -235,11 +230,11 @@ namespace Ocelot.Raft
} }
} }
return logsToReturn; return Task.FromResult(logsToReturn);
} }
} }
public long GetTermAtIndex(int index) public Task<long> GetTermAtIndex(int index)
{ {
lock(_lock) lock(_lock)
{ {
@ -264,11 +259,11 @@ namespace Ocelot.Raft
} }
} }
return result; return Task.FromResult(result);
} }
} }
public void Remove(int indexOfCommand) public Task Remove(int indexOfCommand)
{ {
lock(_lock) lock(_lock)
{ {
@ -284,6 +279,8 @@ namespace Ocelot.Raft
} }
} }
} }
return Task.CompletedTask;
} }
} }
} }

View File

@ -52,7 +52,7 @@ namespace Ocelot.Requester
if(context.DownstreamReRoute.DangerousAcceptAnyServerCertificateValidator) if(context.DownstreamReRoute.DangerousAcceptAnyServerCertificateValidator)
{ {
httpclientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; httpclientHandler.ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => true;
_logger _logger
.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {context.DownstreamReRoute.DownstreamPathTemplate}"); .LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {context.DownstreamReRoute.DownstreamPathTemplate}");

View File

@ -316,8 +316,6 @@ namespace Ocelot.AcceptanceTests
{ {
new Scope("api2"), new Scope("api2"),
new Scope("api2.readOnly"), new Scope("api2.readOnly"),
new Scope("openid"),
new Scope("offline_access")
}, },
ApiSecrets = new List<Secret>() ApiSecrets = new List<Secret>()
{ {

View File

@ -34,26 +34,27 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CacheManager.Serialization.Json" Version="1.1.2" /> <PackageReference Include="CacheManager.Serialization.Json" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.2" /> <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2"> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.2" />
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" /> <PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
<PackageReference Include="Shouldly" Version="3.0.0" /> <PackageReference Include="Shouldly" Version="3.0.0" />
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" /> <PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
<PackageReference Include="Consul" Version="0.7.2.4" /> <PackageReference Include="Consul" Version="0.7.2.4" />
<PackageReference Include="xunit" Version="2.3.1" /> <PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" /> <PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
<PackageReference Include="Rafty" Version="0.4.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -664,7 +664,8 @@ namespace Ocelot.AcceptanceTests
{ {
using (var httpClient = new HttpClient()) using (var httpClient = new HttpClient())
{ {
var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").Result; var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").GetAwaiter().GetResult();
var content = response.Content.ReadAsStringAsync().GetAwaiter();
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
} }
} }
@ -701,7 +702,7 @@ namespace Ocelot.AcceptanceTests
Task.WaitAll(tasks); Task.WaitAll(tasks);
} }
public async Task WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times, string cookie, string value) public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times, string cookie, string value)
{ {
var tasks = new Task[times]; var tasks = new Task[times];

View File

@ -76,16 +76,16 @@ namespace Ocelot.Benchmarks
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
} }
// * Summary* /* * Summary*
// BenchmarkDotNet = v0.10.13, OS = macOS 10.12.6 (16G1212) [Darwin 16.7.0] BenchmarkDotNet = v0.10.13, OS = macOS 10.12.6 (16G1212) [Darwin 16.7.0]
// Intel Core i5-4278U CPU 2.60GHz(Haswell), 1 CPU, 4 logical cores and 2 physical cores Intel Core i5-4278U CPU 2.60GHz(Haswell), 1 CPU, 4 logical cores and 2 physical cores
//.NET Core SDK = 2.1.4 .NET Core SDK = 2.1.4
// [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
// DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
// Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated | Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated |
// --------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:| --------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:|
// Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB | Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB |*/
private void GivenOcelotIsRunning(string url) private void GivenOcelotIsRunning(string url)
{ {

View File

@ -19,7 +19,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.10.13" /> <PackageReference Include="BenchmarkDotNet" Version="0.10.14" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2"> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>

View File

@ -25,26 +25,26 @@
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" /> <ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2"> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" /> <PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
<PackageReference Include="xunit" Version="2.3.1" /> <PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="IdentityServer4" Version="2.1.3" /> <PackageReference Include="IdentityServer4" Version="2.2.0" />
<PackageReference Include="Shouldly" Version="3.0.0" /> <PackageReference Include="Shouldly" Version="3.0.0" />
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" /> <PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
<PackageReference Include="Consul" Version="0.7.2.4" /> <PackageReference Include="Consul" Version="0.7.2.4" />
<PackageReference Include="Rafty" Version="0.4.2" /> <PackageReference Include="Rafty" Version="0.4.3" />
<PackageReference Include="Microsoft.Data.SQLite" Version="2.0.1" /> <PackageReference Include="Microsoft.Data.SQLite" Version="2.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -23,6 +23,8 @@ using Ocelot.Middleware;
namespace Ocelot.IntegrationTests namespace Ocelot.IntegrationTests
{ {
using Xunit.Abstractions;
public class RaftTests : IDisposable public class RaftTests : IDisposable
{ {
private readonly List<IWebHost> _builders; private readonly List<IWebHost> _builders;
@ -34,9 +36,11 @@ namespace Ocelot.IntegrationTests
private BearerToken _token; private BearerToken _token;
private HttpResponseMessage _response; private HttpResponseMessage _response;
private static readonly object _lock = new object(); private static readonly object _lock = new object();
private ITestOutputHelper _output;
public RaftTests() public RaftTests(ITestOutputHelper output)
{ {
_output = output;
_httpClientForAssertions = new HttpClient(); _httpClientForAssertions = new HttpClient();
_httpClient = new HttpClient(); _httpClient = new HttpClient();
var ocelotBaseUrl = "http://localhost:5000"; var ocelotBaseUrl = "http://localhost:5000";
@ -46,21 +50,7 @@ namespace Ocelot.IntegrationTests
_threads = new List<Thread>(); _threads = new List<Thread>();
} }
public void Dispose() [Fact(Skip = "still broken waiting for work in rafty")]
{
foreach (var builder in _builders)
{
builder?.Dispose();
}
foreach (var peer in _peers.Peers)
{
File.Delete(peer.HostAndPort.Replace("/","").Replace(":",""));
File.Delete($"{peer.HostAndPort.Replace("/","").Replace(":","")}.db");
}
}
[Fact(Skip = "This tests is flakey at the moment so ignoring will be fixed long term see https://github.com/TomPallister/Ocelot/issues/245")]
public void should_persist_command_to_five_servers() public void should_persist_command_to_five_servers()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
@ -113,13 +103,13 @@ namespace Ocelot.IntegrationTests
var command = new UpdateFileConfiguration(updatedConfiguration); var command = new UpdateFileConfiguration(updatedConfiguration);
GivenThereIsAConfiguration(configuration); GivenThereIsAConfiguration(configuration);
GivenFiveServersAreRunning(); GivenFiveServersAreRunning();
GivenALeaderIsElected();
GivenIHaveAnOcelotToken("/administration"); GivenIHaveAnOcelotToken("/administration");
WhenISendACommandIntoTheCluster(command); WhenISendACommandIntoTheCluster(command);
Thread.Sleep(5000);
ThenTheCommandIsReplicatedToAllStateMachines(command); ThenTheCommandIsReplicatedToAllStateMachines(command);
} }
[Fact(Skip = "This tests is flakey at the moment so ignoring will be fixed long term see https://github.com/TomPallister/Ocelot/issues/245")] [Fact(Skip = "still broken waiting for work in rafty")]
public void should_persist_command_to_five_servers_when_using_administration_api() public void should_persist_command_to_five_servers_when_using_administration_api()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
@ -166,7 +156,6 @@ namespace Ocelot.IntegrationTests
var command = new UpdateFileConfiguration(updatedConfiguration); var command = new UpdateFileConfiguration(updatedConfiguration);
GivenThereIsAConfiguration(configuration); GivenThereIsAConfiguration(configuration);
GivenFiveServersAreRunning(); GivenFiveServersAreRunning();
GivenALeaderIsElected();
GivenIHaveAnOcelotToken("/administration"); GivenIHaveAnOcelotToken("/administration");
GivenIHaveAddedATokenToMyRequest(); GivenIHaveAddedATokenToMyRequest();
WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration); WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration);
@ -175,37 +164,54 @@ namespace Ocelot.IntegrationTests
private void WhenISendACommandIntoTheCluster(UpdateFileConfiguration command) private void WhenISendACommandIntoTheCluster(UpdateFileConfiguration command)
{ {
var p = _peers.Peers.First(); bool SendCommand()
var json = JsonConvert.SerializeObject(command,new JsonSerializerSettings() {
TypeNameHandling = TypeNameHandling.All
});
var httpContent = new StringContent(json);
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
using(var httpClient = new HttpClient())
{ {
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); try
var response = httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent).GetAwaiter().GetResult(); {
response.EnsureSuccessStatusCode(); var p = _peers.Peers.First();
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); var json = JsonConvert.SerializeObject(command, new JsonSerializerSettings()
var result = JsonConvert.DeserializeObject<OkResponse<UpdateFileConfiguration>>(content); {
result.Command.Configuration.ReRoutes.Count.ShouldBe(2); TypeNameHandling = TypeNameHandling.All
});
var httpContent = new StringContent(json);
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
var response = httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent).GetAwaiter().GetResult();
response.EnsureSuccessStatusCode();
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var errorResult = JsonConvert.DeserializeObject<ErrorResponse<UpdateFileConfiguration>>(content);
if (!string.IsNullOrEmpty(errorResult.Error))
{
return false;
}
var okResult = JsonConvert.DeserializeObject<OkResponse<UpdateFileConfiguration>>(content);
if (okResult.Command.Configuration.ReRoutes.Count == 2)
{
return true;
}
}
return false;
}
catch (Exception e)
{
Console.WriteLine(e);
return false;
}
} }
//dirty sleep to make sure command replicated... var commandSent = WaitFor(20000).Until(() => SendCommand());
var stopwatch = Stopwatch.StartNew(); commandSent.ShouldBeTrue();
while(stopwatch.ElapsedMilliseconds < 10000)
{
}
} }
private void ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expecteds) private void ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expecteds)
{ {
//dirty sleep to give a chance to replicate...
var stopwatch = Stopwatch.StartNew();
while(stopwatch.ElapsedMilliseconds < 2000)
{
}
bool CommandCalledOnAllStateMachines() bool CommandCalledOnAllStateMachines()
{ {
try try
@ -256,6 +262,7 @@ namespace Ocelot.IntegrationTests
} }
catch(Exception e) catch(Exception e)
{ {
//_output.WriteLine($"{e.Message}, {e.StackTrace}");
Console.WriteLine(e); Console.WriteLine(e);
return false; return false;
} }
@ -267,10 +274,29 @@ namespace Ocelot.IntegrationTests
private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration)
{ {
var json = JsonConvert.SerializeObject(updatedConfiguration); bool SendCommand()
var content = new StringContent(json); {
content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); var json = JsonConvert.SerializeObject(updatedConfiguration);
_response = _httpClient.PostAsync(url, content).Result; var content = new StringContent(json);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
_response = _httpClient.PostAsync(url, content).Result;
var responseContent = _response.Content.ReadAsStringAsync().Result;
if(responseContent == "There was a problem. This error message sucks raise an issue in GitHub.")
{
return false;
}
if(string.IsNullOrEmpty(responseContent))
{
return false;
}
return _response.IsSuccessStatusCode;
}
var commandSent = WaitFor(20000).Until(() => SendCommand());
commandSent.ShouldBeTrue();
} }
private void GivenIHaveAddedATokenToMyRequest() private void GivenIHaveAddedATokenToMyRequest()
@ -280,23 +306,40 @@ namespace Ocelot.IntegrationTests
private void GivenIHaveAnOcelotToken(string adminPath) private void GivenIHaveAnOcelotToken(string adminPath)
{ {
var tokenUrl = $"{adminPath}/connect/token"; bool AddToken()
var formData = new List<KeyValuePair<string, string>>
{ {
new KeyValuePair<string, string>("client_id", "admin"), try
new KeyValuePair<string, string>("client_secret", "secret"), {
new KeyValuePair<string, string>("scope", "admin"), var tokenUrl = $"{adminPath}/connect/token";
new KeyValuePair<string, string>("grant_type", "client_credentials") var formData = new List<KeyValuePair<string, string>>
}; {
var content = new FormUrlEncodedContent(formData); new KeyValuePair<string, string>("client_id", "admin"),
new KeyValuePair<string, string>("client_secret", "secret"),
new KeyValuePair<string, string>("scope", "admin"),
new KeyValuePair<string, string>("grant_type", "client_credentials")
};
var content = new FormUrlEncodedContent(formData);
var response = _httpClient.PostAsync(tokenUrl, content).Result; var response = _httpClient.PostAsync(tokenUrl, content).Result;
var responseContent = response.Content.ReadAsStringAsync().Result; var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode(); if(!response.IsSuccessStatusCode)
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent); {
var configPath = $"{adminPath}/.well-known/openid-configuration"; return false;
response = _httpClient.GetAsync(configPath).Result; }
response.EnsureSuccessStatusCode();
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
var configPath = $"{adminPath}/.well-known/openid-configuration";
response = _httpClient.GetAsync(configPath).Result;
return response.IsSuccessStatusCode;
}
catch(Exception)
{
return false;
}
}
var addToken = WaitFor(20000).Until(() => AddToken());
addToken.ShouldBeTrue();
} }
private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
@ -375,18 +418,32 @@ namespace Ocelot.IntegrationTests
foreach (var peer in _peers.Peers) foreach (var peer in _peers.Peers)
{ {
File.Delete(peer.HostAndPort.Replace("/", "").Replace(":", ""));
File.Delete($"{peer.HostAndPort.Replace("/", "").Replace(":", "")}.db");
var thread = new Thread(() => GivenAServerIsRunning(peer.HostAndPort)); var thread = new Thread(() => GivenAServerIsRunning(peer.HostAndPort));
thread.Start(); thread.Start();
_threads.Add(thread); _threads.Add(thread);
} }
} }
private void GivenALeaderIsElected() public void Dispose()
{ {
//dirty sleep to make sure we have a leader foreach (var builder in _builders)
var stopwatch = Stopwatch.StartNew();
while(stopwatch.ElapsedMilliseconds < 20000)
{ {
builder?.Dispose();
}
foreach (var peer in _peers.Peers)
{
try
{
File.Delete(peer.HostAndPort.Replace("/", "").Replace(":", ""));
File.Delete($"{peer.HostAndPort.Replace("/", "").Replace(":", "")}.db");
}
catch (Exception e)
{
Console.WriteLine(e);
}
} }
} }
} }

View File

@ -24,16 +24,16 @@
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" /> <ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
<PackageReference Include="Consul" Version="0.7.2.4" /> <PackageReference Include="Consul" Version="0.7.2.4" />
<PackageReference Include="Polly" Version="5.8.0" /> <PackageReference Include="Polly" Version="6.0.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2"> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>

View File

@ -17,11 +17,11 @@ namespace Ocelot.UnitTests.Configuration
{ {
public class ConsulFileConfigurationPollerTests : IDisposable public class ConsulFileConfigurationPollerTests : IDisposable
{ {
private ConsulFileConfigurationPoller _poller; private readonly ConsulFileConfigurationPoller _poller;
private Mock<IOcelotLoggerFactory> _factory; private Mock<IOcelotLoggerFactory> _factory;
private Mock<IFileConfigurationRepository> _repo; private readonly Mock<IFileConfigurationRepository> _repo;
private Mock<IFileConfigurationSetter> _setter; private readonly Mock<IFileConfigurationSetter> _setter;
private FileConfiguration _fileConfig; private readonly FileConfiguration _fileConfig;
private Mock<IConsulPollerConfiguration> _config; private Mock<IConsulPollerConfiguration> _config;
public ConsulFileConfigurationPollerTests() public ConsulFileConfigurationPollerTests()
@ -70,7 +70,7 @@ namespace Ocelot.UnitTests.Configuration
}; };
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 0)) this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 0))
.Then(x => ThenTheSetterIsCalled(newConfig, 1)) .Then(x => ThenTheSetterIsCalledAtLeast(newConfig, 1))
.BDDfy(); .BDDfy();
} }
@ -154,5 +154,21 @@ namespace Ocelot.UnitTests.Configuration
}); });
result.ShouldBeTrue(); result.ShouldBeTrue();
} }
private void ThenTheSetterIsCalledAtLeast(FileConfiguration fileConfig, int times)
{
var result = WaitFor(2000).Until(() => {
try
{
_setter.Verify(x => x.Set(fileConfig), Times.AtLeast(times));
return true;
}
catch(Exception)
{
return false;
}
});
result.ShouldBeTrue();
}
} }
} }

View File

@ -48,6 +48,7 @@ namespace Ocelot.UnitTests.Configuration
_client _client
.Setup(x => x.KV) .Setup(x => x.KV)
.Returns(_kvEndpoint.Object); .Returns(_kvEndpoint.Object);
_factory _factory
.Setup(x => x.Get(It.IsAny<ConsulRegistryConfiguration>())) .Setup(x => x.Get(It.IsAny<ConsulRegistryConfiguration>()))
.Returns(_client.Object); .Returns(_client.Object);
@ -104,6 +105,46 @@ namespace Ocelot.UnitTests.Configuration
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_set_config_key()
{
var config = FakeFileConfiguration();
this.Given(_ => GivenIHaveAConfiguration(config))
.And(_ => GivenTheConfigKeyComesFromFileConfig("Tom"))
.And(_ => GivenFetchFromConsulSucceeds())
.When(_ => WhenIGetTheConfiguration())
.And(_ => ThenTheConfigKeyIs("Tom"))
.BDDfy();
}
[Fact]
public void should_set_default_config_key()
{
var config = FakeFileConfiguration();
this.Given(_ => GivenIHaveAConfiguration(config))
.And(_ => GivenFetchFromConsulSucceeds())
.When(_ => WhenIGetTheConfiguration())
.And(_ => ThenTheConfigKeyIs("InternalConfiguration"))
.BDDfy();
}
private void ThenTheConfigKeyIs(string expected)
{
_kvEndpoint
.Verify(x => x.Get(expected, It.IsAny<CancellationToken>()), Times.Once);
}
private void GivenTheConfigKeyComesFromFileConfig(string key)
{
_internalRepo
.Setup(x => x.Get())
.Returns(new OkResponse<IInternalConfiguration>(new InternalConfiguration(new List<ReRoute>(), "", new ServiceProviderConfigurationBuilder().WithConfigurationKey(key).Build(), "")));
_repo = new ConsulFileConfigurationRepository(_cache.Object, _internalRepo.Object, _factory.Object, _loggerFactory.Object);
}
private void ThenTheConfigurationIsNull() private void ThenTheConfigurationIsNull()
{ {
_getResult.Data.ShouldBeNull(); _getResult.Data.ShouldBeNull();

View File

@ -29,7 +29,8 @@ namespace Ocelot.UnitTests.Configuration
Host = "127.0.0.1", Host = "127.0.0.1",
Port = 1234, Port = 1234,
Type = "ServiceFabric", Type = "ServiceFabric",
Token = "testtoken" Token = "testtoken",
ConfigurationKey = "woo"
} }
}; };
@ -38,6 +39,7 @@ namespace Ocelot.UnitTests.Configuration
.WithPort(1234) .WithPort(1234)
.WithType("ServiceFabric") .WithType("ServiceFabric")
.WithToken("testtoken") .WithToken("testtoken")
.WithConfigurationKey("woo")
.Build(); .Build();
this.Given(x => x.GivenTheFollowingGlobalConfig(globalConfig)) this.Given(x => x.GivenTheFollowingGlobalConfig(globalConfig))
@ -62,6 +64,7 @@ namespace Ocelot.UnitTests.Configuration
_result.Port.ShouldBe(expected.Port); _result.Port.ShouldBe(expected.Port);
_result.Token.ShouldBe(expected.Token); _result.Token.ShouldBe(expected.Token);
_result.Type.ShouldBe(expected.Type); _result.Type.ShouldBe(expected.Type);
_result.ConfigurationKey.ShouldBe(expected.ConfigurationKey);
} }
} }
} }

View File

@ -126,14 +126,14 @@ namespace Ocelot.UnitTests.Controllers
{ {
_node _node
.Setup(x => x.Accept(It.IsAny<UpdateFileConfiguration>())) .Setup(x => x.Accept(It.IsAny<UpdateFileConfiguration>()))
.Returns(new Rafty.Concensus.OkResponse<UpdateFileConfiguration>(new UpdateFileConfiguration(new FileConfiguration()))); .ReturnsAsync(new Rafty.Concensus.OkResponse<UpdateFileConfiguration>(new UpdateFileConfiguration(new FileConfiguration())));
} }
private void GivenTheNodeReturnsError() private void GivenTheNodeReturnsError()
{ {
_node _node
.Setup(x => x.Accept(It.IsAny<UpdateFileConfiguration>())) .Setup(x => x.Accept(It.IsAny<UpdateFileConfiguration>()))
.Returns(new Rafty.Concensus.ErrorResponse<UpdateFileConfiguration>("error", new UpdateFileConfiguration(new FileConfiguration()))); .ReturnsAsync(new Rafty.Concensus.ErrorResponse<UpdateFileConfiguration>("error", new UpdateFileConfiguration(new FileConfiguration())));
} }
private void GivenTheConfigSetterReturns(Response response) private void GivenTheConfigSetterReturns(Response response)

View File

@ -0,0 +1,40 @@
using System.Threading.Tasks;
using Ocelot.Infrastructure;
using Shouldly;
using Xunit;
namespace Ocelot.UnitTests.Infrastructure
{
public class InMemoryBusTests
{
private readonly InMemoryBus<object> _bus;
public InMemoryBusTests()
{
_bus = new InMemoryBus<object>();
}
[Fact]
public async Task should_publish_with_delay()
{
var called = false;
_bus.Subscribe(x => {
called = true;
});
_bus.Publish(new object(), 1);
await Task.Delay(10);
called.ShouldBeTrue();
}
[Fact]
public void should_not_be_publish_yet_as_no_delay_in_caller()
{
var called = false;
_bus.Subscribe(x => {
called = true;
});
_bus.Publish(new object(), 1);
called.ShouldBeFalse();
}
}
}

View File

@ -1,5 +1,6 @@
namespace Ocelot.UnitTests.LoadBalancer namespace Ocelot.UnitTests.LoadBalancer
{ {
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Responses; using Ocelot.Responses;
@ -10,28 +11,43 @@ namespace Ocelot.UnitTests.LoadBalancer
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections; using System.Collections;
using System.Threading;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.UnitTests.Responder; using Ocelot.UnitTests.Responder;
using TestStack.BDDfy; using TestStack.BDDfy;
using Ocelot.Infrastructure;
public class CookieStickySessionsTests public class CookieStickySessionsTests
{ {
private readonly CookieStickySessions _stickySessions; private readonly CookieStickySessions _stickySessions;
private readonly Mock<ILoadBalancer> _loadBalancer; private readonly Mock<ILoadBalancer> _loadBalancer;
private readonly int _defaultExpiryInMs;
private DownstreamContext _downstreamContext; private DownstreamContext _downstreamContext;
private Response<ServiceHostAndPort> _result; private Response<ServiceHostAndPort> _result;
private Response<ServiceHostAndPort> _firstHostAndPort; private Response<ServiceHostAndPort> _firstHostAndPort;
private Response<ServiceHostAndPort> _secondHostAndPort; private Response<ServiceHostAndPort> _secondHostAndPort;
private readonly FakeBus<StickySession> _bus;
public CookieStickySessionsTests() public CookieStickySessionsTests()
{ {
_bus = new FakeBus<StickySession>();
_loadBalancer = new Mock<ILoadBalancer>(); _loadBalancer = new Mock<ILoadBalancer>();
const int defaultExpiryInMs = 100; _defaultExpiryInMs = 0;
_stickySessions = new CookieStickySessions(_loadBalancer.Object, "sessionid", defaultExpiryInMs); _stickySessions = new CookieStickySessions(_loadBalancer.Object, "sessionid", _defaultExpiryInMs, _bus);
_downstreamContext = new DownstreamContext(new DefaultHttpContext()); _downstreamContext = new DownstreamContext(new DefaultHttpContext());
} }
[Fact]
public void should_expire_sticky_session()
{
this.Given(_ => GivenTheLoadBalancerReturns())
.And(_ => GivenTheDownstreamRequestHasSessionId("321"))
.And(_ => GivenIHackAMessageInWithAPastExpiry())
.And(_ => WhenILease())
.When(_ => WhenTheMessagesAreProcessed())
.Then(_ => ThenTheLoadBalancerIsCalled())
.BDDfy();
}
[Fact] [Fact]
public void should_return_host_and_port() public void should_return_host_and_port()
{ {
@ -48,6 +64,7 @@ namespace Ocelot.UnitTests.LoadBalancer
.And(_ => GivenTheDownstreamRequestHasSessionId("321")) .And(_ => GivenTheDownstreamRequestHasSessionId("321"))
.When(_ => WhenILeaseTwiceInARow()) .When(_ => WhenILeaseTwiceInARow())
.Then(_ => ThenTheFirstAndSecondResponseAreTheSame()) .Then(_ => ThenTheFirstAndSecondResponseAreTheSame())
.And(_ => ThenTheStickySessionWillTimeout())
.BDDfy(); .BDDfy();
} }
@ -69,92 +86,26 @@ namespace Ocelot.UnitTests.LoadBalancer
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_expire_sticky_session()
{
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
.When(_ => WhenTheStickySessionExpires())
.Then(_ => ThenANewHostAndPortIsReturned())
.BDDfy();
}
[Fact]
public void should_refresh_sticky_session()
{
this.Given(_ => GivenTheLoadBalancerReturnsSequence())
.When(_ => WhenIMakeRequestsToKeepRefreshingTheSession())
.Then(_ => ThenTheSessionIsRefreshed())
.BDDfy();
}
[Fact]
public void should_dispose()
{
_stickySessions.Dispose();
}
[Fact] [Fact]
public void should_release() public void should_release()
{ {
_stickySessions.Release(new ServiceHostAndPort("", 0)); _stickySessions.Release(new ServiceHostAndPort("", 0));
} }
private async Task ThenTheSessionIsRefreshed() private void ThenTheLoadBalancerIsCalled()
{ {
var postExpireHostAndPort = await _stickySessions.Lease(_downstreamContext); _loadBalancer.Verify(x => x.Release(It.IsAny<ServiceHostAndPort>()), Times.Once);
postExpireHostAndPort.Data.DownstreamHost.ShouldBe("one");
postExpireHostAndPort.Data.DownstreamPort.ShouldBe(80);
_loadBalancer
.Verify(x => x.Lease(It.IsAny<DownstreamContext>()), Times.Once);
} }
private async Task WhenIMakeRequestsToKeepRefreshingTheSession() private void WhenTheMessagesAreProcessed()
{ {
var context = new DefaultHttpContext(); _bus.Process();
var cookies = new FakeCookies();
cookies.AddCookie("sessionid", "321");
context.Request.Cookies = cookies;
_downstreamContext = new DownstreamContext(context);
var firstHostAndPort = await _stickySessions.Lease(_downstreamContext);
firstHostAndPort.Data.DownstreamHost.ShouldBe("one");
firstHostAndPort.Data.DownstreamPort.ShouldBe(80);
Thread.Sleep(80);
var secondHostAndPort = await _stickySessions.Lease(_downstreamContext);
secondHostAndPort.Data.DownstreamHost.ShouldBe("one");
secondHostAndPort.Data.DownstreamPort.ShouldBe(80);
Thread.Sleep(80);
} }
private async Task ThenANewHostAndPortIsReturned() private void GivenIHackAMessageInWithAPastExpiry()
{ {
var postExpireHostAndPort = await _stickySessions.Lease(_downstreamContext); var hostAndPort = new ServiceHostAndPort("999", 999);
postExpireHostAndPort.Data.DownstreamHost.ShouldBe("two"); _bus.Publish(new StickySession(hostAndPort, DateTime.UtcNow.AddDays(-1), "321"), 0);
postExpireHostAndPort.Data.DownstreamPort.ShouldBe(80);
}
private async Task WhenTheStickySessionExpires()
{
var context = new DefaultHttpContext();
var cookies = new FakeCookies();
cookies.AddCookie("sessionid", "321");
context.Request.Cookies = cookies;
_downstreamContext = new DownstreamContext(context);
var firstHostAndPort = await _stickySessions.Lease(_downstreamContext);
var secondHostAndPort = await _stickySessions.Lease(_downstreamContext);
firstHostAndPort.Data.DownstreamHost.ShouldBe("one");
firstHostAndPort.Data.DownstreamPort.ShouldBe(80);
secondHostAndPort.Data.DownstreamHost.ShouldBe("one");
secondHostAndPort.Data.DownstreamPort.ShouldBe(80);
Thread.Sleep(150);
} }
private void ThenAnErrorIsReturned() private void ThenAnErrorIsReturned()
@ -236,9 +187,14 @@ namespace Ocelot.UnitTests.LoadBalancer
{ {
_result.Data.ShouldNotBeNull(); _result.Data.ShouldNotBeNull();
} }
private void ThenTheStickySessionWillTimeout()
{
_bus.Messages.Count.ShouldBe(2);
}
} }
class FakeCookies : IRequestCookieCollection internal class FakeCookies : IRequestCookieCollection
{ {
private readonly Dictionary<string, string> _cookies = new Dictionary<string, string>(); private readonly Dictionary<string, string> _cookies = new Dictionary<string, string>();
@ -273,4 +229,37 @@ namespace Ocelot.UnitTests.LoadBalancer
return _cookies.GetEnumerator(); return _cookies.GetEnumerator();
} }
} }
internal class FakeBus<T> : IBus<T>
{
public FakeBus()
{
Messages = new List<T>();
Subscriptions = new List<Action<T>>();
}
public List<T> Messages { get; }
public List<Action<T>> Subscriptions { get; }
public void Subscribe(Action<T> action)
{
Subscriptions.Add(action);
}
public void Publish(T message, int delay)
{
Messages.Add(message);
}
public void Process()
{
foreach (var message in Messages)
{
foreach (var subscription in Subscriptions)
{
subscription(message);
}
}
}
}
} }

View File

@ -26,7 +26,7 @@ namespace Ocelot.UnitTests.LoadBalancer
{ {
_factory = new Mock<ILoadBalancerFactory>(); _factory = new Mock<ILoadBalancerFactory>();
_loadBalancerHouse = new LoadBalancerHouse(_factory.Object); _loadBalancerHouse = new LoadBalancerHouse(_factory.Object);
_serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123, string.Empty); _serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123, string.Empty, "configKey");
} }
[Fact] [Fact]

View File

@ -29,30 +29,30 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.2" /> <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2"> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" /> <PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.2" />
<PackageReference Include="Moq" Version="4.8.2" /> <PackageReference Include="Moq" Version="4.8.2" />
<PackageReference Include="Shouldly" Version="3.0.0" /> <PackageReference Include="Shouldly" Version="3.0.0" />
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" /> <PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
<PackageReference Include="xunit" Version="2.3.1" /> <PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" /> <PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
<PackageReference Include="Rafty" Version="0.4.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="WebSockets\" /> <Folder Include="WebSockets\" />
</ItemGroup> </ItemGroup>
</Project> </Project>