mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-06-19 14:48:14 +08:00
Feat/monorepo (#734)
* copied everything from repos back to ocelot repo * added src projects to sln * removed all test projects that have no tests * added all test projects to sln * removed test not on master * merged unit tests * merged acceptance tests * merged integration tests * fixed namepaces * build script creates packages for all projects * updated docs to make sure no references to external repos that we will remove * +semver: breaking
This commit is contained in:
14
src/Ocelot.Administration/IIdentityServerConfiguration.cs
Normal file
14
src/Ocelot.Administration/IIdentityServerConfiguration.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Ocelot.Administration
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public interface IIdentityServerConfiguration
|
||||
{
|
||||
string ApiName { get; }
|
||||
string ApiSecret { get; }
|
||||
bool RequireHttps { get; }
|
||||
List<string> AllowedScopes { get; }
|
||||
string CredentialsSigningCertificateLocation { get; }
|
||||
string CredentialsSigningCertificatePassword { get; }
|
||||
}
|
||||
}
|
30
src/Ocelot.Administration/IdentityServerConfiguration.cs
Normal file
30
src/Ocelot.Administration/IdentityServerConfiguration.cs
Normal file
@ -0,0 +1,30 @@
|
||||
namespace Ocelot.Administration
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class IdentityServerConfiguration : IIdentityServerConfiguration
|
||||
{
|
||||
public IdentityServerConfiguration(
|
||||
string apiName,
|
||||
bool requireHttps,
|
||||
string apiSecret,
|
||||
List<string> allowedScopes,
|
||||
string credentialsSigningCertificateLocation,
|
||||
string credentialsSigningCertificatePassword)
|
||||
{
|
||||
ApiName = apiName;
|
||||
RequireHttps = requireHttps;
|
||||
ApiSecret = apiSecret;
|
||||
AllowedScopes = allowedScopes;
|
||||
CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation;
|
||||
CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword;
|
||||
}
|
||||
|
||||
public string ApiName { get; }
|
||||
public bool RequireHttps { get; }
|
||||
public List<string> AllowedScopes { get; }
|
||||
public string ApiSecret { get; }
|
||||
public string CredentialsSigningCertificateLocation { get; }
|
||||
public string CredentialsSigningCertificatePassword { get; }
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ocelot.Administration
|
||||
{
|
||||
public static class IdentityServerConfigurationCreator
|
||||
{
|
||||
public static IdentityServerConfiguration GetIdentityServerConfiguration(string secret)
|
||||
{
|
||||
var credentialsSigningCertificateLocation = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE");
|
||||
var credentialsSigningCertificatePassword = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD");
|
||||
|
||||
return new IdentityServerConfiguration(
|
||||
"admin",
|
||||
false,
|
||||
secret,
|
||||
new List<string> { "admin", "openid", "offline_access" },
|
||||
credentialsSigningCertificateLocation,
|
||||
credentialsSigningCertificatePassword
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
namespace Ocelot.Administration
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using Configuration;
|
||||
using Configuration.Repository;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
public static class IdentityServerMiddlewareConfigurationProvider
|
||||
{
|
||||
public static OcelotMiddlewareConfigurationDelegate Get = builder =>
|
||||
{
|
||||
var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
|
||||
|
||||
var config = internalConfigRepo.Get();
|
||||
|
||||
if (!string.IsNullOrEmpty(config.Data.AdministrationPath))
|
||||
{
|
||||
builder.Map(config.Data.AdministrationPath, app =>
|
||||
{
|
||||
//todo - hack so we know that we are using internal identity server
|
||||
var identityServerConfiguration = builder.ApplicationServices.GetService<IIdentityServerConfiguration>();
|
||||
|
||||
if (identityServerConfiguration != null)
|
||||
{
|
||||
app.UseIdentityServer();
|
||||
}
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseMvc();
|
||||
});
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
}
|
||||
}
|
38
src/Ocelot.Administration/Ocelot.Administration.csproj
Normal file
38
src/Ocelot.Administration/Ocelot.Administration.csproj
Normal file
@ -0,0 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
<Description>Provides Ocelot extensions to use the administration API and IdentityService dependencies that come with it</Description>
|
||||
<AssemblyTitle>Ocelot.Administration</AssemblyTitle>
|
||||
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||
<AssemblyName>Ocelot.Administration</AssemblyName>
|
||||
<PackageId>Ocelot.Administration</PackageId>
|
||||
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Administration</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Administration</PackageProjectUrl>
|
||||
<PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
|
||||
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
<Authors>Tom Pallister</Authors>
|
||||
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />
|
||||
<PackageReference Include="IdentityServer4" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
128
src/Ocelot.Administration/OcelotBuilderExtensions.cs
Normal file
128
src/Ocelot.Administration/OcelotBuilderExtensions.cs
Normal file
@ -0,0 +1,128 @@
|
||||
namespace Ocelot.Administration
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Configuration;
|
||||
using Configuration.Creator;
|
||||
using DependencyInjection;
|
||||
using IdentityModel;
|
||||
using IdentityServer4.AccessTokenValidation;
|
||||
using IdentityServer4.Models;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
public static class OcelotBuilderExtensions
|
||||
{
|
||||
public static IOcelotAdministrationBuilder AddAdministration(this IOcelotBuilder builder, string path, string secret)
|
||||
{
|
||||
var administrationPath = new AdministrationPath(path);
|
||||
builder.Services.AddSingleton<OcelotMiddlewareConfigurationDelegate>(IdentityServerMiddlewareConfigurationProvider.Get);
|
||||
|
||||
//add identity server for admin area
|
||||
var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(secret);
|
||||
|
||||
if (identityServerConfiguration != null)
|
||||
{
|
||||
AddIdentityServer(identityServerConfiguration, administrationPath, builder, builder.Configuration);
|
||||
}
|
||||
|
||||
builder.Services.AddSingleton<IAdministrationPath>(administrationPath);
|
||||
return new OcelotAdministrationBuilder(builder.Services, builder.Configuration);
|
||||
}
|
||||
|
||||
public static IOcelotAdministrationBuilder AddAdministration(this IOcelotBuilder builder, string path, Action<IdentityServerAuthenticationOptions> configureOptions)
|
||||
{
|
||||
var administrationPath = new AdministrationPath(path);
|
||||
builder.Services.AddSingleton<OcelotMiddlewareConfigurationDelegate>(IdentityServerMiddlewareConfigurationProvider.Get);
|
||||
|
||||
if (configureOptions != null)
|
||||
{
|
||||
AddIdentityServer(configureOptions, builder);
|
||||
}
|
||||
|
||||
builder.Services.AddSingleton<IAdministrationPath>(administrationPath);
|
||||
return new OcelotAdministrationBuilder(builder.Services, builder.Configuration);
|
||||
}
|
||||
|
||||
private static void AddIdentityServer(Action<IdentityServerAuthenticationOptions> configOptions, IOcelotBuilder builder)
|
||||
{
|
||||
builder.Services
|
||||
.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddIdentityServerAuthentication(configOptions);
|
||||
}
|
||||
|
||||
private static void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath, IOcelotBuilder builder, IConfiguration configuration)
|
||||
{
|
||||
builder.Services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
|
||||
var identityServerBuilder = builder.Services
|
||||
.AddIdentityServer(o => {
|
||||
o.IssuerUri = "Ocelot";
|
||||
})
|
||||
.AddInMemoryApiResources(Resources(identityServerConfiguration))
|
||||
.AddInMemoryClients(Client(identityServerConfiguration));
|
||||
|
||||
var urlFinder = new BaseUrlFinder(configuration);
|
||||
var baseSchemeUrlAndPort = urlFinder.Find();
|
||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
||||
|
||||
builder.Services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddIdentityServerAuthentication(o =>
|
||||
{
|
||||
o.Authority = baseSchemeUrlAndPort + adminPath.Path;
|
||||
o.ApiName = identityServerConfiguration.ApiName;
|
||||
o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps;
|
||||
o.SupportedTokens = SupportedTokens.Both;
|
||||
o.ApiSecret = identityServerConfiguration.ApiSecret;
|
||||
});
|
||||
|
||||
//todo - refactor naming..
|
||||
if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword))
|
||||
{
|
||||
identityServerBuilder.AddDeveloperSigningCredential();
|
||||
}
|
||||
else
|
||||
{
|
||||
//todo - refactor so calls method?
|
||||
var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword);
|
||||
identityServerBuilder.AddSigningCredential(cert);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ApiResource> Resources(IIdentityServerConfiguration identityServerConfiguration)
|
||||
{
|
||||
return new List<ApiResource>
|
||||
{
|
||||
new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName)
|
||||
{
|
||||
ApiSecrets = new List<Secret>
|
||||
{
|
||||
new Secret
|
||||
{
|
||||
Value = identityServerConfiguration.ApiSecret.Sha256()
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static List<Client> Client(IIdentityServerConfiguration identityServerConfiguration)
|
||||
{
|
||||
return new List<Client>
|
||||
{
|
||||
new Client
|
||||
{
|
||||
ClientId = identityServerConfiguration.ApiName,
|
||||
AllowedGrantTypes = GrantTypes.ClientCredentials,
|
||||
ClientSecrets = new List<Secret> {new Secret(identityServerConfiguration.ApiSecret.Sha256())},
|
||||
AllowedScopes = { identityServerConfiguration.ApiName }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
18
src/Ocelot.Administration/Properties/AssemblyInfo.cs
Normal file
18
src/Ocelot.Administration/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Ocelot")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
|
@ -0,0 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
<Description>Provides Ocelot extensions to use CacheManager.Net</Description>
|
||||
<AssemblyTitle>Ocelot.Cache.CacheManager</AssemblyTitle>
|
||||
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||
<AssemblyName>Ocelot.Cache.CacheManager</AssemblyName>
|
||||
<PackageId>Ocelot.Cache.CacheManager</PackageId>
|
||||
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Cache.CacheManager</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Cache.CacheManager</PackageProjectUrl>
|
||||
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
<Authors>Tom Pallister</Authors>
|
||||
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CacheManager.Core" Version="1.1.2" />
|
||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.2" />
|
||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
39
src/Ocelot.Cache.CacheManager/OcelotBuilderExtensions.cs
Normal file
39
src/Ocelot.Cache.CacheManager/OcelotBuilderExtensions.cs
Normal file
@ -0,0 +1,39 @@
|
||||
namespace Ocelot.Cache.CacheManager
|
||||
{
|
||||
using System;
|
||||
using Configuration;
|
||||
using Configuration.File;
|
||||
using DependencyInjection;
|
||||
using global::CacheManager.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
public static class OcelotBuilderExtensions
|
||||
{
|
||||
public static IOcelotBuilder AddCacheManager(this IOcelotBuilder builder, Action<ConfigurationBuilderCachePart> settings)
|
||||
{
|
||||
var cacheManagerOutputCache = CacheFactory.Build<CachedResponse>("OcelotOutputCache", settings);
|
||||
var ocelotOutputCacheManager = new OcelotCacheManagerCache<CachedResponse>(cacheManagerOutputCache);
|
||||
|
||||
builder.Services.RemoveAll(typeof(ICacheManager<CachedResponse>));
|
||||
builder.Services.RemoveAll(typeof(IOcelotCache<CachedResponse>));
|
||||
builder.Services.AddSingleton<ICacheManager<CachedResponse>>(cacheManagerOutputCache);
|
||||
builder.Services.AddSingleton<IOcelotCache<CachedResponse>>(ocelotOutputCacheManager);
|
||||
|
||||
var ocelotConfigCacheManagerOutputCache = CacheFactory.Build<IInternalConfiguration>("OcelotConfigurationCache", settings);
|
||||
var ocelotConfigCacheManager = new OcelotCacheManagerCache<IInternalConfiguration>(ocelotConfigCacheManagerOutputCache);
|
||||
builder.Services.RemoveAll(typeof(ICacheManager<IInternalConfiguration>));
|
||||
builder.Services.RemoveAll(typeof(IOcelotCache<IInternalConfiguration>));
|
||||
builder.Services.AddSingleton<ICacheManager<IInternalConfiguration>>(ocelotConfigCacheManagerOutputCache);
|
||||
builder.Services.AddSingleton<IOcelotCache<IInternalConfiguration>>(ocelotConfigCacheManager);
|
||||
|
||||
var fileConfigCacheManagerOutputCache = CacheFactory.Build<FileConfiguration>("FileConfigurationCache", settings);
|
||||
var fileConfigCacheManager = new OcelotCacheManagerCache<FileConfiguration>(fileConfigCacheManagerOutputCache);
|
||||
builder.Services.RemoveAll(typeof(ICacheManager<FileConfiguration>));
|
||||
builder.Services.RemoveAll(typeof(IOcelotCache<FileConfiguration>));
|
||||
builder.Services.AddSingleton<ICacheManager<FileConfiguration>>(fileConfigCacheManagerOutputCache);
|
||||
builder.Services.AddSingleton<IOcelotCache<FileConfiguration>>(fileConfigCacheManager);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
42
src/Ocelot.Cache.CacheManager/OcelotCacheManagerCache.cs
Normal file
42
src/Ocelot.Cache.CacheManager/OcelotCacheManagerCache.cs
Normal file
@ -0,0 +1,42 @@
|
||||
namespace Ocelot.Cache.CacheManager
|
||||
{
|
||||
using System;
|
||||
using global::CacheManager.Core;
|
||||
|
||||
public class OcelotCacheManagerCache<T> : IOcelotCache<T>
|
||||
{
|
||||
private readonly ICacheManager<T> _cacheManager;
|
||||
|
||||
public OcelotCacheManagerCache(ICacheManager<T> cacheManager)
|
||||
{
|
||||
_cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
public void Add(string key, T value, TimeSpan ttl, string region)
|
||||
{
|
||||
_cacheManager.Add(new CacheItem<T>(key, region, value, ExpirationMode.Absolute, ttl));
|
||||
}
|
||||
|
||||
public void AddAndDelete(string key, T value, TimeSpan ttl, string region)
|
||||
{
|
||||
var exists = _cacheManager.Get(key);
|
||||
|
||||
if (exists != null)
|
||||
{
|
||||
_cacheManager.Remove(key);
|
||||
}
|
||||
|
||||
Add(key, value, ttl, region);
|
||||
}
|
||||
|
||||
public T Get(string key, string region)
|
||||
{
|
||||
return _cacheManager.Get<T>(key, region);
|
||||
}
|
||||
|
||||
public void ClearRegion(string region)
|
||||
{
|
||||
_cacheManager.ClearRegion(region);
|
||||
}
|
||||
}
|
||||
}
|
18
src/Ocelot.Cache.CacheManager/Properties/AssemblyInfo.cs
Normal file
18
src/Ocelot.Cache.CacheManager/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Ocelot")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
|
76
src/Ocelot.Provider.Consul/Consul.cs
Normal file
76
src/Ocelot.Provider.Consul/Consul.cs
Normal file
@ -0,0 +1,76 @@
|
||||
namespace Ocelot.Provider.Consul
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using global::Consul;
|
||||
using Infrastructure.Extensions;
|
||||
using Logging;
|
||||
using ServiceDiscovery.Providers;
|
||||
using Values;
|
||||
|
||||
|
||||
public class Consul : IServiceDiscoveryProvider
|
||||
{
|
||||
private readonly ConsulRegistryConfiguration _config;
|
||||
private readonly IOcelotLogger _logger;
|
||||
private readonly IConsulClient _consul;
|
||||
private const string VersionPrefix = "version-";
|
||||
|
||||
public Consul(ConsulRegistryConfiguration config, IOcelotLoggerFactory factory, IConsulClientFactory clientFactory)
|
||||
{
|
||||
_logger = factory.CreateLogger<Consul>();
|
||||
_config = config;
|
||||
_consul = clientFactory.Get(_config);
|
||||
}
|
||||
|
||||
public async Task<List<Service>> Get()
|
||||
{
|
||||
var queryResult = await _consul.Health.Service(_config.KeyOfServiceInConsul, string.Empty, true);
|
||||
|
||||
var services = new List<Service>();
|
||||
|
||||
foreach (var serviceEntry in queryResult.Response)
|
||||
{
|
||||
if (IsValid(serviceEntry))
|
||||
{
|
||||
services.Add(BuildService(serviceEntry));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning($"Unable to use service Address: {serviceEntry.Service.Address} and Port: {serviceEntry.Service.Port} as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0");
|
||||
}
|
||||
}
|
||||
|
||||
return services.ToList();
|
||||
}
|
||||
|
||||
private Service BuildService(ServiceEntry serviceEntry)
|
||||
{
|
||||
return new Service(
|
||||
serviceEntry.Service.Service,
|
||||
new ServiceHostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port),
|
||||
serviceEntry.Service.ID,
|
||||
GetVersionFromStrings(serviceEntry.Service.Tags),
|
||||
serviceEntry.Service.Tags ?? Enumerable.Empty<string>());
|
||||
}
|
||||
|
||||
private bool IsValid(ServiceEntry serviceEntry)
|
||||
{
|
||||
if (string.IsNullOrEmpty(serviceEntry.Service.Address) || serviceEntry.Service.Address.Contains("http://") || serviceEntry.Service.Address.Contains("https://") || serviceEntry.Service.Port <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GetVersionFromStrings(IEnumerable<string> strings)
|
||||
{
|
||||
return strings
|
||||
?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal))
|
||||
.TrimStart(VersionPrefix);
|
||||
}
|
||||
}
|
||||
}
|
21
src/Ocelot.Provider.Consul/ConsulClientFactory.cs
Normal file
21
src/Ocelot.Provider.Consul/ConsulClientFactory.cs
Normal file
@ -0,0 +1,21 @@
|
||||
namespace Ocelot.Provider.Consul
|
||||
{
|
||||
using System;
|
||||
using global::Consul;
|
||||
|
||||
public class ConsulClientFactory : IConsulClientFactory
|
||||
{
|
||||
public IConsulClient Get(ConsulRegistryConfiguration config)
|
||||
{
|
||||
return new ConsulClient(c =>
|
||||
{
|
||||
c.Address = new Uri($"http://{config.Host}:{config.Port}");
|
||||
|
||||
if (!string.IsNullOrEmpty(config?.Token))
|
||||
{
|
||||
c.Token = config.Token;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
namespace Ocelot.Provider.Consul
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Configuration.File;
|
||||
using Configuration.Repository;
|
||||
using global::Consul;
|
||||
using Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Responses;
|
||||
|
||||
public class ConsulFileConfigurationRepository : IFileConfigurationRepository
|
||||
{
|
||||
private readonly IConsulClient _consul;
|
||||
private readonly string _configurationKey;
|
||||
private readonly Cache.IOcelotCache<FileConfiguration> _cache;
|
||||
private readonly IOcelotLogger _logger;
|
||||
|
||||
public ConsulFileConfigurationRepository(
|
||||
Cache.IOcelotCache<FileConfiguration> cache,
|
||||
IInternalConfigurationRepository repo,
|
||||
IConsulClientFactory factory,
|
||||
IOcelotLoggerFactory loggerFactory)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger<ConsulFileConfigurationRepository>();
|
||||
_cache = cache;
|
||||
|
||||
var internalConfig = repo.Get();
|
||||
|
||||
_configurationKey = "InternalConfiguration";
|
||||
|
||||
string token = null;
|
||||
|
||||
if (!internalConfig.IsError)
|
||||
{
|
||||
token = internalConfig.Data.ServiceProviderConfiguration.Token;
|
||||
_configurationKey = !string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey) ?
|
||||
internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey : _configurationKey;
|
||||
}
|
||||
|
||||
var config = new ConsulRegistryConfiguration(internalConfig.Data.ServiceProviderConfiguration.Host,
|
||||
internalConfig.Data.ServiceProviderConfiguration.Port, _configurationKey, token);
|
||||
|
||||
_consul = factory.Get(config);
|
||||
}
|
||||
|
||||
public async Task<Response<FileConfiguration>> Get()
|
||||
{
|
||||
var config = _cache.Get(_configurationKey, _configurationKey);
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
return new OkResponse<FileConfiguration>(config);
|
||||
}
|
||||
|
||||
var queryResult = await _consul.KV.Get(_configurationKey);
|
||||
|
||||
if (queryResult.Response == null)
|
||||
{
|
||||
return new OkResponse<FileConfiguration>(null);
|
||||
}
|
||||
|
||||
var bytes = queryResult.Response.Value;
|
||||
|
||||
var json = Encoding.UTF8.GetString(bytes);
|
||||
|
||||
var consulConfig = JsonConvert.DeserializeObject<FileConfiguration>(json);
|
||||
|
||||
return new OkResponse<FileConfiguration>(consulConfig);
|
||||
}
|
||||
|
||||
public async Task<Response> Set(FileConfiguration ocelotConfiguration)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
var kvPair = new KVPair(_configurationKey)
|
||||
{
|
||||
Value = bytes
|
||||
};
|
||||
|
||||
var result = await _consul.KV.Put(kvPair);
|
||||
|
||||
if (result.Response)
|
||||
{
|
||||
_cache.AddAndDelete(_configurationKey, ocelotConfiguration, TimeSpan.FromSeconds(3), _configurationKey);
|
||||
|
||||
return new OkResponse();
|
||||
}
|
||||
|
||||
return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}"));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
namespace Ocelot.Provider.Consul
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Configuration.Creator;
|
||||
using Configuration.File;
|
||||
using Configuration.Repository;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Middleware;
|
||||
using Responses;
|
||||
|
||||
public static class ConsulMiddlewareConfigurationProvider
|
||||
{
|
||||
public static OcelotMiddlewareConfigurationDelegate Get = async builder =>
|
||||
{
|
||||
var fileConfigRepo = builder.ApplicationServices.GetService<IFileConfigurationRepository>();
|
||||
var fileConfig = builder.ApplicationServices.GetService<IOptionsMonitor<FileConfiguration>>();
|
||||
var internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>();
|
||||
var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
|
||||
|
||||
if (UsingConsul(fileConfigRepo))
|
||||
{
|
||||
await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo);
|
||||
}
|
||||
};
|
||||
|
||||
private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo)
|
||||
{
|
||||
return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository);
|
||||
}
|
||||
|
||||
private static async Task SetFileConfigInConsul(IApplicationBuilder builder,
|
||||
IFileConfigurationRepository fileConfigRepo, IOptionsMonitor<FileConfiguration> fileConfig,
|
||||
IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo)
|
||||
{
|
||||
// get the config from consul.
|
||||
var fileConfigFromConsul = await fileConfigRepo.Get();
|
||||
|
||||
if (IsError(fileConfigFromConsul))
|
||||
{
|
||||
ThrowToStopOcelotStarting(fileConfigFromConsul);
|
||||
}
|
||||
else if (ConfigNotStoredInConsul(fileConfigFromConsul))
|
||||
{
|
||||
//there was no config in consul set the file in config in consul
|
||||
await fileConfigRepo.Set(fileConfig.CurrentValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// create the internal config from consul data
|
||||
var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data);
|
||||
|
||||
if (IsError(internalConfig))
|
||||
{
|
||||
ThrowToStopOcelotStarting(internalConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
// add the internal config to the internal repo
|
||||
var response = internalConfigRepo.AddOrReplace(internalConfig.Data);
|
||||
|
||||
if (IsError(response))
|
||||
{
|
||||
ThrowToStopOcelotStarting(response);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsError(internalConfig))
|
||||
{
|
||||
ThrowToStopOcelotStarting(internalConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ThrowToStopOcelotStarting(Response config)
|
||||
{
|
||||
throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}");
|
||||
}
|
||||
|
||||
private static bool IsError(Response response)
|
||||
{
|
||||
return response == null || response.IsError;
|
||||
}
|
||||
|
||||
private static bool ConfigNotStoredInConsul(Response<FileConfiguration> fileConfigFromConsul)
|
||||
{
|
||||
return fileConfigFromConsul.Data == null;
|
||||
}
|
||||
}
|
||||
}
|
29
src/Ocelot.Provider.Consul/ConsulProviderFactory.cs
Normal file
29
src/Ocelot.Provider.Consul/ConsulProviderFactory.cs
Normal file
@ -0,0 +1,29 @@
|
||||
namespace Ocelot.Provider.Consul
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using Logging;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ServiceDiscovery;
|
||||
|
||||
public static class ConsulProviderFactory
|
||||
{
|
||||
public static ServiceDiscoveryFinderDelegate Get = (provider, config, name) =>
|
||||
{
|
||||
var factory = provider.GetService<IOcelotLoggerFactory>();
|
||||
|
||||
var consulFactory = provider.GetService<IConsulClientFactory>();
|
||||
|
||||
var consulRegistryConfiguration = new ConsulRegistryConfiguration(config.Host, config.Port, name, config.Token);
|
||||
|
||||
var consulServiceDiscoveryProvider = new Consul(consulRegistryConfiguration, factory, consulFactory);
|
||||
|
||||
if (config.Type?.ToLower() == "pollconsul")
|
||||
{
|
||||
return new PollConsul(config.PollingInterval, factory, consulServiceDiscoveryProvider);
|
||||
}
|
||||
|
||||
return consulServiceDiscoveryProvider;
|
||||
};
|
||||
}
|
||||
}
|
18
src/Ocelot.Provider.Consul/ConsulRegistryConfiguration.cs
Normal file
18
src/Ocelot.Provider.Consul/ConsulRegistryConfiguration.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Ocelot.Provider.Consul
|
||||
{
|
||||
public class ConsulRegistryConfiguration
|
||||
{
|
||||
public ConsulRegistryConfiguration(string host, int port, string keyOfServiceInConsul, string token)
|
||||
{
|
||||
Host = string.IsNullOrEmpty(host) ? "localhost" : host;
|
||||
Port = port > 0 ? port : 8500;
|
||||
KeyOfServiceInConsul = keyOfServiceInConsul;
|
||||
Token = token;
|
||||
}
|
||||
|
||||
public string KeyOfServiceInConsul { get; }
|
||||
public string Host { get; }
|
||||
public int Port { get; }
|
||||
public string Token { get; }
|
||||
}
|
||||
}
|
9
src/Ocelot.Provider.Consul/IConsulClientFactory.cs
Normal file
9
src/Ocelot.Provider.Consul/IConsulClientFactory.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Ocelot.Provider.Consul
|
||||
{
|
||||
using global::Consul;
|
||||
|
||||
public interface IConsulClientFactory
|
||||
{
|
||||
IConsulClient Get(ConsulRegistryConfiguration config);
|
||||
}
|
||||
}
|
37
src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj
Normal file
37
src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj
Normal file
@ -0,0 +1,37 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
<Description>Provides Ocelot extensions to use Consul</Description>
|
||||
<AssemblyTitle>Ocelot.Provider.Consul</AssemblyTitle>
|
||||
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||
<AssemblyName>Ocelot.Provider.Consul</AssemblyName>
|
||||
<PackageId>Ocelot.Provider.Consul</PackageId>
|
||||
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Consul</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Consul</PackageProjectUrl>
|
||||
<PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
|
||||
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
<Authors>Tom Pallister</Authors>
|
||||
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Consul" Version="0.7.2.6" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
26
src/Ocelot.Provider.Consul/OcelotBuilderExtensions.cs
Normal file
26
src/Ocelot.Provider.Consul/OcelotBuilderExtensions.cs
Normal file
@ -0,0 +1,26 @@
|
||||
namespace Ocelot.Provider.Consul
|
||||
{
|
||||
using Configuration.Repository;
|
||||
using DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Middleware;
|
||||
using ServiceDiscovery;
|
||||
|
||||
public static class OcelotBuilderExtensions
|
||||
{
|
||||
public static IOcelotBuilder AddConsul(this IOcelotBuilder builder)
|
||||
{
|
||||
builder.Services.AddSingleton<ServiceDiscoveryFinderDelegate>(ConsulProviderFactory.Get);
|
||||
builder.Services.AddSingleton<IConsulClientFactory, ConsulClientFactory>();
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IOcelotBuilder AddConfigStoredInConsul(this IOcelotBuilder builder)
|
||||
{
|
||||
builder.Services.AddSingleton<OcelotMiddlewareConfigurationDelegate>(ConsulMiddlewareConfigurationProvider.Get);
|
||||
builder.Services.AddHostedService<FileConfigurationPoller>();
|
||||
builder.Services.AddSingleton<IFileConfigurationRepository, ConsulFileConfigurationRepository>();
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
namespace Ocelot.Provider.Consul
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Logging;
|
||||
using ServiceDiscovery.Providers;
|
||||
using Values;
|
||||
|
||||
public class PollConsul : IServiceDiscoveryProvider
|
||||
{
|
||||
private readonly IOcelotLogger _logger;
|
||||
private readonly IServiceDiscoveryProvider _consulServiceDiscoveryProvider;
|
||||
private readonly Timer _timer;
|
||||
private bool _polling;
|
||||
private List<Service> _services;
|
||||
|
||||
public PollConsul(int pollingInterval, IOcelotLoggerFactory factory, IServiceDiscoveryProvider consulServiceDiscoveryProvider)
|
||||
{
|
||||
_logger = factory.CreateLogger<PollConsul>();
|
||||
_consulServiceDiscoveryProvider = consulServiceDiscoveryProvider;
|
||||
_services = new List<Service>();
|
||||
|
||||
_timer = new Timer(async x =>
|
||||
{
|
||||
if (_polling)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_polling = true;
|
||||
await Poll();
|
||||
_polling = false;
|
||||
}, null, pollingInterval, pollingInterval);
|
||||
}
|
||||
|
||||
public Task<List<Service>> Get()
|
||||
{
|
||||
return Task.FromResult(_services);
|
||||
}
|
||||
|
||||
private async Task Poll()
|
||||
{
|
||||
_services = await _consulServiceDiscoveryProvider.Get();
|
||||
}
|
||||
}
|
||||
}
|
18
src/Ocelot.Provider.Consul/Properties/AssemblyInfo.cs
Normal file
18
src/Ocelot.Provider.Consul/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Ocelot")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
|
12
src/Ocelot.Provider.Consul/UnableToSetConfigInConsulError.cs
Normal file
12
src/Ocelot.Provider.Consul/UnableToSetConfigInConsulError.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Ocelot.Provider.Consul
|
||||
{
|
||||
using Errors;
|
||||
|
||||
public class UnableToSetConfigInConsulError : Error
|
||||
{
|
||||
public UnableToSetConfigInConsulError(string s)
|
||||
: base(s, OcelotErrorCode.UnknownError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
35
src/Ocelot.Provider.Eureka/Eureka.cs
Normal file
35
src/Ocelot.Provider.Eureka/Eureka.cs
Normal file
@ -0,0 +1,35 @@
|
||||
namespace Ocelot.Provider.Eureka
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ServiceDiscovery.Providers;
|
||||
using Steeltoe.Common.Discovery;
|
||||
using Values;
|
||||
|
||||
public class Eureka : IServiceDiscoveryProvider
|
||||
{
|
||||
private readonly IDiscoveryClient _client;
|
||||
private readonly string _serviceName;
|
||||
|
||||
public Eureka(string serviceName, IDiscoveryClient client)
|
||||
{
|
||||
_client = client;
|
||||
_serviceName = serviceName;
|
||||
}
|
||||
|
||||
public Task<List<Service>> Get()
|
||||
{
|
||||
var services = new List<Service>();
|
||||
|
||||
var instances = _client.GetInstances(_serviceName);
|
||||
|
||||
if (instances != null && instances.Any())
|
||||
{
|
||||
services.AddRange(instances.Select(i => new Service(i.ServiceId, new ServiceHostAndPort(i.Host, i.Port), "", "", new List<string>())));
|
||||
}
|
||||
|
||||
return Task.FromResult(services);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
namespace Ocelot.Provider.Eureka
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using Configuration;
|
||||
using Configuration.Repository;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Middleware;
|
||||
using Pivotal.Discovery.Client;
|
||||
|
||||
public class EurekaMiddlewareConfigurationProvider
|
||||
{
|
||||
public static OcelotMiddlewareConfigurationDelegate Get = builder =>
|
||||
{
|
||||
var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
|
||||
|
||||
var config = internalConfigRepo.Get();
|
||||
|
||||
if (UsingEurekaServiceDiscoveryProvider(config.Data))
|
||||
{
|
||||
builder.UseDiscoveryClient();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
private static bool UsingEurekaServiceDiscoveryProvider(IInternalConfiguration configuration)
|
||||
{
|
||||
return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "eureka";
|
||||
}
|
||||
}
|
||||
}
|
22
src/Ocelot.Provider.Eureka/EurekaProviderFactory.cs
Normal file
22
src/Ocelot.Provider.Eureka/EurekaProviderFactory.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace Ocelot.Provider.Eureka
|
||||
{
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ServiceDiscovery;
|
||||
using ServiceDiscovery.Providers;
|
||||
using Steeltoe.Common.Discovery;
|
||||
|
||||
public static class EurekaProviderFactory
|
||||
{
|
||||
public static ServiceDiscoveryFinderDelegate Get = (provider, config, name) =>
|
||||
{
|
||||
var client = provider.GetService<IDiscoveryClient>();
|
||||
|
||||
if (config.Type?.ToLower() == "eureka" && client != null)
|
||||
{
|
||||
return new Eureka(name, client);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
37
src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj
Normal file
37
src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj
Normal file
@ -0,0 +1,37 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
<Description>Provides Ocelot extensions to use Eureka</Description>
|
||||
<AssemblyTitle>Ocelot.Provider.Eureka</AssemblyTitle>
|
||||
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||
<AssemblyName>Ocelot.Provider.Eureka</AssemblyName>
|
||||
<PackageId>Ocelot.Provider.Eureka</PackageId>
|
||||
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Eureka</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Eureka</PackageProjectUrl>
|
||||
<PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
|
||||
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
<Authors>Tom Pallister</Authors>
|
||||
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Pivotal.Discovery.ClientCore" Version="2.0.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
23
src/Ocelot.Provider.Eureka/OcelotBuilderExtensions.cs
Normal file
23
src/Ocelot.Provider.Eureka/OcelotBuilderExtensions.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace Ocelot.Provider.Eureka
|
||||
{
|
||||
using System.Linq;
|
||||
using DependencyInjection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Middleware;
|
||||
using Pivotal.Discovery.Client;
|
||||
using ServiceDiscovery;
|
||||
|
||||
public static class OcelotBuilderExtensions
|
||||
{
|
||||
public static IOcelotBuilder AddEureka(this IOcelotBuilder builder)
|
||||
{
|
||||
var service = builder.Services.First(x => x.ServiceType == typeof(IConfiguration));
|
||||
var configuration = (IConfiguration)service.ImplementationInstance;
|
||||
builder.Services.AddDiscoveryClient(configuration);
|
||||
builder.Services.AddSingleton<ServiceDiscoveryFinderDelegate>(EurekaProviderFactory.Get);
|
||||
builder.Services.AddSingleton<OcelotMiddlewareConfigurationDelegate>(EurekaMiddlewareConfigurationProvider.Get);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
18
src/Ocelot.Provider.Eureka/Properties/AssemblyInfo.cs
Normal file
18
src/Ocelot.Provider.Eureka/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Ocelot")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
|
17
src/Ocelot.Provider.Polly/CircuitBreaker.cs
Normal file
17
src/Ocelot.Provider.Polly/CircuitBreaker.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Polly.CircuitBreaker;
|
||||
using Polly.Timeout;
|
||||
|
||||
namespace Ocelot.Provider.Polly
|
||||
{
|
||||
public class CircuitBreaker
|
||||
{
|
||||
public CircuitBreaker(CircuitBreakerPolicy circuitBreakerPolicy, TimeoutPolicy timeoutPolicy)
|
||||
{
|
||||
CircuitBreakerPolicy = circuitBreakerPolicy;
|
||||
TimeoutPolicy = timeoutPolicy;
|
||||
}
|
||||
|
||||
public CircuitBreakerPolicy CircuitBreakerPolicy { get; private set; }
|
||||
public TimeoutPolicy TimeoutPolicy { get; private set; }
|
||||
}
|
||||
}
|
37
src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj
Normal file
37
src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj
Normal file
@ -0,0 +1,37 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
<Description>Provides Ocelot extensions to use Polly.NET</Description>
|
||||
<AssemblyTitle>Ocelot.Provider.Polly</AssemblyTitle>
|
||||
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||
<AssemblyName>Ocelot.Provider.Polly</AssemblyName>
|
||||
<PackageId>Ocelot.Provider.Polly</PackageId>
|
||||
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Polly</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Polly</PackageProjectUrl>
|
||||
<PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
|
||||
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
<Authors>Tom Pallister</Authors>
|
||||
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Polly" Version="6.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
38
src/Ocelot.Provider.Polly/OcelotBuilderExtensions.cs
Normal file
38
src/Ocelot.Provider.Polly/OcelotBuilderExtensions.cs
Normal file
@ -0,0 +1,38 @@
|
||||
namespace Ocelot.Provider.Polly
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Configuration;
|
||||
using DependencyInjection;
|
||||
using Errors;
|
||||
using global::Polly.CircuitBreaker;
|
||||
using global::Polly.Timeout;
|
||||
using Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Requester;
|
||||
|
||||
public static class OcelotBuilderExtensions
|
||||
{
|
||||
public static IOcelotBuilder AddPolly(this IOcelotBuilder builder)
|
||||
{
|
||||
var errorMapping = new Dictionary<Type, Func<Exception, Error>>
|
||||
{
|
||||
{typeof(TaskCanceledException), e => new RequestTimedOutError(e)},
|
||||
{typeof(TimeoutRejectedException), e => new RequestTimedOutError(e)},
|
||||
{typeof(BrokenCircuitException), e => new RequestTimedOutError(e)}
|
||||
};
|
||||
|
||||
builder.Services.AddSingleton(errorMapping);
|
||||
|
||||
DelegatingHandler QosDelegatingHandlerDelegate(DownstreamReRoute reRoute, IOcelotLoggerFactory logger)
|
||||
{
|
||||
return new PollyCircuitBreakingDelegatingHandler(new PollyQoSProvider(reRoute, logger), logger);
|
||||
}
|
||||
|
||||
builder.Services.AddSingleton((QosDelegatingHandlerDelegate) QosDelegatingHandlerDelegate);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Logging;
|
||||
using Polly;
|
||||
using Polly.CircuitBreaker;
|
||||
|
||||
namespace Ocelot.Provider.Polly
|
||||
{
|
||||
public class PollyCircuitBreakingDelegatingHandler : DelegatingHandler
|
||||
{
|
||||
private readonly PollyQoSProvider _qoSProvider;
|
||||
private readonly IOcelotLogger _logger;
|
||||
|
||||
public PollyCircuitBreakingDelegatingHandler(
|
||||
PollyQoSProvider qoSProvider,
|
||||
IOcelotLoggerFactory loggerFactory)
|
||||
{
|
||||
_qoSProvider = qoSProvider;
|
||||
_logger = loggerFactory.CreateLogger<PollyCircuitBreakingDelegatingHandler>();
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Policy
|
||||
.WrapAsync(_qoSProvider.CircuitBreaker.CircuitBreakerPolicy, _qoSProvider.CircuitBreaker.TimeoutPolicy)
|
||||
.ExecuteAsync(() => base.SendAsync(request,cancellationToken));
|
||||
}
|
||||
catch (BrokenCircuitException ex)
|
||||
{
|
||||
_logger.LogError($"Reached to allowed number of exceptions. Circuit is open",ex);
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAync", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
src/Ocelot.Provider.Polly/PollyQoSProvider.cs
Normal file
52
src/Ocelot.Provider.Polly/PollyQoSProvider.cs
Normal file
@ -0,0 +1,52 @@
|
||||
namespace Ocelot.Provider.Polly
|
||||
{
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using global::Polly;
|
||||
using global::Polly.CircuitBreaker;
|
||||
using global::Polly.Timeout;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Logging;
|
||||
|
||||
public class PollyQoSProvider
|
||||
{
|
||||
private readonly CircuitBreakerPolicy _circuitBreakerPolicy;
|
||||
private readonly TimeoutPolicy _timeoutPolicy;
|
||||
private readonly IOcelotLogger _logger;
|
||||
|
||||
public PollyQoSProvider(DownstreamReRoute reRoute, IOcelotLoggerFactory loggerFactory)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger<PollyQoSProvider>();
|
||||
|
||||
Enum.TryParse(reRoute.QosOptions.TimeoutStrategy, out TimeoutStrategy strategy);
|
||||
|
||||
_timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(reRoute.QosOptions.TimeoutValue), strategy);
|
||||
|
||||
_circuitBreakerPolicy = Policy
|
||||
.Handle<HttpRequestException>()
|
||||
.Or<TimeoutRejectedException>()
|
||||
.Or<TimeoutException>()
|
||||
.CircuitBreakerAsync(
|
||||
exceptionsAllowedBeforeBreaking: reRoute.QosOptions.ExceptionsAllowedBeforeBreaking,
|
||||
durationOfBreak: TimeSpan.FromMilliseconds(reRoute.QosOptions.DurationOfBreak),
|
||||
onBreak: (ex, breakDelay) =>
|
||||
{
|
||||
_logger.LogError(
|
||||
".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex);
|
||||
},
|
||||
onReset: () =>
|
||||
{
|
||||
_logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again.");
|
||||
},
|
||||
onHalfOpen: () =>
|
||||
{
|
||||
_logger.LogDebug(".Breaker logging: Half-open; next call is a trial.");
|
||||
}
|
||||
);
|
||||
|
||||
CircuitBreaker = new CircuitBreaker(_circuitBreakerPolicy, _timeoutPolicy);
|
||||
}
|
||||
|
||||
public CircuitBreaker CircuitBreaker { get; }
|
||||
}
|
||||
}
|
18
src/Ocelot.Provider.Polly/Properties/AssemblyInfo.cs
Normal file
18
src/Ocelot.Provider.Polly/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Ocelot")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
|
13
src/Ocelot.Provider.Polly/RequestTimedOutError.cs
Normal file
13
src/Ocelot.Provider.Polly/RequestTimedOutError.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Ocelot.Provider.Polly
|
||||
{
|
||||
using System;
|
||||
using Errors;
|
||||
|
||||
public class RequestTimedOutError : Error
|
||||
{
|
||||
public RequestTimedOutError(Exception exception)
|
||||
: base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
16
src/Ocelot.Provider.Rafty/BearerToken.cs
Normal file
16
src/Ocelot.Provider.Rafty/BearerToken.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Ocelot.Provider.Rafty
|
||||
{
|
||||
using Newtonsoft.Json;
|
||||
|
||||
internal 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; }
|
||||
}
|
||||
}
|
14
src/Ocelot.Provider.Rafty/FakeCommand.cs
Normal file
14
src/Ocelot.Provider.Rafty/FakeCommand.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Ocelot.Provider.Rafty
|
||||
{
|
||||
using global::Rafty.FiniteStateMachine;
|
||||
|
||||
public class FakeCommand : ICommand
|
||||
{
|
||||
public FakeCommand(string value)
|
||||
{
|
||||
this.Value = value;
|
||||
}
|
||||
|
||||
public string Value { get; private set; }
|
||||
}
|
||||
}
|
7
src/Ocelot.Provider.Rafty/FilePeer.cs
Normal file
7
src/Ocelot.Provider.Rafty/FilePeer.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Ocelot.Provider.Rafty
|
||||
{
|
||||
public class FilePeer
|
||||
{
|
||||
public string HostAndPort { get; set; }
|
||||
}
|
||||
}
|
14
src/Ocelot.Provider.Rafty/FilePeers.cs
Normal file
14
src/Ocelot.Provider.Rafty/FilePeers.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Ocelot.Provider.Rafty
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class FilePeers
|
||||
{
|
||||
public FilePeers()
|
||||
{
|
||||
Peers = new List<FilePeer>();
|
||||
}
|
||||
|
||||
public List<FilePeer> Peers { get; set; }
|
||||
}
|
||||
}
|
45
src/Ocelot.Provider.Rafty/FilePeersProvider.cs
Normal file
45
src/Ocelot.Provider.Rafty/FilePeersProvider.cs
Normal file
@ -0,0 +1,45 @@
|
||||
namespace Ocelot.Provider.Rafty
|
||||
{
|
||||
using System.Net.Http;
|
||||
using Configuration;
|
||||
using Configuration.Repository;
|
||||
using global::Rafty.Concensus.Peers;
|
||||
using global::Rafty.Infrastructure;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Middleware;
|
||||
using System.Collections.Generic;
|
||||
using Administration;
|
||||
|
||||
public class FilePeersProvider : IPeersProvider
|
||||
{
|
||||
private readonly IOptions<FilePeers> _options;
|
||||
private readonly List<IPeer> _peers;
|
||||
private IBaseUrlFinder _finder;
|
||||
private IInternalConfigurationRepository _repo;
|
||||
private IIdentityServerConfiguration _identityServerConfig;
|
||||
|
||||
public FilePeersProvider(IOptions<FilePeers> options, IBaseUrlFinder finder, IInternalConfigurationRepository repo, IIdentityServerConfiguration identityServerConfig)
|
||||
{
|
||||
_identityServerConfig = identityServerConfig;
|
||||
_repo = repo;
|
||||
_finder = finder;
|
||||
_options = options;
|
||||
_peers = new List<IPeer>();
|
||||
|
||||
var config = _repo.Get();
|
||||
foreach (var item in _options.Value.Peers)
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
|
||||
//todo what if this errors?
|
||||
var httpPeer = new HttpPeer(item.HostAndPort, httpClient, _finder, config.Data, _identityServerConfig);
|
||||
_peers.Add(httpPeer);
|
||||
}
|
||||
}
|
||||
|
||||
public List<IPeer> Get()
|
||||
{
|
||||
return _peers;
|
||||
}
|
||||
}
|
||||
}
|
130
src/Ocelot.Provider.Rafty/HttpPeer.cs
Normal file
130
src/Ocelot.Provider.Rafty/HttpPeer.cs
Normal file
@ -0,0 +1,130 @@
|
||||
namespace Ocelot.Provider.Rafty
|
||||
{
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Configuration;
|
||||
using global::Rafty.Concensus.Messages;
|
||||
using global::Rafty.Concensus.Peers;
|
||||
using global::Rafty.FiniteStateMachine;
|
||||
using global::Rafty.Infrastructure;
|
||||
using Middleware;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Administration;
|
||||
|
||||
public class HttpPeer : IPeer
|
||||
{
|
||||
private readonly string _hostAndPort;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly JsonSerializerSettings _jsonSerializerSettings;
|
||||
private readonly string _baseSchemeUrlAndPort;
|
||||
private BearerToken _token;
|
||||
private readonly IInternalConfiguration _config;
|
||||
private readonly IIdentityServerConfiguration _identityServerConfiguration;
|
||||
|
||||
public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IInternalConfiguration config, IIdentityServerConfiguration identityServerConfiguration)
|
||||
{
|
||||
_identityServerConfiguration = identityServerConfiguration;
|
||||
_config = config;
|
||||
Id = hostAndPort;
|
||||
_hostAndPort = hostAndPort;
|
||||
_httpClient = httpClient;
|
||||
_jsonSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
_baseSchemeUrlAndPort = finder.Find();
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public async Task<RequestVoteResponse> Request(RequestVote requestVote)
|
||||
{
|
||||
if (_token == null)
|
||||
{
|
||||
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 = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<RequestVoteResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
||||
}
|
||||
|
||||
return new RequestVoteResponse(false, requestVote.Term);
|
||||
}
|
||||
|
||||
public async Task<AppendEntriesResponse> Request(AppendEntries appendEntries)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_token == null)
|
||||
{
|
||||
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 = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<AppendEntriesResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
||||
}
|
||||
|
||||
return new AppendEntriesResponse(appendEntries.Term, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
return new AppendEntriesResponse(appendEntries.Term, false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Response<T>> Request<T>(T command)
|
||||
where T : ICommand
|
||||
{
|
||||
Console.WriteLine("SENDING REQUEST....");
|
||||
if (_token == null)
|
||||
{
|
||||
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 = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine("REQUEST OK....");
|
||||
var okResponse = JsonConvert.DeserializeObject<OkResponse<ICommand>>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
||||
return new OkResponse<T>((T)okResponse.Command);
|
||||
}
|
||||
|
||||
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>>
|
||||
{
|
||||
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 = 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);
|
||||
}
|
||||
}
|
||||
}
|
39
src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj
Normal file
39
src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj
Normal file
@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
<Description>Provides Ocelot extensions to use Rafty</Description>
|
||||
<AssemblyTitle>Ocelot.Provider.Rafty</AssemblyTitle>
|
||||
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||
<AssemblyName>Ocelot.Provider.Rafty</AssemblyName>
|
||||
<PackageId>Ocelot.Provider.Rafty</PackageId>
|
||||
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Rafty</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Rafty</PackageProjectUrl>
|
||||
<PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
|
||||
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
<Authors>Tom Pallister</Authors>
|
||||
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
|
||||
<ProjectReference Include="..\Ocelot.Administration\Ocelot.Administration.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.SQLite" Version="2.2.0"/>
|
||||
<PackageReference Include="Rafty" Version="0.4.4"/>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -0,0 +1,28 @@
|
||||
namespace Ocelot.Provider.Rafty
|
||||
{
|
||||
using Configuration.Setter;
|
||||
using DependencyInjection;
|
||||
using global::Rafty.Concensus.Node;
|
||||
using global::Rafty.FiniteStateMachine;
|
||||
using global::Rafty.Infrastructure;
|
||||
using global::Rafty.Log;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
public static class OcelotAdministrationBuilderExtensions
|
||||
{
|
||||
public static IOcelotAdministrationBuilder AddRafty(this IOcelotAdministrationBuilder builder)
|
||||
{
|
||||
var settings = new InMemorySettings(4000, 6000, 100, 10000);
|
||||
builder.Services.RemoveAll<IFileConfigurationSetter>();
|
||||
builder.Services.AddSingleton<IFileConfigurationSetter, RaftyFileConfigurationSetter>();
|
||||
builder.Services.AddSingleton<ILog, SqlLiteLog>();
|
||||
builder.Services.AddSingleton<IFiniteStateMachine, OcelotFiniteStateMachine>();
|
||||
builder.Services.AddSingleton<ISettings>(settings);
|
||||
builder.Services.AddSingleton<IPeersProvider, FilePeersProvider>();
|
||||
builder.Services.AddSingleton<INode, Node>();
|
||||
builder.Services.Configure<FilePeers>(builder.ConfigurationRoot);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
25
src/Ocelot.Provider.Rafty/OcelotFiniteStateMachine.cs
Normal file
25
src/Ocelot.Provider.Rafty/OcelotFiniteStateMachine.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace Ocelot.Provider.Rafty
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using Configuration.Setter;
|
||||
using global::Rafty.FiniteStateMachine;
|
||||
using global::Rafty.Log;
|
||||
|
||||
public class OcelotFiniteStateMachine : IFiniteStateMachine
|
||||
{
|
||||
private readonly IFileConfigurationSetter _setter;
|
||||
|
||||
public OcelotFiniteStateMachine(IFileConfigurationSetter setter)
|
||||
{
|
||||
_setter = setter;
|
||||
}
|
||||
|
||||
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;
|
||||
await _setter.Set(hack.Configuration);
|
||||
}
|
||||
}
|
||||
}
|
18
src/Ocelot.Provider.Rafty/Properties/AssemblyInfo.cs
Normal file
18
src/Ocelot.Provider.Rafty/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Ocelot")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
|
96
src/Ocelot.Provider.Rafty/RaftController.cs
Normal file
96
src/Ocelot.Provider.Rafty/RaftController.cs
Normal file
@ -0,0 +1,96 @@
|
||||
namespace Ocelot.Provider.Rafty
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using global::Rafty.Concensus.Messages;
|
||||
using global::Rafty.Concensus.Node;
|
||||
using global::Rafty.FiniteStateMachine;
|
||||
using Logging;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Middleware;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
[Authorize]
|
||||
[Route("raft")]
|
||||
public class RaftController : Controller
|
||||
{
|
||||
private readonly INode _node;
|
||||
private readonly IOcelotLogger _logger;
|
||||
private readonly string _baseSchemeUrlAndPort;
|
||||
private readonly JsonSerializerSettings _jsonSerialiserSettings;
|
||||
|
||||
public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IBaseUrlFinder finder)
|
||||
{
|
||||
_jsonSerialiserSettings = new JsonSerializerSettings
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
_baseSchemeUrlAndPort = finder.Find();
|
||||
_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 = await _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 = await _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 = await _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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
src/Ocelot.Provider.Rafty/RaftyFileConfigurationSetter.cs
Normal file
30
src/Ocelot.Provider.Rafty/RaftyFileConfigurationSetter.cs
Normal file
@ -0,0 +1,30 @@
|
||||
namespace Ocelot.Provider.Rafty
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using Configuration.File;
|
||||
using Configuration.Setter;
|
||||
using global::Rafty.Concensus.Node;
|
||||
using global::Rafty.Infrastructure;
|
||||
|
||||
public class RaftyFileConfigurationSetter : IFileConfigurationSetter
|
||||
{
|
||||
private readonly INode _node;
|
||||
|
||||
public RaftyFileConfigurationSetter(INode node)
|
||||
{
|
||||
_node = node;
|
||||
}
|
||||
|
||||
public async Task<Responses.Response> Set(FileConfiguration fileConfiguration)
|
||||
{
|
||||
var result = await _node.Accept(new UpdateFileConfiguration(fileConfiguration));
|
||||
|
||||
if (result.GetType() == typeof(ErrorResponse<UpdateFileConfiguration>))
|
||||
{
|
||||
return new Responses.ErrorResponse(new UnableToSaveAcceptCommand($"unable to save file configuration to state machine"));
|
||||
}
|
||||
|
||||
return new Responses.OkResponse();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
namespace Ocelot.Provider.Rafty
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using global::Rafty.Concensus.Node;
|
||||
using global::Rafty.Infrastructure;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Middleware;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
public static class RaftyMiddlewareConfigurationProvider
|
||||
{
|
||||
public static OcelotMiddlewareConfigurationDelegate Get = builder =>
|
||||
{
|
||||
if (UsingRafty(builder))
|
||||
{
|
||||
SetUpRafty(builder);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
private static bool UsingRafty(IApplicationBuilder builder)
|
||||
{
|
||||
var node = builder.ApplicationServices.GetService<INode>();
|
||||
if (node != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SetUpRafty(IApplicationBuilder builder)
|
||||
{
|
||||
var applicationLifetime = builder.ApplicationServices.GetService<IApplicationLifetime>();
|
||||
applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder));
|
||||
var node = builder.ApplicationServices.GetService<INode>();
|
||||
var nodeId = builder.ApplicationServices.GetService<NodeId>();
|
||||
node.Start(nodeId);
|
||||
}
|
||||
|
||||
private static void OnShutdown(IApplicationBuilder app)
|
||||
{
|
||||
var node = app.ApplicationServices.GetService<INode>();
|
||||
node.Stop();
|
||||
}
|
||||
}
|
||||
}
|
334
src/Ocelot.Provider.Rafty/SqlLiteLog.cs
Normal file
334
src/Ocelot.Provider.Rafty/SqlLiteLog.cs
Normal file
@ -0,0 +1,334 @@
|
||||
namespace Ocelot.Provider.Rafty
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using global::Rafty.Infrastructure;
|
||||
using global::Rafty.Log;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
public class SqlLiteLog : ILog
|
||||
{
|
||||
private readonly string _path;
|
||||
private readonly SemaphoreSlim _sempaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly ILogger _logger;
|
||||
private readonly NodeId _nodeId;
|
||||
|
||||
public SqlLiteLog(NodeId nodeId, ILoggerFactory loggerFactory)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger<SqlLiteLog>();
|
||||
_nodeId = nodeId;
|
||||
_path = $"{nodeId.Id.Replace("/", "").Replace(":", "")}.db";
|
||||
_sempaphore.Wait();
|
||||
|
||||
if (!File.Exists(_path))
|
||||
{
|
||||
var fs = File.Create(_path);
|
||||
|
||||
fs.Dispose();
|
||||
|
||||
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
const string sql = @"create table logs (
|
||||
id integer primary key,
|
||||
data text not null
|
||||
)";
|
||||
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var result = command.ExecuteNonQuery();
|
||||
|
||||
_logger.LogInformation(result == 0
|
||||
? $"id: {_nodeId.Id} create database, result: {result}"
|
||||
: $"id: {_nodeId.Id} did not create database., result: {result}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
}
|
||||
|
||||
public async Task<int> LastLogIndex()
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
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(await command.ExecuteScalarAsync());
|
||||
if (index > result)
|
||||
{
|
||||
result = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<long> LastLogTerm()
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
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(await command.ExecuteScalarAsync());
|
||||
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
if (log != null && log.Term > result)
|
||||
{
|
||||
result = log.Term;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<int> Count()
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
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(await command.ExecuteScalarAsync());
|
||||
if (index > result)
|
||||
{
|
||||
result = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<int> Apply(LogEntry log)
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
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}')";
|
||||
_logger.LogInformation($"id: {_nodeId.Id}, sql: {sql}");
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var result = await command.ExecuteNonQueryAsync();
|
||||
_logger.LogInformation($"id: {_nodeId.Id}, insert log result: {result}");
|
||||
}
|
||||
|
||||
sql = "select last_insert_rowid()";
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var result = await command.ExecuteScalarAsync();
|
||||
_logger.LogInformation($"id: {_nodeId.Id}, about to release semaphore");
|
||||
_sempaphore.Release();
|
||||
_logger.LogInformation($"id: {_nodeId.Id}, saved log to sqlite");
|
||||
return Convert.ToInt32(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteConflictsFromThisLog(int index, LogEntry logEntry)
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
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};";
|
||||
_logger.LogInformation($"id: {_nodeId.Id} sql: {sql}");
|
||||
using (var command = new SqliteCommand(sql, connection))
|
||||
{
|
||||
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
|
||||
_logger.LogInformation($"id {_nodeId.Id} got log for index: {index}, data is {data} and new log term is {logEntry.Term}");
|
||||
|
||||
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};";
|
||||
_logger.LogInformation($"id: {_nodeId.Id} sql: {deleteSql}");
|
||||
using (var deleteCommand = new SqliteCommand(deleteSql, connection))
|
||||
{
|
||||
var result = await deleteCommand.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
}
|
||||
|
||||
public async Task<bool> IsDuplicate(int index, LogEntry logEntry)
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
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(await command.ExecuteScalarAsync());
|
||||
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
|
||||
if (logEntry != null && log != null && logEntry.Term == log.Term)
|
||||
{
|
||||
_sempaphore.Release();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<LogEntry> Get(int index)
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
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(await command.ExecuteScalarAsync());
|
||||
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
_sempaphore.Release();
|
||||
return log;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<(int index, LogEntry logEntry)>> GetFrom(int index)
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
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 = await command.ExecuteReaderAsync())
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
return logsToReturn;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<long> GetTermAtIndex(int index)
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
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(await command.ExecuteScalarAsync());
|
||||
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
};
|
||||
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||
if (log != null && log.Term > result)
|
||||
{
|
||||
result = log.Term;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task Remove(int indexOfCommand)
|
||||
{
|
||||
_sempaphore.Wait();
|
||||
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
//todo - sql injection dont copy this..
|
||||
var deleteSql = $"delete from logs where id >= {indexOfCommand};";
|
||||
_logger.LogInformation($"id: {_nodeId.Id} Remove {deleteSql}");
|
||||
using (var deleteCommand = new SqliteCommand(deleteSql, connection))
|
||||
{
|
||||
var result = await deleteCommand.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
|
||||
_sempaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
11
src/Ocelot.Provider.Rafty/UnableToSaveAcceptCommand.cs
Normal file
11
src/Ocelot.Provider.Rafty/UnableToSaveAcceptCommand.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Ocelot.Provider.Rafty
|
||||
{
|
||||
using Errors;
|
||||
public class UnableToSaveAcceptCommand : Error
|
||||
{
|
||||
public UnableToSaveAcceptCommand(string message)
|
||||
: base(message, OcelotErrorCode.UnknownError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
15
src/Ocelot.Provider.Rafty/UpdateFileConfiguration.cs
Normal file
15
src/Ocelot.Provider.Rafty/UpdateFileConfiguration.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace Ocelot.Provider.Rafty
|
||||
{
|
||||
using Configuration.File;
|
||||
using global::Rafty.FiniteStateMachine;
|
||||
|
||||
public class UpdateFileConfiguration : ICommand
|
||||
{
|
||||
public UpdateFileConfiguration(FileConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public FileConfiguration Configuration { get; private set; }
|
||||
}
|
||||
}
|
103
src/Ocelot.Tracing.Butterfly/ButterflyTracer.cs
Normal file
103
src/Ocelot.Tracing.Butterfly/ButterflyTracer.cs
Normal file
@ -0,0 +1,103 @@
|
||||
namespace Ocelot.Tracing.Butterfly
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using global::Butterfly.Client.AspNetCore;
|
||||
using global::Butterfly.Client.Tracing;
|
||||
using global::Butterfly.OpenTracing;
|
||||
using Infrastructure.Extensions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
public class ButterflyTracer : DelegatingHandler, Logging.ITracer
|
||||
{
|
||||
private readonly IServiceTracer _tracer;
|
||||
private const string PrefixSpanId = "ot-spanId";
|
||||
|
||||
public ButterflyTracer(IServiceProvider services)
|
||||
{
|
||||
_tracer = services.GetService<IServiceTracer>();
|
||||
}
|
||||
|
||||
public void Event(HttpContext httpContext, string @event)
|
||||
{
|
||||
// todo - if the user isnt using tracing the code gets here and will blow up on
|
||||
// _tracer.Tracer.TryExtract..
|
||||
if (_tracer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var span = httpContext.GetSpan();
|
||||
|
||||
if (span == null)
|
||||
{
|
||||
var spanBuilder = new SpanBuilder($"server {httpContext.Request.Method} {httpContext.Request.Path}");
|
||||
if (_tracer.Tracer.TryExtract(out var spanContext, httpContext.Request.Headers, (c, k) => c[k].GetValue(),
|
||||
c => c.Select(x => new KeyValuePair<string, string>(x.Key, x.Value.GetValue())).GetEnumerator()))
|
||||
{
|
||||
spanBuilder.AsChildOf(spanContext);
|
||||
}
|
||||
|
||||
span = _tracer.Start(spanBuilder);
|
||||
httpContext.SetSpan(span);
|
||||
}
|
||||
|
||||
span?.Log(LogField.CreateNew().Event(@event));
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken,
|
||||
Action<string> addTraceIdToRepo,
|
||||
Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> baseSendAsync)
|
||||
{
|
||||
return _tracer.ChildTraceAsync($"httpclient {request.Method}", DateTimeOffset.UtcNow, span => TracingSendAsync(span, request, cancellationToken, addTraceIdToRepo, baseSendAsync));
|
||||
}
|
||||
|
||||
protected virtual async Task<HttpResponseMessage> TracingSendAsync(
|
||||
ISpan span,
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken,
|
||||
Action<string> addTraceIdToRepo,
|
||||
Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> baseSendAsync)
|
||||
{
|
||||
if (request.Headers.Contains(PrefixSpanId))
|
||||
{
|
||||
request.Headers.Remove(PrefixSpanId);
|
||||
request.Headers.TryAddWithoutValidation(PrefixSpanId, span.SpanContext.SpanId);
|
||||
}
|
||||
|
||||
addTraceIdToRepo(span.SpanContext.TraceId);
|
||||
|
||||
span.Tags.Client().Component("HttpClient")
|
||||
.HttpMethod(request.Method.Method)
|
||||
.HttpUrl(request.RequestUri.OriginalString)
|
||||
.HttpHost(request.RequestUri.Host)
|
||||
.HttpPath(request.RequestUri.PathAndQuery)
|
||||
.PeerAddress(request.RequestUri.OriginalString)
|
||||
.PeerHostName(request.RequestUri.Host)
|
||||
.PeerPort(request.RequestUri.Port);
|
||||
|
||||
_tracer.Tracer.Inject(span.SpanContext, request.Headers, (c, k, v) =>
|
||||
{
|
||||
if (!c.Contains(k))
|
||||
{
|
||||
c.Add(k, v);
|
||||
}
|
||||
});
|
||||
|
||||
span.Log(LogField.CreateNew().ClientSend());
|
||||
|
||||
var responseMessage = await baseSendAsync(request, cancellationToken);
|
||||
|
||||
span.Log(LogField.CreateNew().ClientReceive());
|
||||
|
||||
return responseMessage;
|
||||
}
|
||||
}
|
||||
}
|
37
src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj
Normal file
37
src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj
Normal file
@ -0,0 +1,37 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
<Description>This package provides methods to integrate Butterfly tracing with Ocelot.</Description>
|
||||
<AssemblyTitle>Ocelot.Tracing.Butterfly</AssemblyTitle>
|
||||
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||
<AssemblyName>Ocelot.Tracing.Butterfly</AssemblyName>
|
||||
<PackageId>Ocelot.Tracing.Butterfly</PackageId>
|
||||
<PackageTags>API Gateway;.NET core; Butterfly; ButterflyAPM</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/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>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
<Authors>Tom Pallister</Authors>
|
||||
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||
<RootNamespace>Ocelot.Tracing.Butterfly</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Butterfly.Client" Version="0.0.8" />
|
||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
18
src/Ocelot.Tracing.Butterfly/OcelotBuilderExtensions.cs
Normal file
18
src/Ocelot.Tracing.Butterfly/OcelotBuilderExtensions.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Ocelot.Tracing.Butterfly
|
||||
{
|
||||
using System;
|
||||
using DependencyInjection;
|
||||
using global::Butterfly.Client.AspNetCore;
|
||||
using Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
public static class OcelotBuilderExtensions
|
||||
{
|
||||
public static IOcelotBuilder AddButterfly(this IOcelotBuilder builder, Action<ButterflyOptions> settings)
|
||||
{
|
||||
builder.Services.AddSingleton<ITracer, ButterflyTracer>();
|
||||
builder.Services.AddButterfly(settings);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
@ -10,8 +10,8 @@
|
||||
<AssemblyName>Ocelot</AssemblyName>
|
||||
<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/ThreeMammals/Ocelot</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot</PackageProjectUrl>
|
||||
<PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
|
||||
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
|
Reference in New Issue
Block a user