Skip to content

Commit

Permalink
Feature/steeltoe (#324)
Browse files Browse the repository at this point in the history
* #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
  • Loading branch information
TomPallister authored Apr 20, 2018
1 parent a5f3e0f commit 4f061f2
Show file tree
Hide file tree
Showing 18 changed files with 665 additions and 72 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio

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

## How to install

Expand Down
35 changes: 34 additions & 1 deletion docs/features/servicediscovery.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,37 @@ If you are using ACL with Consul Ocelot supports adding the X-Consul-Token heade
"Token": "footoken"
}
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.

2 changes: 1 addition & 1 deletion src/Ocelot/DependencyInjection/IOcelotBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public interface IOcelotBuilder

IOcelotBuilder AddSingletonDelegatingHandler<T>(bool global = false)
where T : DelegatingHandler;

IOcelotBuilder AddTransientDelegatingHandler<T>(bool global = false)
where T : DelegatingHandler;

Expand Down
21 changes: 21 additions & 0 deletions src/Ocelot/DependencyInjection/OcelotBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ namespace Ocelot.DependencyInjection
using Ocelot.Infrastructure.Consul;
using Butterfly.Client.Tracing;
using Ocelot.Middleware.Multiplexer;
using Pivotal.Discovery.Client;
using ServiceDiscovery.Providers;

public class OcelotBuilder : IOcelotBuilder
{
Expand Down Expand Up @@ -112,6 +114,17 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo
_services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();
_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
// could maybe use a scoped data repository
_services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Expand Down Expand Up @@ -346,5 +359,13 @@ private List<Client> Client(IIdentityServerConfiguration identityServerConfigura
}
};
}

private static bool UsingEurekaServiceDiscoveryProvider(IConfiguration configurationRoot)
{
var type = configurationRoot.GetValue<string>("GlobalConfiguration:ServiceDiscoveryProvider:Type",
string.Empty);

return type.ToLower() == "eureka";
}
}
}
11 changes: 11 additions & 0 deletions src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Rafty.Concensus;
using Rafty.Infrastructure;
using Ocelot.Middleware.Pipeline;
using Pivotal.Discovery.Client;

public static class OcelotMiddlewareExtensions
{
Expand All @@ -38,6 +39,11 @@ public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder
SetUpRafty(builder);
}

if (UsingEurekaServiceDiscoveryProvider(configuration))
{
builder.UseDiscoveryClient();
}

ConfigureDiagnosticListener(builder);

var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices);
Expand All @@ -63,6 +69,11 @@ rest of asp.net..
return builder;
}

private static bool UsingEurekaServiceDiscoveryProvider(IInternalConfiguration configuration)
{
return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "eureka";
}

private static bool UsingRafty(IApplicationBuilder builder)
{
var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode;
Expand Down
1 change: 1 addition & 0 deletions src/Ocelot/Ocelot.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.2" />
<PackageReference Include="Consul" Version="0.7.2.4" />
<PackageReference Include="Polly" Version="5.8.0" />
<PackageReference Include="Pivotal.Discovery.Client" Version="1.1.0" />
<PackageReference Include="IdentityServer4" Version="2.1.3" />
<PackageReference Include="Rafty" Version="0.4.2" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
27 changes: 27 additions & 0 deletions src/Ocelot/ServiceDiscovery/Providers/FakeEurekaDiscoveryClient.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
19 changes: 14 additions & 5 deletions src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@

namespace Ocelot.ServiceDiscovery
{
using Pivotal.Discovery.Client;

public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory
{
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;
_clientFactory = clientFactory;
_consulFactory = consulFactory;
_eurekaClient = eurekaClient;
}

public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute)
Expand All @@ -40,14 +44,19 @@ public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig,

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);
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);
return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory, _clientFactory);
return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory, _consulFactory);
}
}
}
3 changes: 3 additions & 0 deletions test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
</PropertyGroup>

<ItemGroup>
<None Update="appsettings.product.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
Loading

0 comments on commit 4f061f2

Please sign in to comment.