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
Ocelot is designed to work with ASP.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.
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.
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.
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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -2,14 +2,14 @@ Getting Started
===============
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
^^^^^^^^^^^^^
**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.
``Install-Package Ocelot``

View File

@ -6,6 +6,7 @@ namespace Ocelot.Configuration.Builder
private int _serviceDiscoveryProviderPort;
private string _type;
private string _token;
private string _configurationKey;
public ServiceProviderConfigurationBuilder WithHost(string serviceDiscoveryProviderHost)
{
@ -31,9 +32,15 @@ namespace Ocelot.Configuration.Builder
return this;
}
public ServiceProviderConfigurationBuilder WithConfigurationKey(string configurationKey)
{
_configurationKey = configurationKey;
return this;
}
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 Ocelot.Configuration.File;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Responses;

View File

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

View File

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

View File

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

View File

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

View File

@ -41,13 +41,15 @@ namespace Ocelot.Configuration
[HttpPost]
public async Task<IActionResult> Post([FromBody]FileConfiguration fileConfiguration)
{
try
{
//todo - this code is a bit shit sort it out..
var test = _provider.GetService(typeof(INode));
if (test != null)
{
var node = (INode)test;
var result = node.Accept(new UpdateFileConfiguration(fileConfiguration));
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.");
@ -65,5 +67,10 @@ namespace Ocelot.Configuration
return new OkObjectResult(fileConfiguration);
}
catch(Exception e)
{
return new BadRequestObjectResult($"{e.Message}:{e.StackTrace}");
}
}
}
}

View File

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

View File

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

View File

@ -2,8 +2,9 @@
{
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;
Port = port;
Token = token;
@ -14,5 +15,6 @@
public int Port { get; }
public string Type { get; }
public string Token { get; }
public string ConfigurationKey { get; }
}
}

View File

@ -3,6 +3,7 @@ namespace Ocelot.Headers
using System.Collections.Generic;
using Ocelot.Configuration.Creator;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Logging;
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,62 +3,65 @@ namespace Ocelot.LoadBalancer.LoadBalancers
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Ocelot.Infrastructure;
using Ocelot.Middleware;
using Responses;
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 ILoadBalancer _loadBalancer;
private readonly ConcurrentDictionary<string, StickySession> _stored;
private readonly Timer _timer;
private bool _expiring;
private readonly IBus<StickySession> _bus;
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;
_expiryInMs = expiryInMs;
_keyExpiryInMs = keyExpiryInMs;
_loadBalancer = loadBalancer;
_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)
{
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;
_bus.Publish(updated, _keyExpiryInMs);
return new OkResponse<ServiceHostAndPort>(updated.HostAndPort);
}
}
var next = await _loadBalancer.Lease(context);
@ -67,9 +70,14 @@ namespace Ocelot.LoadBalancer.LoadBalancers
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);
@ -78,16 +86,5 @@ namespace Ocelot.LoadBalancer.LoadBalancers
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 Ocelot.Configuration;
using Ocelot.Infrastructure;
using Ocelot.ServiceDiscovery;
namespace Ocelot.LoadBalancer.LoadBalancers
@ -25,7 +26,8 @@ namespace Ocelot.LoadBalancer.LoadBalancers
return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName);
case nameof(CookieStickySessions):
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:
return new NoLoadBalancer(await serviceProvider.Get());
}

View File

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

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
<NoPackageAnalysis>true</NoPackageAnalysis>
@ -25,18 +25,26 @@
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<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="IdentityServer4.AccessTokenValidation" Version="2.4.0" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.4" />
<PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="2.0.3" />
<PackageReference Include="Microsoft.Data.SQLite" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" 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">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
@ -45,9 +53,9 @@
<PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.2" />
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.2" />
<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="IdentityServer4" Version="2.1.3" />
<PackageReference Include="Rafty" Version="0.4.2" />
<PackageReference Include="IdentityServer4" Version="2.2.0" />
<PackageReference Include="Rafty" Version="0.4.3" />
</ItemGroup>
</Project>

View File

@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Rafty.FiniteStateMachine;
using Rafty.Infrastructure;
@ -17,7 +18,7 @@ namespace Ocelot.Raft
_id = nodeId.Id.Replace("/","").Replace(":","");
}
public void Handle(LogEntry log)
public Task Handle(LogEntry log)
{
try
{
@ -28,6 +29,8 @@ namespace Ocelot.Raft
{
Console.WriteLine(exception);
}
return Task.CompletedTask;
}
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Ocelot.Configuration;
using Ocelot.Middleware;
@ -33,51 +34,47 @@ namespace Ocelot.Raft
_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)
{
SetToken();
await SetToken();
}
var json = JsonConvert.SerializeObject(requestVote, _jsonSerializerSettings);
var content = new StringContent(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)
{
return JsonConvert.DeserializeObject<RequestVoteResponse>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings);
}
else
{
return new RequestVoteResponse(false, requestVote.Term);
}
return JsonConvert.DeserializeObject<RequestVoteResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
}
public AppendEntriesResponse Request(AppendEntries appendEntries)
return new RequestVoteResponse(false, requestVote.Term);
}
public async Task<AppendEntriesResponse> Request(AppendEntries appendEntries)
{
try
{
if(_token == null)
{
SetToken();
await SetToken();
}
var json = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings);
var content = new StringContent(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)
{
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);
}
}
catch(Exception ex)
{
Console.WriteLine(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
{
Console.WriteLine("SENDING REQUEST....");
if(_token == null)
{
SetToken();
await SetToken();
}
var json = JsonConvert.SerializeObject(command, _jsonSerializerSettings);
var content = new StringContent(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)
{
return JsonConvert.DeserializeObject<OkResponse<T>>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings);
}
else
{
return new ErrorResponse<T>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), command);
}
Console.WriteLine("REQUEST OK....");
var okResponse = JsonConvert.DeserializeObject<OkResponse<ICommand>>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
return new OkResponse<T>((T)okResponse.Command);
}
private void SetToken()
Console.WriteLine("REQUEST NOT OK....");
return new ErrorResponse<T>(await response.Content.ReadAsStringAsync(), command);
}
private async Task SetToken()
{
var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token";
var formData = new List<KeyValuePair<string, string>>
@ -118,8 +117,8 @@ namespace Ocelot.Raft
new KeyValuePair<string, string>("grant_type", "client_credentials")
};
var content = new FormUrlEncodedContent(formData);
var response = _httpClient.PostAsync(tokenUrl, content).GetAwaiter().GetResult();
var responseContent = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var response = await _httpClient.PostAsync(tokenUrl, content);
var responseContent = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode();
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
_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 Rafty.FiniteStateMachine;
using Rafty.Log;
@ -14,12 +15,12 @@ namespace Ocelot.Raft
_setter = setter;
}
public void Handle(LogEntry log)
public async Task Handle(LogEntry log)
{
//todo - handle an error
//hack it to just cast as at the moment we know this is the only command :P
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
{
private readonly INode _node;
private IOcelotLogger _logger;
private string _baseSchemeUrlAndPort;
private JsonSerializerSettings _jsonSerialiserSettings;
private readonly IOcelotLogger _logger;
private readonly string _baseSchemeUrlAndPort;
private readonly JsonSerializerSettings _jsonSerialiserSettings;
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}");
var appendEntriesResponse = _node.Handle(appendEntries);
var appendEntriesResponse = await _node.Handle(appendEntries);
return new OkObjectResult(appendEntriesResponse);
}
@ -62,7 +62,7 @@ namespace Ocelot.Raft
_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);
}
@ -81,7 +81,7 @@ namespace Ocelot.Raft
_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);
@ -91,7 +91,7 @@ namespace Ocelot.Raft
catch(Exception 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 Rafty.Infrastructure;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Ocelot.Raft
{
//todo - use async await
[ExcludeFromCoverage]
public class SqlLiteLog : ILog
{
@ -40,9 +42,7 @@ namespace Ocelot.Raft
}
}
public int LastLogIndex
{
get
public Task<int> LastLogIndex()
{
lock(_lock)
{
@ -61,14 +61,11 @@ namespace Ocelot.Raft
}
}
return result;
}
return Task.FromResult(result);
}
}
public long LastLogTerm
{
get
public Task<long> LastLogTerm ()
{
lock(_lock)
{
@ -91,14 +88,11 @@ namespace Ocelot.Raft
}
}
return result;
}
return Task.FromResult(result);
}
}
public int Count
{
get
public Task<int> Count ()
{
lock(_lock)
{
@ -117,12 +111,11 @@ namespace Ocelot.Raft
}
}
return result;
}
return Task.FromResult(result);
}
}
public int Apply(LogEntry log)
public Task<int> Apply(LogEntry log)
{
lock(_lock)
{
@ -145,13 +138,13 @@ namespace Ocelot.Raft
using(var command = new SqliteCommand(sql, connection))
{
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)
{
@ -180,9 +173,11 @@ namespace Ocelot.Raft
}
}
}
return Task.CompletedTask;
}
public LogEntry Get(int index)
public Task<LogEntry> Get(int index)
{
lock(_lock)
{
@ -199,13 +194,13 @@ namespace Ocelot.Raft
TypeNameHandling = TypeNameHandling.All
};
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)
{
@ -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)
{
@ -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)
{
@ -284,6 +279,8 @@ namespace Ocelot.Raft
}
}
}
return Task.CompletedTask;
}
}
}

View File

@ -52,7 +52,7 @@ namespace Ocelot.Requester
if(context.DownstreamReRoute.DangerousAcceptAnyServerCertificateValidator)
{
httpclientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
httpclientHandler.ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => true;
_logger
.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.readOnly"),
new Scope("openid"),
new Scope("offline_access")
},
ApiSecrets = new List<Secret>()
{

View File

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

View File

@ -664,7 +664,8 @@ namespace Ocelot.AcceptanceTests
{
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();
}
}
@ -701,7 +702,7 @@ namespace Ocelot.AcceptanceTests
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];

View File

@ -76,16 +76,16 @@ namespace Ocelot.Benchmarks
response.EnsureSuccessStatusCode();
}
// * Summary*
// 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
//.NET Core SDK = 2.1.4
/* * Summary*
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
.NET Core SDK = 2.1.4
// [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
// 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 |
[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
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 |*/
private void GivenOcelotIsRunning(string url)
{

View File

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

View File

@ -25,26 +25,26 @@
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
</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="StyleCop.Analyzers" Version="1.0.2">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
<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="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
<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="TestStack.BDDfy" Version="4.3.2" />
<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" />
</ItemGroup>
</Project>

View File

@ -23,6 +23,8 @@ using Ocelot.Middleware;
namespace Ocelot.IntegrationTests
{
using Xunit.Abstractions;
public class RaftTests : IDisposable
{
private readonly List<IWebHost> _builders;
@ -34,9 +36,11 @@ namespace Ocelot.IntegrationTests
private BearerToken _token;
private HttpResponseMessage _response;
private static readonly object _lock = new object();
private ITestOutputHelper _output;
public RaftTests()
public RaftTests(ITestOutputHelper output)
{
_output = output;
_httpClientForAssertions = new HttpClient();
_httpClient = new HttpClient();
var ocelotBaseUrl = "http://localhost:5000";
@ -46,21 +50,7 @@ namespace Ocelot.IntegrationTests
_threads = new List<Thread>();
}
public void Dispose()
{
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")]
[Fact(Skip = "still broken waiting for work in rafty")]
public void should_persist_command_to_five_servers()
{
var configuration = new FileConfiguration
@ -113,13 +103,13 @@ namespace Ocelot.IntegrationTests
var command = new UpdateFileConfiguration(updatedConfiguration);
GivenThereIsAConfiguration(configuration);
GivenFiveServersAreRunning();
GivenALeaderIsElected();
GivenIHaveAnOcelotToken("/administration");
WhenISendACommandIntoTheCluster(command);
Thread.Sleep(5000);
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()
{
var configuration = new FileConfiguration
@ -166,7 +156,6 @@ namespace Ocelot.IntegrationTests
var command = new UpdateFileConfiguration(updatedConfiguration);
GivenThereIsAConfiguration(configuration);
GivenFiveServersAreRunning();
GivenALeaderIsElected();
GivenIHaveAnOcelotToken("/administration");
GivenIHaveAddedATokenToMyRequest();
WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration);
@ -174,9 +163,14 @@ namespace Ocelot.IntegrationTests
}
private void WhenISendACommandIntoTheCluster(UpdateFileConfiguration command)
{
bool SendCommand()
{
try
{
var p = _peers.Peers.First();
var json = JsonConvert.SerializeObject(command,new JsonSerializerSettings() {
var json = JsonConvert.SerializeObject(command, new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
});
var httpContent = new StringContent(json);
@ -187,25 +181,37 @@ namespace Ocelot.IntegrationTests
var response = httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent).GetAwaiter().GetResult();
response.EnsureSuccessStatusCode();
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var result = JsonConvert.DeserializeObject<OkResponse<UpdateFileConfiguration>>(content);
result.Command.Configuration.ReRoutes.Count.ShouldBe(2);
var errorResult = JsonConvert.DeserializeObject<ErrorResponse<UpdateFileConfiguration>>(content);
if (!string.IsNullOrEmpty(errorResult.Error))
{
return false;
}
//dirty sleep to make sure command replicated...
var stopwatch = Stopwatch.StartNew();
while(stopwatch.ElapsedMilliseconds < 10000)
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;
}
}
var commandSent = WaitFor(20000).Until(() => SendCommand());
commandSent.ShouldBeTrue();
}
private void ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expecteds)
{
//dirty sleep to give a chance to replicate...
var stopwatch = Stopwatch.StartNew();
while(stopwatch.ElapsedMilliseconds < 2000)
{
}
bool CommandCalledOnAllStateMachines()
{
try
@ -256,6 +262,7 @@ namespace Ocelot.IntegrationTests
}
catch(Exception e)
{
//_output.WriteLine($"{e.Message}, {e.StackTrace}");
Console.WriteLine(e);
return false;
}
@ -266,11 +273,30 @@ namespace Ocelot.IntegrationTests
}
private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration)
{
bool SendCommand()
{
var json = JsonConvert.SerializeObject(updatedConfiguration);
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()
@ -279,6 +305,10 @@ namespace Ocelot.IntegrationTests
}
private void GivenIHaveAnOcelotToken(string adminPath)
{
bool AddToken()
{
try
{
var tokenUrl = $"{adminPath}/connect/token";
var formData = new List<KeyValuePair<string, string>>
@ -292,11 +322,24 @@ namespace Ocelot.IntegrationTests
var response = _httpClient.PostAsync(tokenUrl, content).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
if(!response.IsSuccessStatusCode)
{
return false;
}
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
var configPath = $"{adminPath}/.well-known/openid-configuration";
response = _httpClient.GetAsync(configPath).Result;
response.EnsureSuccessStatusCode();
return response.IsSuccessStatusCode;
}
catch(Exception)
{
return false;
}
}
var addToken = WaitFor(20000).Until(() => AddToken());
addToken.ShouldBeTrue();
}
private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
@ -375,18 +418,32 @@ namespace Ocelot.IntegrationTests
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));
thread.Start();
_threads.Add(thread);
}
}
private void GivenALeaderIsElected()
public void Dispose()
{
//dirty sleep to make sure we have a leader
var stopwatch = Stopwatch.StartNew();
while(stopwatch.ElapsedMilliseconds < 20000)
foreach (var builder in _builders)
{
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" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.2" />
<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="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">
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@ -17,11 +17,11 @@ namespace Ocelot.UnitTests.Configuration
{
public class ConsulFileConfigurationPollerTests : IDisposable
{
private ConsulFileConfigurationPoller _poller;
private readonly ConsulFileConfigurationPoller _poller;
private Mock<IOcelotLoggerFactory> _factory;
private Mock<IFileConfigurationRepository> _repo;
private Mock<IFileConfigurationSetter> _setter;
private FileConfiguration _fileConfig;
private readonly Mock<IFileConfigurationRepository> _repo;
private readonly Mock<IFileConfigurationSetter> _setter;
private readonly FileConfiguration _fileConfig;
private Mock<IConsulPollerConfiguration> _config;
public ConsulFileConfigurationPollerTests()
@ -70,7 +70,7 @@ namespace Ocelot.UnitTests.Configuration
};
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 0))
.Then(x => ThenTheSetterIsCalled(newConfig, 1))
.Then(x => ThenTheSetterIsCalledAtLeast(newConfig, 1))
.BDDfy();
}
@ -154,5 +154,21 @@ namespace Ocelot.UnitTests.Configuration
});
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
.Setup(x => x.KV)
.Returns(_kvEndpoint.Object);
_factory
.Setup(x => x.Get(It.IsAny<ConsulRegistryConfiguration>()))
.Returns(_client.Object);
@ -104,6 +105,46 @@ namespace Ocelot.UnitTests.Configuration
.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()
{
_getResult.Data.ShouldBeNull();

View File

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

View File

@ -126,14 +126,14 @@ namespace Ocelot.UnitTests.Controllers
{
_node
.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()
{
_node
.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)

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
{
using System;
using System.Threading.Tasks;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Responses;
@ -10,28 +11,43 @@ namespace Ocelot.UnitTests.LoadBalancer
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using System.Collections;
using System.Threading;
using Ocelot.Middleware;
using Ocelot.UnitTests.Responder;
using TestStack.BDDfy;
using Ocelot.Infrastructure;
public class CookieStickySessionsTests
{
private readonly CookieStickySessions _stickySessions;
private readonly Mock<ILoadBalancer> _loadBalancer;
private readonly int _defaultExpiryInMs;
private DownstreamContext _downstreamContext;
private Response<ServiceHostAndPort> _result;
private Response<ServiceHostAndPort> _firstHostAndPort;
private Response<ServiceHostAndPort> _secondHostAndPort;
private readonly FakeBus<StickySession> _bus;
public CookieStickySessionsTests()
{
_bus = new FakeBus<StickySession>();
_loadBalancer = new Mock<ILoadBalancer>();
const int defaultExpiryInMs = 100;
_stickySessions = new CookieStickySessions(_loadBalancer.Object, "sessionid", defaultExpiryInMs);
_defaultExpiryInMs = 0;
_stickySessions = new CookieStickySessions(_loadBalancer.Object, "sessionid", _defaultExpiryInMs, _bus);
_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]
public void should_return_host_and_port()
{
@ -48,6 +64,7 @@ namespace Ocelot.UnitTests.LoadBalancer
.And(_ => GivenTheDownstreamRequestHasSessionId("321"))
.When(_ => WhenILeaseTwiceInARow())
.Then(_ => ThenTheFirstAndSecondResponseAreTheSame())
.And(_ => ThenTheStickySessionWillTimeout())
.BDDfy();
}
@ -69,92 +86,26 @@ namespace Ocelot.UnitTests.LoadBalancer
.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]
public void should_release()
{
_stickySessions.Release(new ServiceHostAndPort("", 0));
}
private async Task ThenTheSessionIsRefreshed()
private void ThenTheLoadBalancerIsCalled()
{
var postExpireHostAndPort = await _stickySessions.Lease(_downstreamContext);
postExpireHostAndPort.Data.DownstreamHost.ShouldBe("one");
postExpireHostAndPort.Data.DownstreamPort.ShouldBe(80);
_loadBalancer
.Verify(x => x.Lease(It.IsAny<DownstreamContext>()), Times.Once);
_loadBalancer.Verify(x => x.Release(It.IsAny<ServiceHostAndPort>()), Times.Once);
}
private async Task WhenIMakeRequestsToKeepRefreshingTheSession()
private void WhenTheMessagesAreProcessed()
{
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);
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);
_bus.Process();
}
private async Task ThenANewHostAndPortIsReturned()
private void GivenIHackAMessageInWithAPastExpiry()
{
var postExpireHostAndPort = await _stickySessions.Lease(_downstreamContext);
postExpireHostAndPort.Data.DownstreamHost.ShouldBe("two");
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);
var hostAndPort = new ServiceHostAndPort("999", 999);
_bus.Publish(new StickySession(hostAndPort, DateTime.UtcNow.AddDays(-1), "321"), 0);
}
private void ThenAnErrorIsReturned()
@ -236,9 +187,14 @@ namespace Ocelot.UnitTests.LoadBalancer
{
_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>();
@ -273,4 +229,37 @@ namespace Ocelot.UnitTests.LoadBalancer
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>();
_loadBalancerHouse = new LoadBalancerHouse(_factory.Object);
_serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123, string.Empty);
_serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123, string.Empty, "configKey");
}
[Fact]

View File

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