mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-06-19 07:08:14 +08:00
Raft round 2 (#182)
* brought in rafty * moved raft classes into Ocelot and deleted from int project * started to set up rafty in Ocelot * RAFTY INSIDE OCELOT...WOOT * more work adding rafty...just need to get auth working now * rudimentary authenticated raft requests working * asyn await stuff * hacked rafty into the fileconfigurationcontroller...everything seems to be working roughly but I have a lot of refactoring to do * updated to latest rafty that doesnt need an id * hacky but all tests passing * changed admin area set up to use builder not configuration.json, changed admin area auth to use client credentials * missing code coverage * ignore raft sectionf for code coverage * ignore raft sectionf for code coverage * back to normal filters * try exclude attr * missed these * moved client secret to builder for authentication and updated docs * lock to try and fix error accessing identity server created temprsa file on build server * updated postman scripts and changed Ocelot to not always use type handling as this looked crap when manually accessing the configuration endpoint * added rafty docs * changes I missed * added serialisation code we need for rafty to process commands when they proxy to leader * moved controllers into their feature slices
This commit is contained in:
16
src/Ocelot/Authentication/BearerToken.cs
Normal file
16
src/Ocelot/Authentication/BearerToken.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Ocelot.Authentication
|
||||
{
|
||||
class BearerToken
|
||||
{
|
||||
[JsonProperty("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
[JsonProperty("expires_in")]
|
||||
public int ExpiresIn { get; set; }
|
||||
|
||||
[JsonProperty("token_type")]
|
||||
public string TokenType { get; set; }
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Ocelot.Cache;
|
||||
using Ocelot.Configuration.Provider;
|
||||
|
||||
namespace Ocelot.Controllers
|
||||
namespace Ocelot.Cache
|
||||
{
|
||||
[Authorize]
|
||||
[Route("outputcache")]
|
@ -1,53 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityServer4.Models;
|
||||
using IdentityServer4.Validation;
|
||||
using Ocelot.Configuration.Provider;
|
||||
|
||||
namespace Ocelot.Configuration.Authentication
|
||||
{
|
||||
public class OcelotResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
|
||||
{
|
||||
private readonly IHashMatcher _matcher;
|
||||
private readonly IIdentityServerConfiguration _identityServerConfiguration;
|
||||
|
||||
public OcelotResourceOwnerPasswordValidator(IHashMatcher matcher, IIdentityServerConfiguration identityServerConfiguration)
|
||||
{
|
||||
_identityServerConfiguration = identityServerConfiguration;
|
||||
_matcher = matcher;
|
||||
}
|
||||
|
||||
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = _identityServerConfiguration.Users.FirstOrDefault(u => u.UserName == context.UserName);
|
||||
|
||||
if(user == null)
|
||||
{
|
||||
context.Result = new GrantValidationResult(
|
||||
TokenRequestErrors.InvalidGrant,
|
||||
"invalid custom credential");
|
||||
}
|
||||
else if(_matcher.Match(context.Password, user.Salt, user.Hash))
|
||||
{
|
||||
context.Result = new GrantValidationResult(
|
||||
subject: "admin",
|
||||
authenticationMethod: "custom");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Result = new GrantValidationResult(
|
||||
TokenRequestErrors.InvalidGrant,
|
||||
"invalid custom credential");
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ using Ocelot.Configuration.Builder;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Parser;
|
||||
using Ocelot.Configuration.Validator;
|
||||
using Ocelot.DependencyInjection;
|
||||
using Ocelot.LoadBalancer;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
using Ocelot.Logging;
|
||||
@ -35,6 +36,8 @@ namespace Ocelot.Configuration.Creator
|
||||
private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator;
|
||||
private readonly IRegionCreator _regionCreator;
|
||||
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
|
||||
private readonly IAdministrationPath _adminPath;
|
||||
|
||||
|
||||
public FileOcelotConfigurationCreator(
|
||||
IOptions<FileConfiguration> options,
|
||||
@ -49,9 +52,11 @@ namespace Ocelot.Configuration.Creator
|
||||
IReRouteOptionsCreator fileReRouteOptionsCreator,
|
||||
IRateLimitOptionsCreator rateLimitOptionsCreator,
|
||||
IRegionCreator regionCreator,
|
||||
IHttpHandlerOptionsCreator httpHandlerOptionsCreator
|
||||
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
|
||||
IAdministrationPath adminPath
|
||||
)
|
||||
{
|
||||
_adminPath = adminPath;
|
||||
_regionCreator = regionCreator;
|
||||
_rateLimitOptionsCreator = rateLimitOptionsCreator;
|
||||
_requestIdKeyCreator = requestIdKeyCreator;
|
||||
@ -92,7 +97,7 @@ namespace Ocelot.Configuration.Creator
|
||||
|
||||
var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
|
||||
|
||||
var config = new OcelotConfiguration(reRoutes, fileConfiguration.GlobalConfiguration.AdministrationPath, serviceProviderConfiguration);
|
||||
var config = new OcelotConfiguration(reRoutes, _adminPath.Path, serviceProviderConfiguration);
|
||||
|
||||
return new OkResponse<IOcelotConfiguration>(config);
|
||||
}
|
||||
|
@ -8,29 +8,16 @@ namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public static class IdentityServerConfigurationCreator
|
||||
{
|
||||
public static IdentityServerConfiguration GetIdentityServerConfiguration()
|
||||
public static IdentityServerConfiguration GetIdentityServerConfiguration(string secret)
|
||||
{
|
||||
var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME");
|
||||
var hash = Environment.GetEnvironmentVariable("OCELOT_HASH");
|
||||
var salt = Environment.GetEnvironmentVariable("OCELOT_SALT");
|
||||
var credentialsSigningCertificateLocation = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE");
|
||||
var credentialsSigningCertificatePassword = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD");
|
||||
|
||||
return new IdentityServerConfiguration(
|
||||
"admin",
|
||||
false,
|
||||
SupportedTokens.Both,
|
||||
"secret",
|
||||
secret,
|
||||
new List<string> { "admin", "openid", "offline_access" },
|
||||
"Ocelot Administration",
|
||||
true,
|
||||
GrantTypes.ResourceOwnerPassword,
|
||||
AccessTokenType.Jwt,
|
||||
false,
|
||||
new List<User>
|
||||
{
|
||||
new User("admin", username, hash, salt)
|
||||
},
|
||||
credentialsSigningCertificateLocation,
|
||||
credentialsSigningCertificatePassword
|
||||
);
|
||||
|
@ -12,7 +12,6 @@ namespace Ocelot.Configuration.File
|
||||
public string RequestIdKey { get; set; }
|
||||
|
||||
public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;}
|
||||
public string AdministrationPath {get;set;}
|
||||
|
||||
public FileRateLimitOptions RateLimitOptions { get; set; }
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Provider;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Ocelot.Raft;
|
||||
using Rafty.Concensus;
|
||||
|
||||
namespace Ocelot.Controllers
|
||||
namespace Ocelot.Configuration
|
||||
{
|
||||
[Authorize]
|
||||
[Route("configuration")]
|
||||
@ -13,11 +17,13 @@ namespace Ocelot.Controllers
|
||||
{
|
||||
private readonly IFileConfigurationProvider _configGetter;
|
||||
private readonly IFileConfigurationSetter _configSetter;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public FileConfigurationController(IFileConfigurationProvider getFileConfig, IFileConfigurationSetter configSetter)
|
||||
public FileConfigurationController(IFileConfigurationProvider getFileConfig, IFileConfigurationSetter configSetter, IServiceProvider serviceProvider)
|
||||
{
|
||||
_configGetter = getFileConfig;
|
||||
_configSetter = configSetter;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -36,9 +42,23 @@ namespace Ocelot.Controllers
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Post([FromBody]FileConfiguration fileConfiguration)
|
||||
{
|
||||
//todo - this code is a bit shit sort it out..
|
||||
var test = _serviceProvider.GetService(typeof(INode));
|
||||
if (test != null)
|
||||
{
|
||||
var node = (INode)test;
|
||||
var result = 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);
|
||||
}
|
||||
|
||||
var response = await _configSetter.Set(fileConfiguration);
|
||||
|
||||
if(response.IsError)
|
||||
|
||||
if (response.IsError)
|
||||
{
|
||||
return new BadRequestObjectResult(response.Errors);
|
||||
}
|
||||
@ -46,4 +66,4 @@ namespace Ocelot.Controllers
|
||||
return new OkObjectResult(fileConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,16 +7,9 @@ namespace Ocelot.Configuration.Provider
|
||||
public interface IIdentityServerConfiguration
|
||||
{
|
||||
string ApiName { get; }
|
||||
string ApiSecret { get; }
|
||||
bool RequireHttps { get; }
|
||||
List<string> AllowedScopes { get; }
|
||||
SupportedTokens SupportedTokens { get; }
|
||||
string ApiSecret { get; }
|
||||
string Description {get;}
|
||||
bool Enabled {get;}
|
||||
IEnumerable<string> AllowedGrantTypes {get;}
|
||||
AccessTokenType AccessTokenType {get;}
|
||||
bool RequireClientSecret {get;}
|
||||
List<User> Users {get;}
|
||||
string CredentialsSigningCertificateLocation { get; }
|
||||
string CredentialsSigningCertificatePassword { get; }
|
||||
}
|
||||
|
@ -9,27 +9,15 @@ namespace Ocelot.Configuration.Provider
|
||||
public IdentityServerConfiguration(
|
||||
string apiName,
|
||||
bool requireHttps,
|
||||
SupportedTokens supportedTokens,
|
||||
string apiSecret,
|
||||
List<string> allowedScopes,
|
||||
string description,
|
||||
bool enabled,
|
||||
IEnumerable<string> grantType,
|
||||
AccessTokenType accessTokenType,
|
||||
bool requireClientSecret,
|
||||
List<User> users, string credentialsSigningCertificateLocation, string credentialsSigningCertificatePassword)
|
||||
string credentialsSigningCertificateLocation,
|
||||
string credentialsSigningCertificatePassword)
|
||||
{
|
||||
ApiName = apiName;
|
||||
RequireHttps = requireHttps;
|
||||
SupportedTokens = supportedTokens;
|
||||
ApiSecret = apiSecret;
|
||||
AllowedScopes = allowedScopes;
|
||||
Description = description;
|
||||
Enabled = enabled;
|
||||
AllowedGrantTypes = grantType;
|
||||
AccessTokenType = accessTokenType;
|
||||
RequireClientSecret = requireClientSecret;
|
||||
Users = users;
|
||||
CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation;
|
||||
CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword;
|
||||
}
|
||||
@ -37,14 +25,7 @@ namespace Ocelot.Configuration.Provider
|
||||
public string ApiName { get; private set; }
|
||||
public bool RequireHttps { get; private set; }
|
||||
public List<string> AllowedScopes { get; private set; }
|
||||
public SupportedTokens SupportedTokens { get; private set; }
|
||||
public string ApiSecret { get; private set; }
|
||||
public string Description {get;private set;}
|
||||
public bool Enabled {get;private set;}
|
||||
public IEnumerable<string> AllowedGrantTypes {get;private set;}
|
||||
public AccessTokenType AccessTokenType {get;private set;}
|
||||
public bool RequireClientSecret {get;private set;}
|
||||
public List<User> Users {get;private set;}
|
||||
public string CredentialsSigningCertificateLocation { get; private set; }
|
||||
public string CredentialsSigningCertificatePassword { get; private set; }
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
namespace Ocelot.Configuration.Provider
|
||||
{
|
||||
public class User
|
||||
{
|
||||
public User(string subject, string userName, string hash, string salt)
|
||||
{
|
||||
Subject = subject;
|
||||
UserName = userName;
|
||||
Hash = hash;
|
||||
Salt = salt;
|
||||
}
|
||||
public string Subject { get; private set; }
|
||||
public string UserName { get; private set; }
|
||||
public string Hash { get; private set; }
|
||||
public string Salt { get; private set; }
|
||||
}
|
||||
}
|
@ -7,5 +7,6 @@ namespace Ocelot.DependencyInjection
|
||||
{
|
||||
IOcelotBuilder AddStoreOcelotConfigurationInConsul();
|
||||
IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings);
|
||||
IOcelotAdministrationBuilder AddAdministration(string path, string secret);
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ using Ocelot.Configuration.Provider;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Ocelot.Configuration.Validator;
|
||||
using Ocelot.Controllers;
|
||||
using Ocelot.DownstreamRouteFinder.Finder;
|
||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||
using Ocelot.DownstreamUrlCreator;
|
||||
@ -47,6 +46,12 @@ using Ocelot.Configuration.Builder;
|
||||
using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using System.Linq;
|
||||
using Ocelot.Raft;
|
||||
using Rafty.Concensus;
|
||||
using Rafty.FiniteStateMachine;
|
||||
using Rafty.Infrastructure;
|
||||
using Rafty.Log;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Ocelot.DependencyInjection
|
||||
{
|
||||
@ -121,14 +126,6 @@ namespace Ocelot.DependencyInjection
|
||||
_services.AddMemoryCache();
|
||||
_services.TryAddSingleton<OcelotDiagnosticListener>();
|
||||
|
||||
//add identity server for admin area
|
||||
var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration();
|
||||
|
||||
if (identityServerConfiguration != null)
|
||||
{
|
||||
AddIdentityServer(identityServerConfiguration);
|
||||
}
|
||||
|
||||
//add asp.net services..
|
||||
var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly;
|
||||
|
||||
@ -141,6 +138,24 @@ namespace Ocelot.DependencyInjection
|
||||
_services.AddLogging();
|
||||
_services.AddMiddlewareAnalysis();
|
||||
_services.AddWebEncoders();
|
||||
_services.AddSingleton<IAdministrationPath>(new NullAdministrationPath());
|
||||
}
|
||||
|
||||
public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
|
||||
{
|
||||
var administrationPath = new AdministrationPath(path);
|
||||
|
||||
//add identity server for admin area
|
||||
var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(secret);
|
||||
|
||||
if (identityServerConfiguration != null)
|
||||
{
|
||||
AddIdentityServer(identityServerConfiguration, administrationPath);
|
||||
}
|
||||
|
||||
var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath);
|
||||
_services.Replace(descriptor);
|
||||
return new OcelotAdministrationBuilder(_services, _configurationRoot);
|
||||
}
|
||||
|
||||
public IOcelotBuilder AddStoreOcelotConfigurationInConsul()
|
||||
@ -185,7 +200,7 @@ namespace Ocelot.DependencyInjection
|
||||
return this;
|
||||
}
|
||||
|
||||
private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration)
|
||||
private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath)
|
||||
{
|
||||
_services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
|
||||
_services.TryAddSingleton<IHashMatcher, HashMatcher>();
|
||||
@ -194,8 +209,7 @@ namespace Ocelot.DependencyInjection
|
||||
o.IssuerUri = "Ocelot";
|
||||
})
|
||||
.AddInMemoryApiResources(Resources(identityServerConfiguration))
|
||||
.AddInMemoryClients(Client(identityServerConfiguration))
|
||||
.AddResourceOwnerValidator<OcelotResourceOwnerPasswordValidator>();
|
||||
.AddInMemoryClients(Client(identityServerConfiguration));
|
||||
|
||||
//todo - refactor a method so we know why this is happening
|
||||
var whb = _services.First(x => x.ServiceType == typeof(IWebHostBuilder));
|
||||
@ -206,8 +220,7 @@ namespace Ocelot.DependencyInjection
|
||||
_services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddIdentityServerAuthentication(o =>
|
||||
{
|
||||
var adminPath = _configurationRoot.GetValue("GlobalConfiguration:AdministrationPath", string.Empty);
|
||||
o.Authority = baseSchemeUrlAndPort + adminPath;
|
||||
o.Authority = baseSchemeUrlAndPort + adminPath.Path;
|
||||
o.ApiName = identityServerConfiguration.ApiName;
|
||||
o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps;
|
||||
o.SupportedTokens = SupportedTokens.Both;
|
||||
@ -240,7 +253,7 @@ namespace Ocelot.DependencyInjection
|
||||
Value = identityServerConfiguration.ApiSecret.Sha256()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -251,12 +264,65 @@ namespace Ocelot.DependencyInjection
|
||||
new Client
|
||||
{
|
||||
ClientId = identityServerConfiguration.ApiName,
|
||||
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
|
||||
AllowedGrantTypes = GrantTypes.ClientCredentials,
|
||||
ClientSecrets = new List<Secret> {new Secret(identityServerConfiguration.ApiSecret.Sha256())},
|
||||
AllowedScopes = { identityServerConfiguration.ApiName }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public interface IOcelotAdministrationBuilder
|
||||
{
|
||||
IOcelotAdministrationBuilder AddRafty();
|
||||
}
|
||||
|
||||
public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder
|
||||
{
|
||||
private IServiceCollection _services;
|
||||
private IConfigurationRoot _configurationRoot;
|
||||
|
||||
public OcelotAdministrationBuilder(IServiceCollection services, IConfigurationRoot configurationRoot)
|
||||
{
|
||||
_configurationRoot = configurationRoot;
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public IOcelotAdministrationBuilder AddRafty()
|
||||
{
|
||||
var settings = new InMemorySettings(4000, 5000, 100, 5000);
|
||||
_services.AddSingleton<ILog, SqlLiteLog>();
|
||||
_services.AddSingleton<IFiniteStateMachine, OcelotFiniteStateMachine>();
|
||||
_services.AddSingleton<ISettings>(settings);
|
||||
_services.AddSingleton<IPeersProvider, FilePeersProvider>();
|
||||
_services.AddSingleton<INode, Node>();
|
||||
_services.Configure<FilePeers>(_configurationRoot);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IAdministrationPath
|
||||
{
|
||||
string Path {get;}
|
||||
}
|
||||
|
||||
public class NullAdministrationPath : IAdministrationPath
|
||||
{
|
||||
public NullAdministrationPath()
|
||||
{
|
||||
Path = null;
|
||||
}
|
||||
|
||||
public string Path {get;private set;}
|
||||
}
|
||||
|
||||
public class AdministrationPath : IAdministrationPath
|
||||
{
|
||||
public AdministrationPath(string path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public string Path {get;private set;}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Ocelot.Authentication.Middleware;
|
||||
using Ocelot.Cache.Middleware;
|
||||
using Ocelot.Claims.Middleware;
|
||||
using Ocelot.Controllers;
|
||||
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||
using Ocelot.DownstreamUrlCreator.Middleware;
|
||||
using Ocelot.Errors.Middleware;
|
||||
@ -23,12 +22,15 @@ using Ocelot.RateLimit.Middleware;
|
||||
namespace Ocelot.Middleware
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Authorisation.Middleware;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Creator;
|
||||
using Ocelot.Configuration.File;
|
||||
@ -36,7 +38,10 @@ namespace Ocelot.Middleware
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Ocelot.LoadBalancer.Middleware;
|
||||
using Ocelot.Raft;
|
||||
using Ocelot.Responses;
|
||||
using Rafty.Concensus;
|
||||
using Rafty.Infrastructure;
|
||||
|
||||
public static class OcelotMiddlewareExtensions
|
||||
{
|
||||
@ -64,6 +69,11 @@ namespace Ocelot.Middleware
|
||||
|
||||
await CreateAdministrationArea(builder, configuration);
|
||||
|
||||
if(UsingRafty(builder))
|
||||
{
|
||||
SetUpRafty(builder);
|
||||
}
|
||||
|
||||
ConfigureDiagnosticListener(builder);
|
||||
|
||||
// This is registered to catch any global exceptions that are not handled
|
||||
@ -149,6 +159,26 @@ namespace Ocelot.Middleware
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static bool UsingRafty(IApplicationBuilder builder)
|
||||
{
|
||||
var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode;
|
||||
if(possible != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SetUpRafty(IApplicationBuilder builder)
|
||||
{
|
||||
var applicationLifetime = (IApplicationLifetime)builder.ApplicationServices.GetService(typeof(IApplicationLifetime));
|
||||
applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder));
|
||||
var node = (INode)builder.ApplicationServices.GetService(typeof(INode));
|
||||
var nodeId = (NodeId)builder.ApplicationServices.GetService(typeof(NodeId));
|
||||
node.Start(nodeId.Id);
|
||||
}
|
||||
|
||||
private static async Task<IOcelotConfiguration> CreateConfiguration(IApplicationBuilder builder)
|
||||
{
|
||||
var deps = GetDependencies(builder);
|
||||
@ -183,7 +213,7 @@ namespace Ocelot.Middleware
|
||||
return response == null || response.IsError;
|
||||
}
|
||||
|
||||
private static bool ConfigurationNotSetUp(Response<IOcelotConfiguration> ocelotConfiguration)
|
||||
private static bool ConfigurationNotSetUp(Ocelot.Responses.Response<IOcelotConfiguration> ocelotConfiguration)
|
||||
{
|
||||
return ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError;
|
||||
}
|
||||
@ -247,6 +277,7 @@ namespace Ocelot.Middleware
|
||||
return new ErrorResponse(ocelotConfig.Errors);
|
||||
}
|
||||
config = await ocelotConfigurationRepository.AddOrReplace(ocelotConfig.Data);
|
||||
//todo - this starts the poller if it has been registered...please this is so bad.
|
||||
var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller));
|
||||
}
|
||||
|
||||
@ -292,5 +323,11 @@ namespace Ocelot.Middleware
|
||||
diagnosticListener.SubscribeWithAdapter(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnShutdown(IApplicationBuilder app)
|
||||
{
|
||||
var node = (INode)app.ApplicationServices.GetService(typeof(INode));
|
||||
node.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||
@ -11,39 +10,37 @@
|
||||
<PackageId>Ocelot</PackageId>
|
||||
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
|
||||
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
<Authors>Tom Pallister</Authors>
|
||||
<Authors>Tom Pallister</Authors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="7.2.1" />
|
||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.0" />
|
||||
<PackageReference Include="CacheManager.Core" Version="1.1.1" />
|
||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.1" />
|
||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.1" />
|
||||
<PackageReference Include="Consul" Version="0.7.2.3" />
|
||||
<PackageReference Include="Polly" Version="5.3.1" />
|
||||
<PackageReference Include="IdentityServer4" Version="2.0.2" />
|
||||
<PackageReference Include="FluentValidation" Version="7.2.1"/>
|
||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.1.0"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0"/>
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.0"/>
|
||||
<PackageReference Include="CacheManager.Core" Version="1.1.1"/>
|
||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.1"/>
|
||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.1"/>
|
||||
<PackageReference Include="Consul" Version="0.7.2.3"/>
|
||||
<PackageReference Include="Polly" Version="5.3.1"/>
|
||||
<PackageReference Include="IdentityServer4" Version="2.0.2"/>
|
||||
<PackageReference Include="Rafty" Version="0.4.2"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
7
src/Ocelot/Raft/ExcludeFromCoverage.cs
Normal file
7
src/Ocelot/Raft/ExcludeFromCoverage.cs
Normal file
@ -0,0 +1,7 @@
|
||||
using System;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method|AttributeTargets.Property)]
|
||||
public class ExcludeFromCoverageAttribute : Attribute{}
|
||||
}
|
15
src/Ocelot/Raft/FakeCommand.cs
Normal file
15
src/Ocelot/Raft/FakeCommand.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Rafty.FiniteStateMachine;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class FakeCommand : ICommand
|
||||
{
|
||||
public FakeCommand(string value)
|
||||
{
|
||||
this.Value = value;
|
||||
|
||||
}
|
||||
public string Value { get; private set; }
|
||||
}
|
||||
}
|
33
src/Ocelot/Raft/FileFsm.cs
Normal file
33
src/Ocelot/Raft/FileFsm.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Rafty.FiniteStateMachine;
|
||||
using Rafty.Infrastructure;
|
||||
using Rafty.Log;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class FileFsm : IFiniteStateMachine
|
||||
{
|
||||
private string _id;
|
||||
|
||||
public FileFsm(NodeId nodeId)
|
||||
{
|
||||
_id = nodeId.Id.Replace("/","").Replace(":","");
|
||||
}
|
||||
|
||||
public void Handle(LogEntry log)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(log.CommandData);
|
||||
File.AppendAllText(_id, json);
|
||||
}
|
||||
catch(Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
src/Ocelot/Raft/FilePeer.cs
Normal file
8
src/Ocelot/Raft/FilePeer.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class FilePeer
|
||||
{
|
||||
public string HostAndPort { get; set; }
|
||||
}
|
||||
}
|
15
src/Ocelot/Raft/FilePeers.cs
Normal file
15
src/Ocelot/Raft/FilePeers.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class FilePeers
|
||||
{
|
||||
public FilePeers()
|
||||
{
|
||||
Peers = new List<FilePeer>();
|
||||
}
|
||||
|
||||
public List<FilePeer> Peers {get; set;}
|
||||
}
|
||||
}
|
44
src/Ocelot/Raft/FilePeersProvider.cs
Normal file
44
src/Ocelot/Raft/FilePeersProvider.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Provider;
|
||||
using Rafty.Concensus;
|
||||
using Rafty.Infrastructure;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class FilePeersProvider : IPeersProvider
|
||||
{
|
||||
private readonly IOptions<FilePeers> _options;
|
||||
private List<IPeer> _peers;
|
||||
private IWebHostBuilder _builder;
|
||||
private IOcelotConfigurationProvider _provider;
|
||||
private IIdentityServerConfiguration _identityServerConfig;
|
||||
|
||||
public FilePeersProvider(IOptions<FilePeers> options, IWebHostBuilder builder, IOcelotConfigurationProvider provider, IIdentityServerConfiguration identityServerConfig)
|
||||
{
|
||||
_identityServerConfig = identityServerConfig;
|
||||
_provider = provider;
|
||||
_builder = builder;
|
||||
_options = options;
|
||||
_peers = new List<IPeer>();
|
||||
//todo - sort out async nonsense..
|
||||
var config = _provider.Get().GetAwaiter().GetResult();
|
||||
foreach (var item in _options.Value.Peers)
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
//todo what if this errors?
|
||||
var httpPeer = new HttpPeer(item.HostAndPort, httpClient, _builder, config.Data, _identityServerConfig);
|
||||
_peers.Add(httpPeer);
|
||||
}
|
||||
}
|
||||
public List<IPeer> Get()
|
||||
{
|
||||
return _peers;
|
||||
}
|
||||
}
|
||||
}
|
128
src/Ocelot/Raft/HttpPeer.cs
Normal file
128
src/Ocelot/Raft/HttpPeer.cs
Normal file
@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Authentication;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Provider;
|
||||
using Rafty.Concensus;
|
||||
using Rafty.FiniteStateMachine;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class HttpPeer : IPeer
|
||||
{
|
||||
private string _hostAndPort;
|
||||
private HttpClient _httpClient;
|
||||
private JsonSerializerSettings _jsonSerializerSettings;
|
||||
private string _baseSchemeUrlAndPort;
|
||||
private BearerToken _token;
|
||||
private IOcelotConfiguration _config;
|
||||
private IIdentityServerConfiguration _identityServerConfiguration;
|
||||
|
||||
public HttpPeer(string hostAndPort, HttpClient httpClient, IWebHostBuilder builder, IOcelotConfiguration config, IIdentityServerConfiguration identityServerConfiguration)
|
||||
{
|
||||
_identityServerConfiguration = identityServerConfiguration;
|
||||
_config = config;
|
||||
Id = hostAndPort;
|
||||
_hostAndPort = hostAndPort;
|
||||
_httpClient = httpClient;
|
||||
_jsonSerializerSettings = new JsonSerializerSettings() {
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
_baseSchemeUrlAndPort = builder.GetSetting(WebHostDefaults.ServerUrlsKey);
|
||||
}
|
||||
|
||||
public string Id {get; private set;}
|
||||
|
||||
public RequestVoteResponse Request(RequestVote requestVote)
|
||||
{
|
||||
if(_token == null)
|
||||
{
|
||||
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();
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<RequestVoteResponse>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RequestVoteResponse(false, requestVote.Term);
|
||||
}
|
||||
}
|
||||
|
||||
public AppendEntriesResponse Request(AppendEntries appendEntries)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(_token == null)
|
||||
{
|
||||
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();
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<AppendEntriesResponse>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(),_jsonSerializerSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new AppendEntriesResponse(appendEntries.Term, false);
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
return new AppendEntriesResponse(appendEntries.Term, false);
|
||||
}
|
||||
}
|
||||
|
||||
public Response<T> Request<T>(T command) where T : ICommand
|
||||
{
|
||||
if(_token == null)
|
||||
{
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetToken()
|
||||
{
|
||||
var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token";
|
||||
var formData = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("client_id", _identityServerConfiguration.ApiName),
|
||||
new KeyValuePair<string, string>("client_secret", _identityServerConfiguration.ApiSecret),
|
||||
new KeyValuePair<string, string>("scope", _identityServerConfiguration.ApiName),
|
||||
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();
|
||||
response.EnsureSuccessStatusCode();
|
||||
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken);
|
||||
}
|
||||
}
|
||||
}
|
25
src/Ocelot/Raft/OcelotFiniteStateMachine.cs
Normal file
25
src/Ocelot/Raft/OcelotFiniteStateMachine.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Rafty.FiniteStateMachine;
|
||||
using Rafty.Log;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class OcelotFiniteStateMachine : IFiniteStateMachine
|
||||
{
|
||||
private IFileConfigurationSetter _setter;
|
||||
|
||||
public OcelotFiniteStateMachine(IFileConfigurationSetter setter)
|
||||
{
|
||||
_setter = setter;
|
||||
}
|
||||
|
||||
public void 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();;
|
||||
}
|
||||
}
|
||||
}
|
84
src/Ocelot/Raft/RaftController.cs
Normal file
84
src/Ocelot/Raft/RaftController.cs
Normal file
@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Raft;
|
||||
using Rafty.Concensus;
|
||||
using Rafty.FiniteStateMachine;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
[Authorize]
|
||||
[Route("raft")]
|
||||
public class RaftController : Controller
|
||||
{
|
||||
private readonly INode _node;
|
||||
private IOcelotLogger _logger;
|
||||
private string _baseSchemeUrlAndPort;
|
||||
private JsonSerializerSettings _jsonSerialiserSettings;
|
||||
|
||||
public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IWebHostBuilder builder)
|
||||
{
|
||||
_jsonSerialiserSettings = new JsonSerializerSettings {
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
_baseSchemeUrlAndPort = builder.GetSetting(WebHostDefaults.ServerUrlsKey);
|
||||
_logger = loggerFactory.CreateLogger<RaftController>();
|
||||
_node = node;
|
||||
}
|
||||
|
||||
[Route("appendentries")]
|
||||
public async Task<IActionResult> AppendEntries()
|
||||
{
|
||||
using(var reader = new StreamReader(HttpContext.Request.Body))
|
||||
{
|
||||
var json = await reader.ReadToEndAsync();
|
||||
var appendEntries = JsonConvert.DeserializeObject<AppendEntries>(json, _jsonSerialiserSettings);
|
||||
_logger.LogDebug($"{_baseSchemeUrlAndPort}/appendentries called, my state is {_node.State.GetType().FullName}");
|
||||
var appendEntriesResponse = _node.Handle(appendEntries);
|
||||
return new OkObjectResult(appendEntriesResponse);
|
||||
}
|
||||
}
|
||||
|
||||
[Route("requestvote")]
|
||||
public async Task<IActionResult> RequestVote()
|
||||
{
|
||||
using(var reader = new StreamReader(HttpContext.Request.Body))
|
||||
{
|
||||
var json = await reader.ReadToEndAsync();
|
||||
var requestVote = JsonConvert.DeserializeObject<RequestVote>(json, _jsonSerialiserSettings);
|
||||
_logger.LogDebug($"{_baseSchemeUrlAndPort}/requestvote called, my state is {_node.State.GetType().FullName}");
|
||||
var requestVoteResponse = _node.Handle(requestVote);
|
||||
return new OkObjectResult(requestVoteResponse);
|
||||
}
|
||||
}
|
||||
|
||||
[Route("command")]
|
||||
public async Task<IActionResult> Command()
|
||||
{
|
||||
try
|
||||
{
|
||||
using(var reader = new StreamReader(HttpContext.Request.Body))
|
||||
{
|
||||
var json = await reader.ReadToEndAsync();
|
||||
var command = JsonConvert.DeserializeObject<ICommand>(json, _jsonSerialiserSettings);
|
||||
_logger.LogDebug($"{_baseSchemeUrlAndPort}/command called, my state is {_node.State.GetType().FullName}");
|
||||
var commandResponse = _node.Accept(command);
|
||||
json = JsonConvert.SerializeObject(commandResponse, _jsonSerialiserSettings);
|
||||
return StatusCode(200, json);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
_logger.LogError($"THERE WAS A PROBLEM ON NODE {_node.State.CurrentState.Id}", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
279
src/Ocelot/Raft/SqlLiteLog.cs
Normal file
279
src/Ocelot/Raft/SqlLiteLog.cs
Normal file
@ -0,0 +1,279 @@
|
||||
using System.IO;
|
||||
using Rafty.Log;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using Rafty.Infrastructure;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class SqlLiteLog : ILog
|
||||
{
|
||||
private string _path;
|
||||
private readonly object _lock = new object();
|
||||
|
||||
public SqlLiteLog(NodeId nodeId)
|
||||
{
|
||||
_path = $"{nodeId.Id.Replace("/","").Replace(":","")}.db";
|
||||
if(!File.Exists(_path))
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
FileStream fs = File.Create(_path);
|
||||
fs.Dispose();
|
||||
}
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
var sql = @"create table logs (
|
||||
id integer primary key,
|
||||
data text not null
|
||||
)";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var result = command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int LastLogIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
var result = 1;
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
var sql = @"select id from logs order by id desc limit 1";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var index = Convert.ToInt32(command.ExecuteScalar());
|
||||
if(index > result)
|
||||
{
|
||||
result = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long LastLogTerm
|
||||
{
|
||||
get
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
long result = 0;
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
var sql = @"select data from logs order by id desc limit 1";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var data = Convert.ToString(command.ExecuteScalar());
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
var result = 0;
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
var sql = @"select count(id) from logs";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var index = Convert.ToInt32(command.ExecuteScalar());
|
||||
if(index > result)
|
||||
{
|
||||
result = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Apply(LogEntry log)
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
var jsonSerializerSettings = new JsonSerializerSettings() {
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var data = JsonConvert.SerializeObject(log, jsonSerializerSettings);
|
||||
//todo - sql injection dont copy this..
|
||||
var sql = $"insert into logs (data) values ('{data}')";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var result = command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
sql = "select last_insert_rowid()";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var result = command.ExecuteScalar();
|
||||
return Convert.ToInt32(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteConflictsFromThisLog(int index, LogEntry logEntry)
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
//todo - sql injection dont copy this..
|
||||
var sql = $"select data from logs where id = {index};";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var data = Convert.ToString(command.ExecuteScalar());
|
||||
var jsonSerializerSettings = new JsonSerializerSettings() {
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
if(logEntry != null && log != null && logEntry.Term != log.Term)
|
||||
{
|
||||
//todo - sql injection dont copy this..
|
||||
var deleteSql = $"delete from logs where id >= {index};";
|
||||
using(var deleteCommand = new SqliteCommand(deleteSql, connection))
|
||||
{
|
||||
var result = deleteCommand.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LogEntry Get(int index)
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
//todo - sql injection dont copy this..
|
||||
var sql = $"select data from logs where id = {index}";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var data = Convert.ToString(command.ExecuteScalar());
|
||||
var jsonSerializerSettings = new JsonSerializerSettings() {
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
return log;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public System.Collections.Generic.List<(int index, LogEntry logEntry)> GetFrom(int index)
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
var logsToReturn = new List<(int, LogEntry)>();
|
||||
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
//todo - sql injection dont copy this..
|
||||
var sql = $"select id, data from logs where id >= {index}";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
using(var reader = command.ExecuteReader())
|
||||
{
|
||||
while(reader.Read())
|
||||
{
|
||||
var id = Convert.ToInt32(reader[0]);
|
||||
var data = (string)reader[1];
|
||||
var jsonSerializerSettings = new JsonSerializerSettings() {
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
logsToReturn.Add((id, log));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return logsToReturn;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public long GetTermAtIndex(int index)
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
long result = 0;
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
//todo - sql injection dont copy this..
|
||||
var sql = $"select data from logs where id = {index}";
|
||||
using(var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var data = Convert.ToString(command.ExecuteScalar());
|
||||
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;
|
||||
}
|
||||
}
|
||||
public void Remove(int indexOfCommand)
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
//todo - sql injection dont copy this..
|
||||
var deleteSql = $"delete from logs where id >= {indexOfCommand};";
|
||||
using(var deleteCommand = new SqliteCommand(deleteSql, connection))
|
||||
{
|
||||
var result = deleteCommand.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
src/Ocelot/Raft/UpdateFileConfiguration.cs
Normal file
15
src/Ocelot/Raft/UpdateFileConfiguration.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Ocelot.Configuration.File;
|
||||
using Rafty.FiniteStateMachine;
|
||||
|
||||
namespace Ocelot.Raft
|
||||
{
|
||||
public class UpdateFileConfiguration : ICommand
|
||||
{
|
||||
public UpdateFileConfiguration(FileConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public FileConfiguration Configuration {get;private set;}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user