Feature/steeltoe (#324)

* #262 - Integrated Steeltoe Service Discovery with Ocelot for review.

* messing around

* seems to be working with eureka

* acceptance test passing with external lib references

* #262 support for netflix eureka service discovery thanks to pivotal

* #262 fixed warnings
This commit is contained in:
Tom Pallister 2018-04-20 21:28:49 +01:00 committed by GitHub
parent a5f3e0fa75
commit 4f061f2b74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 665 additions and 72 deletions

View File

@ -39,7 +39,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio
* Routing * Routing
* Request Aggregation * Request Aggregation
* Service Discovery with Consul * Service Discovery with Consul & Eureka
* Service Fabric * Service Fabric
* WebSockets * WebSockets
* Authentication * Authentication
@ -52,6 +52,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio
* Headers / Query String / Claims Transformation * Headers / Query String / Claims Transformation
* Custom Middleware / Delegating Handlers * Custom Middleware / Delegating Handlers
* Configuration / Administration REST API * Configuration / Administration REST API
* Platform / Cloud agnostic
## How to install ## How to install

View File

@ -53,3 +53,36 @@ If you are using ACL with Consul Ocelot supports adding the X-Consul-Token heade
} }
Ocelot will add this token to the consul client that it uses to make requests and that is then used for every request. Ocelot will add this token to the consul client that it uses to make requests and that is then used for every request.
Eureka
^^^^^^
This feature was requested as part of `Issue 262 <https://github.com/TomPallister/Ocelot/issue/262>`_ . to add support for Netflix's
Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe <https://steeltoe.io/>`_ which is something
to do with `Pivotal <https://pivotal.io/platform>`_! Anyway enough of the background.
In order to get this working add the following to ocelot.json..
.. code-block:: json
"ServiceDiscoveryProvider": {
"Type": "Eureka"
}
And following the guide `Here <https://steeltoe.io/docs/steeltoe-discovery/>`_ you may also need to add some stuff to appsettings.json. For example the json below
tells the steeltoe / pivotal services where to look for the service discovery server and if the service should register with it.
.. code-block:: json
"eureka": {
"client": {
"serviceUrl": "http://localhost:8761/eureka/",
"shouldRegisterWithEureka": true
}
}
Ocelot will now register all the necessary services when it starts up and if you have the json above will register itself with
Eureka. One of the services polls Eureka every 30 seconds (default) and gets the latest service state and persists this in memory.
When Ocelot asks for a given service it is retrieved from memory so performance is not a big problem. Please note that this code
is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them for all the hard work.

View File

@ -45,6 +45,8 @@ namespace Ocelot.DependencyInjection
using Ocelot.Infrastructure.Consul; using Ocelot.Infrastructure.Consul;
using Butterfly.Client.Tracing; using Butterfly.Client.Tracing;
using Ocelot.Middleware.Multiplexer; using Ocelot.Middleware.Multiplexer;
using Pivotal.Discovery.Client;
using ServiceDiscovery.Providers;
public class OcelotBuilder : IOcelotBuilder public class OcelotBuilder : IOcelotBuilder
{ {
@ -112,6 +114,17 @@ namespace Ocelot.DependencyInjection
_services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>(); _services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();
_services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>(); _services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>();
if (UsingEurekaServiceDiscoveryProvider(configurationRoot))
{
_services.AddDiscoveryClient(configurationRoot);
}
else
{
_services.TryAddSingleton<IDiscoveryClient, FakeEurekaDiscoveryClient>();
}
_services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc
// could maybe use a scoped data repository // could maybe use a scoped data repository
_services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>(); _services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
@ -346,5 +359,13 @@ namespace Ocelot.DependencyInjection
} }
}; };
} }
private static bool UsingEurekaServiceDiscoveryProvider(IConfiguration configurationRoot)
{
var type = configurationRoot.GetValue<string>("GlobalConfiguration:ServiceDiscoveryProvider:Type",
string.Empty);
return type.ToLower() == "eureka";
}
} }
} }

View File

@ -17,6 +17,7 @@
using Rafty.Concensus; using Rafty.Concensus;
using Rafty.Infrastructure; using Rafty.Infrastructure;
using Ocelot.Middleware.Pipeline; using Ocelot.Middleware.Pipeline;
using Pivotal.Discovery.Client;
public static class OcelotMiddlewareExtensions public static class OcelotMiddlewareExtensions
{ {
@ -38,6 +39,11 @@
SetUpRafty(builder); SetUpRafty(builder);
} }
if (UsingEurekaServiceDiscoveryProvider(configuration))
{
builder.UseDiscoveryClient();
}
ConfigureDiagnosticListener(builder); ConfigureDiagnosticListener(builder);
var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices);
@ -63,6 +69,11 @@
return builder; return builder;
} }
private static bool UsingEurekaServiceDiscoveryProvider(IInternalConfiguration configuration)
{
return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "eureka";
}
private static bool UsingRafty(IApplicationBuilder builder) private static bool UsingRafty(IApplicationBuilder builder)
{ {
var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode; var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode;

View File

@ -46,6 +46,7 @@
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.2" /> <PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.2" />
<PackageReference Include="Consul" Version="0.7.2.4" /> <PackageReference Include="Consul" Version="0.7.2.4" />
<PackageReference Include="Polly" Version="5.8.0" /> <PackageReference Include="Polly" Version="5.8.0" />
<PackageReference Include="Pivotal.Discovery.Client" Version="1.1.0" />
<PackageReference Include="IdentityServer4" Version="2.1.3" /> <PackageReference Include="IdentityServer4" Version="2.1.3" />
<PackageReference Include="Rafty" Version="0.4.2" /> <PackageReference Include="Rafty" Version="0.4.2" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,34 @@
namespace Ocelot.ServiceDiscovery.Providers
{
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Pivotal.Discovery.Client;
using Values;
public class EurekaServiceDiscoveryProvider : IServiceDiscoveryProvider
{
private readonly IDiscoveryClient _client;
private readonly string _serviceName;
public EurekaServiceDiscoveryProvider(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);
}
}
}

View File

@ -0,0 +1,27 @@
namespace Ocelot.ServiceDiscovery.Providers
{
using System.Collections.Generic;
using System.Threading.Tasks;
using Pivotal.Discovery.Client;
public class FakeEurekaDiscoveryClient : IDiscoveryClient
{
public IServiceInstance GetLocalServiceInstance()
{
throw new System.NotImplementedException();
}
public IList<IServiceInstance> GetInstances(string serviceId)
{
throw new System.NotImplementedException();
}
public Task ShutdownAsync()
{
throw new System.NotImplementedException();
}
public string Description { get; }
public IList<string> Services { get; }
}
}

View File

@ -8,15 +8,19 @@ using Ocelot.Values;
namespace Ocelot.ServiceDiscovery namespace Ocelot.ServiceDiscovery
{ {
using Pivotal.Discovery.Client;
public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory
{ {
private readonly IOcelotLoggerFactory _factory; private readonly IOcelotLoggerFactory _factory;
private readonly IConsulClientFactory _clientFactory; private readonly IConsulClientFactory _consulFactory;
private readonly IDiscoveryClient _eurekaClient;
public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IConsulClientFactory clientFactory) public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IConsulClientFactory consulFactory, IDiscoveryClient eurekaClient)
{ {
_factory = factory; _factory = factory;
_clientFactory = clientFactory; _consulFactory = consulFactory;
_eurekaClient = eurekaClient;
} }
public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute) public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute)
@ -40,14 +44,19 @@ namespace Ocelot.ServiceDiscovery
private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration serviceConfig, string serviceName) private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration serviceConfig, string serviceName)
{ {
if (serviceConfig.Type == "ServiceFabric") if (serviceConfig.Type?.ToLower() == "servicefabric")
{ {
var config = new ServiceFabricConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName); var config = new ServiceFabricConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName);
return new ServiceFabricServiceDiscoveryProvider(config); return new ServiceFabricServiceDiscoveryProvider(config);
} }
if (serviceConfig.Type?.ToLower() == "eureka")
{
return new EurekaServiceDiscoveryProvider(serviceName, _eurekaClient);
}
var consulRegistryConfiguration = new ConsulRegistryConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName, serviceConfig.Token); var consulRegistryConfiguration = new ConsulRegistryConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName, serviceConfig.Token);
return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory, _clientFactory); return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory, _consulFactory);
} }
} }
} }

View File

@ -15,6 +15,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Update="appsettings.product.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="appsettings.json"> <None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>

View File

@ -1,3 +1,5 @@
namespace Ocelot.AcceptanceTests
{
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -11,16 +13,17 @@ using Ocelot.Configuration.File;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using Newtonsoft.Json;
using Pivotal.Discovery.Client;
namespace Ocelot.AcceptanceTests
{
public class ServiceDiscoveryTests : IDisposable public class ServiceDiscoveryTests : IDisposable
{ {
private IWebHost _builderOne; private IWebHost _builderOne;
private IWebHost _builderTwo; private IWebHost _builderTwo;
private IWebHost _fakeConsulBuilder; private IWebHost _fakeConsulBuilder;
private readonly Steps _steps; private readonly Steps _steps;
private readonly List<ServiceEntry> _serviceEntries; private readonly List<ServiceEntry> _consulServices;
private readonly List<IServiceInstance> _eurekaInstances;
private int _counterOne; private int _counterOne;
private int _counterTwo; private int _counterTwo;
private static readonly object SyncLock = new object(); private static readonly object SyncLock = new object();
@ -31,11 +34,59 @@ namespace Ocelot.AcceptanceTests
public ServiceDiscoveryTests() public ServiceDiscoveryTests()
{ {
_steps = new Steps(); _steps = new Steps();
_serviceEntries = new List<ServiceEntry>(); _consulServices = new List<ServiceEntry>();
_eurekaInstances = new List<IServiceInstance>();
} }
[Fact] [Fact]
public void should_use_service_discovery_and_load_balance_request() public void should_use_eureka_service_discovery_and_make_request()
{
var eurekaPort = 8761;
var serviceName = "product";
var downstreamServicePort = 50371;
var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}";
var fakeEurekaServiceDiscoveryUrl = $"http://localhost:{eurekaPort}";
var instanceOne = new FakeEurekaService(serviceName, "localhost", downstreamServicePort, false,
new Uri($"http://localhost:{downstreamServicePort}"), new Dictionary<string, string>());
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
ServiceName = serviceName,
LoadBalancer = "LeastConnection",
UseServiceDiscovery = true,
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Type = "Eureka"
}
}
};
this.Given(x => x.GivenEurekaProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
.And(x => x.GivenThereIsAFakeEurekaServiceDiscoveryProvider(fakeEurekaServiceDiscoveryUrl, serviceName))
.And(x => x.GivenTheServicesAreRegisteredWithEureka(instanceOne))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(_ => _steps.ThenTheResponseBodyShouldBe(nameof(ServiceDiscoveryTests)))
.BDDfy();
}
[Fact]
public void should_use_consul_service_discovery_and_load_balance_request()
{ {
var consulPort = 8502; var consulPort = 8502;
var serviceName = "product"; var serviceName = "product";
@ -102,7 +153,6 @@ namespace Ocelot.AcceptanceTests
.BDDfy(); .BDDfy();
} }
//test from issue #213
[Fact] [Fact]
public void should_handle_request_to_consul_for_downstream_service_and_make_request() public void should_handle_request_to_consul_for_downstream_service_and_make_request()
{ {
@ -158,7 +208,6 @@ namespace Ocelot.AcceptanceTests
.BDDfy(); .BDDfy();
} }
//test from issue #295
[Fact] [Fact]
public void should_use_token_to_make_request_to_consul() public void should_use_token_to_make_request_to_consul()
{ {
@ -218,7 +267,7 @@ namespace Ocelot.AcceptanceTests
} }
[Fact] [Fact]
public void should_send_request_to_service_after_it_becomes_available() public void should_send_request_to_service_after_it_becomes_available_in_consul()
{ {
var consulPort = 8501; var consulPort = 8501;
var serviceName = "product"; var serviceName = "product";
@ -296,7 +345,7 @@ namespace Ocelot.AcceptanceTests
private void WhenIAddAServiceBackIn(ServiceEntry serviceEntryTwo) private void WhenIAddAServiceBackIn(ServiceEntry serviceEntryTwo)
{ {
_serviceEntries.Add(serviceEntryTwo); _consulServices.Add(serviceEntryTwo);
} }
private void ThenOnlyOneServiceHasBeenCalled() private void ThenOnlyOneServiceHasBeenCalled()
@ -307,7 +356,7 @@ namespace Ocelot.AcceptanceTests
private void WhenIRemoveAService(ServiceEntry serviceEntryTwo) private void WhenIRemoveAService(ServiceEntry serviceEntryTwo)
{ {
_serviceEntries.Remove(serviceEntryTwo); _consulServices.Remove(serviceEntryTwo);
} }
private void GivenIResetCounters() private void GivenIResetCounters()
@ -332,10 +381,100 @@ namespace Ocelot.AcceptanceTests
{ {
foreach(var serviceEntry in serviceEntries) foreach(var serviceEntry in serviceEntries)
{ {
_serviceEntries.Add(serviceEntry); _consulServices.Add(serviceEntry);
} }
} }
private void GivenTheServicesAreRegisteredWithEureka(params IServiceInstance[] serviceInstances)
{
foreach (var instance in serviceInstances)
{
_eurekaInstances.Add(instance);
}
}
private void GivenThereIsAFakeEurekaServiceDiscoveryProvider(string url, string serviceName)
{
_fakeConsulBuilder = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
if (context.Request.Path.Value == "/eureka/apps/")
{
var apps = new List<Application>();
foreach (var serviceInstance in _eurekaInstances)
{
var a = new Application
{
name = serviceName,
instance = new List<Instance>
{
new Instance
{
instanceId = $"{serviceInstance.Host}:{serviceInstance}",
hostName = serviceInstance.Host,
app = serviceName,
ipAddr = "127.0.0.1",
status = "UP",
overriddenstatus = "UNKNOWN",
port = new Port {value = serviceInstance.Port, enabled = "true"},
securePort = new SecurePort {value = serviceInstance.Port, enabled = "true"},
countryId = 1,
dataCenterInfo = new DataCenterInfo {value = "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", name = "MyOwn"},
leaseInfo = new LeaseInfo
{
renewalIntervalInSecs = 30,
durationInSecs = 90,
registrationTimestamp = 1457714988223,
lastRenewalTimestamp= 1457716158319,
evictionTimestamp = 0,
serviceUpTimestamp = 1457714988223
},
metadata = new Metadata
{
value = "java.util.Collections$EmptyMap"
},
homePageUrl = $"{serviceInstance.Host}:{serviceInstance.Port}",
statusPageUrl = $"{serviceInstance.Host}:{serviceInstance.Port}",
healthCheckUrl = $"{serviceInstance.Host}:{serviceInstance.Port}",
vipAddress = serviceName,
isCoordinatingDiscoveryServer = "false",
lastUpdatedTimestamp = "1457714988223",
lastDirtyTimestamp = "1457714988172",
actionType = "ADDED"
}
}
};
apps.Add(a);
}
var applications = new EurekaApplications
{
applications = new Applications
{
application = apps,
apps__hashcode = "UP_1_",
versions__delta = "1"
}
};
await context.Response.WriteJsonAsync(applications);
}
});
})
.Build();
_fakeConsulBuilder.Start();
}
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName) private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName)
{ {
_fakeConsulBuilder = new WebHostBuilder() _fakeConsulBuilder = new WebHostBuilder()
@ -355,7 +494,7 @@ namespace Ocelot.AcceptanceTests
_receivedToken = values.First(); _receivedToken = values.First();
} }
await context.Response.WriteJsonAsync(_serviceEntries); await context.Response.WriteJsonAsync(_consulServices);
} }
}); });
}) })
@ -433,6 +572,34 @@ namespace Ocelot.AcceptanceTests
_builderTwo.Start(); _builderTwo.Start();
} }
private void GivenEurekaProductServiceOneIsRunning(string url, int statusCode)
{
_builderOne = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
try
{
context.Response.StatusCode = 200;
await context.Response.WriteAsync(nameof(ServiceDiscoveryTests));
}
catch (Exception exception)
{
await context.Response.WriteAsync(exception.StackTrace);
}
});
})
.Build();
_builderOne.Start();
}
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody)
{ {
_builder = new WebHostBuilder() _builder = new WebHostBuilder()
@ -471,4 +638,113 @@ namespace Ocelot.AcceptanceTests
_steps.Dispose(); _steps.Dispose();
} }
} }
public class FakeEurekaService : IServiceInstance
{
public FakeEurekaService(string serviceId, string host, int port, bool isSecure, Uri uri, IDictionary<string, string> metadata)
{
ServiceId = serviceId;
Host = host;
Port = port;
IsSecure = isSecure;
Uri = uri;
Metadata = metadata;
}
public string ServiceId { get; }
public string Host { get; }
public int Port { get; }
public bool IsSecure { get; }
public Uri Uri { get; }
public IDictionary<string, string> Metadata { get; }
}
public class Port
{
[JsonProperty("$")]
public int value { get; set; }
[JsonProperty("@enabled")]
public string enabled { get; set; }
}
public class SecurePort
{
[JsonProperty("$")]
public int value { get; set; }
[JsonProperty("@enabled")]
public string enabled { get; set; }
}
public class DataCenterInfo
{
[JsonProperty("@class")]
public string value { get; set; }
public string name { get; set; }
}
public class LeaseInfo
{
public int renewalIntervalInSecs { get; set; }
public int durationInSecs { get; set; }
public long registrationTimestamp { get; set; }
public long lastRenewalTimestamp { get; set; }
public int evictionTimestamp { get; set; }
public long serviceUpTimestamp { get; set; }
}
public class Metadata
{
[JsonProperty("@class")]
public string value { get; set; }
}
public class Instance
{
public string instanceId { get; set; }
public string hostName { get; set; }
public string app { get; set; }
public string ipAddr { get; set; }
public string status { get; set; }
public string overriddenstatus { get; set; }
public Port port { get; set; }
public SecurePort securePort { get; set; }
public int countryId { get; set; }
public DataCenterInfo dataCenterInfo { get; set; }
public LeaseInfo leaseInfo { get; set; }
public Metadata metadata { get; set; }
public string homePageUrl { get; set; }
public string statusPageUrl { get; set; }
public string healthCheckUrl { get; set; }
public string vipAddress { get; set; }
public string isCoordinatingDiscoveryServer { get; set; }
public string lastUpdatedTimestamp { get; set; }
public string lastDirtyTimestamp { get; set; }
public string actionType { get; set; }
}
public class Application
{
public string name { get; set; }
public List<Instance> instance { get; set; }
}
public class Applications
{
public string versions__delta { get; set; }
public string apps__hashcode { get; set; }
public List<Application> application { get; set; }
}
public class EurekaApplications
{
public Applications applications { get; set; }
}
} }

View File

@ -6,5 +6,19 @@
"System": "Error", "System": "Error",
"Microsoft": "Error" "Microsoft": "Error"
} }
},
"spring": {
"application": {
"name": "ocelot"
}
},
"eureka": {
"client": {
"serviceUrl": "http://localhost:8761/eureka/",
"shouldRegisterWithEureka": true,
"shouldFetchRegistry": true,
"port": 5000,
"hostName": "localhost"
}
} }
} }

View File

@ -0,0 +1,24 @@
{
"Logging": {
"IncludeScopes": true,
"LogLevel": {
"Default": "Error",
"System": "Error",
"Microsoft": "Error"
}
},
"spring": {
"application": {
"name": "product"
}
},
"eureka": {
"client": {
"serviceUrl": "http://localhost:8761/eureka/",
"shouldRegisterWithEureka": true
},
"instance": {
"port": 50371
}
}
}

View File

@ -1,13 +1,13 @@
using System.IO; namespace Ocelot.ManualTest
{
using System.IO;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.DependencyInjection; using Ocelot.DependencyInjection;
using Ocelot.Middleware; using Ocelot.Middleware;
namespace Ocelot.ManualTest
{
public class Program public class Program
{ {
public static void Main(string[] args) public static void Main(string[] args)

View File

@ -2,9 +2,15 @@
"Logging": { "Logging": {
"IncludeScopes": false, "IncludeScopes": false,
"LogLevel": { "LogLevel": {
"Default": "Trace", "Default": "Error",
"System": "Error", "System": "Error",
"Microsoft": "Error" "Microsoft": "Error"
} }
},
"eureka": {
"client": {
"serviceUrl": "http://localhost:8761/eureka/",
"shouldRegisterWithEureka": true
}
} }
} }

View File

@ -1,20 +1,15 @@
using System.Threading.Tasks; namespace Ocelot.UnitTests.Middleware
using Microsoft.AspNetCore.Http; {
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.DependencyInjection; using Ocelot.DependencyInjection;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Middleware.Pipeline; using Ocelot.Middleware.Pipeline;
using Pivotal.Discovery.Client;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
namespace Ocelot.UnitTests.Middleware
{
public class OcelotPipelineExtensionsTests public class OcelotPipelineExtensionsTests
{ {
private OcelotPipelineBuilder _builder; private OcelotPipelineBuilder _builder;
@ -45,6 +40,7 @@ namespace Ocelot.UnitTests.Middleware
var root = test.Build(); var root = test.Build();
var services = new ServiceCollection(); var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(root); services.AddSingleton<IConfiguration>(root);
services.AddDiscoveryClient(new DiscoveryOptions {ClientType = DiscoveryClientType.EUREKA});
services.AddOcelot(); services.AddOcelot();
var provider = services.BuildServiceProvider(); var provider = services.BuildServiceProvider();
_builder = new OcelotPipelineBuilder(provider); _builder = new OcelotPipelineBuilder(provider);

View File

@ -0,0 +1,117 @@
namespace Ocelot.UnitTests.ServiceDiscovery
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Moq;
using Ocelot.ServiceDiscovery.Providers;
using Pivotal.Discovery.Client;
using Shouldly;
using TestStack.BDDfy;
using Values;
using Xunit;
public class EurekaServiceDiscoveryProviderTests
{
private readonly EurekaServiceDiscoveryProvider _provider;
private readonly Mock<IDiscoveryClient> _client;
private readonly string _serviceId;
private List<IServiceInstance> _instances;
private List<Service> _result;
public EurekaServiceDiscoveryProviderTests()
{
_serviceId = "Laura";
_client = new Mock<IDiscoveryClient>();
_provider = new EurekaServiceDiscoveryProvider(_serviceId, _client.Object);
}
[Fact]
public void should_return_empty_services()
{
this.When(_ => WhenIGet())
.Then(_ => ThenTheCountIs(0))
.BDDfy();
}
[Fact]
public void should_return_service_from_client()
{
var instances = new List<IServiceInstance>
{
new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary<string, string>())
};
this.Given(_ => GivenThe(instances))
.When(_ => WhenIGet())
.Then(_ => ThenTheCountIs(1))
.And(_ => ThenTheClientIsCalledCorrectly())
.And(_ => ThenTheServiceIsMapped())
.BDDfy();
}
[Fact]
public void should_return_services_from_client()
{
var instances = new List<IServiceInstance>
{
new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary<string, string>()),
new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary<string, string>())
};
this.Given(_ => GivenThe(instances))
.When(_ => WhenIGet())
.Then(_ => ThenTheCountIs(2))
.And(_ => ThenTheClientIsCalledCorrectly())
.BDDfy();
}
private void ThenTheServiceIsMapped()
{
_result[0].HostAndPort.DownstreamHost.ShouldBe("somehost");
_result[0].HostAndPort.DownstreamPort.ShouldBe(801);
_result[0].Name.ShouldBe(_serviceId);
}
private void ThenTheCountIs(int expected)
{
_result.Count.ShouldBe(expected);
}
private void ThenTheClientIsCalledCorrectly()
{
_client.Verify(x => x.GetInstances(_serviceId), Times.Once);
}
private async Task WhenIGet()
{
_result = await _provider.Get();
}
private void GivenThe(List<IServiceInstance> instances)
{
_instances = instances;
_client.Setup(x => x.GetInstances(It.IsAny<string>())).Returns(instances);
}
}
public class EurekaService : IServiceInstance
{
public EurekaService(string serviceId, string host, int port, bool isSecure, Uri uri, IDictionary<string, string> metadata)
{
ServiceId = serviceId;
Host = host;
Port = port;
IsSecure = isSecure;
Uri = uri;
Metadata = metadata;
}
public string ServiceId { get; }
public string Host { get; }
public int Port { get; }
public bool IsSecure { get; }
public Uri Uri { get; }
public IDictionary<string, string> Metadata { get; }
}
}

View File

@ -13,6 +13,8 @@ using Xunit;
namespace Ocelot.UnitTests.ServiceDiscovery namespace Ocelot.UnitTests.ServiceDiscovery
{ {
using Pivotal.Discovery.Client;
public class ServiceProviderFactoryTests public class ServiceProviderFactoryTests
{ {
private ServiceProviderConfiguration _serviceConfig; private ServiceProviderConfiguration _serviceConfig;
@ -20,13 +22,13 @@ namespace Ocelot.UnitTests.ServiceDiscovery
private readonly ServiceDiscoveryProviderFactory _factory; private readonly ServiceDiscoveryProviderFactory _factory;
private DownstreamReRoute _reRoute; private DownstreamReRoute _reRoute;
private Mock<IOcelotLoggerFactory> _loggerFactory; private Mock<IOcelotLoggerFactory> _loggerFactory;
private IConsulClientFactory _clientFactory; private Mock<IDiscoveryClient> _discoveryClient;
public ServiceProviderFactoryTests() public ServiceProviderFactoryTests()
{ {
_loggerFactory = new Mock<IOcelotLoggerFactory>(); _loggerFactory = new Mock<IOcelotLoggerFactory>();
_clientFactory = new ConsulClientFactory(); _discoveryClient = new Mock<IDiscoveryClient>();
_factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, _clientFactory); _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, new ConsulClientFactory(), _discoveryClient.Object);
} }
[Fact] [Fact]
@ -99,6 +101,24 @@ namespace Ocelot.UnitTests.ServiceDiscovery
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_return_eureka_provider()
{
var reRoute = new DownstreamReRouteBuilder()
.WithServiceName("product")
.WithUseServiceDiscovery(true)
.Build();
var serviceConfig = new ServiceProviderConfigurationBuilder()
.WithType("Eureka")
.Build();
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
.When(x => x.WhenIGetTheServiceProvider())
.Then(x => x.ThenTheServiceProviderIs<EurekaServiceDiscoveryProvider>())
.BDDfy();
}
private void ThenTheFollowingServicesAreReturned(List<DownstreamHostAndPort> downstreamAddresses) private void ThenTheFollowingServicesAreReturned(List<DownstreamHostAndPort> downstreamAddresses)
{ {
var result = (ConfigurationServiceProvider)_result; var result = (ConfigurationServiceProvider)_result;