Fix issue #936: Kubernetes service discovery provider doesn't allow cross-namespace discovery (#938)

* Allow default k8s namespace to be overridden

* Add ServiceNamespace to ReRoute configuration

* Remove debug comments

* Update unit tests

* Unit tests (Eureka)

* Update docs

* Re-run build
This commit is contained in:
Jason Morton 2019-06-25 04:08:18 +01:00 committed by Thiago Loureiro
parent 959a92ec6a
commit e1d7f28951
13 changed files with 65 additions and 20 deletions

View File

@ -75,3 +75,21 @@ The polling interval is in milliseconds and tells Ocelot how often to call kuber
Please note there are tradeoffs here. If you poll kubernetes it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volatile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling kubernetes per request. Please note there are tradeoffs here. If you poll kubernetes it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volatile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling kubernetes per request.
There is no way for Ocelot to work these out for you. There is no way for Ocelot to work these out for you.
If your downstream service resides in a different namespace you can override the global setting at the ReRoute level by specifying a ServiceNamespace
.. code-block:: json
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/values",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/values",
"ServiceName": "downstreamservice",
"ServiceNamespace": "downstream-namespace",
"UpstreamHttpMethod": [ "Get" ]
}
]
}

View File

@ -6,13 +6,13 @@
public static class ConsulProviderFactory public static class ConsulProviderFactory
{ {
public static ServiceDiscoveryFinderDelegate Get = (provider, config, name) => public static ServiceDiscoveryFinderDelegate Get = (provider, config, reRoute) =>
{ {
var factory = provider.GetService<IOcelotLoggerFactory>(); var factory = provider.GetService<IOcelotLoggerFactory>();
var consulFactory = provider.GetService<IConsulClientFactory>(); var consulFactory = provider.GetService<IConsulClientFactory>();
var consulRegistryConfiguration = new ConsulRegistryConfiguration(config.Host, config.Port, name, config.Token); var consulRegistryConfiguration = new ConsulRegistryConfiguration(config.Host, config.Port, reRoute.ServiceName, config.Token);
var consulServiceDiscoveryProvider = new Consul(consulRegistryConfiguration, factory, consulFactory); var consulServiceDiscoveryProvider = new Consul(consulRegistryConfiguration, factory, consulFactory);

View File

@ -6,13 +6,13 @@
public static class EurekaProviderFactory public static class EurekaProviderFactory
{ {
public static ServiceDiscoveryFinderDelegate Get = (provider, config, name) => public static ServiceDiscoveryFinderDelegate Get = (provider, config, reRoute) =>
{ {
var client = provider.GetService<IDiscoveryClient>(); var client = provider.GetService<IDiscoveryClient>();
if (config.Type?.ToLower() == "eureka" && client != null) if (config.Type?.ToLower() == "eureka" && client != null)
{ {
return new Eureka(name, client); return new Eureka(reRoute.ServiceName, client);
} }
return null; return null;

View File

@ -24,8 +24,7 @@ namespace Ocelot.Provider.Kubernetes
public async Task<List<Service>> Get() public async Task<List<Service>> Get()
{ {
var service = await kubeApi.ServicesV1() var service = await kubeApi.ServicesV1().Get(kubeRegistryConfiguration.KeyOfServiceInK8s, kubeRegistryConfiguration.KubeNamespace);
.Get(kubeRegistryConfiguration.KeyOfServiceInK8s, kubeRegistryConfiguration.KubeNamespace);
var services = new List<Service>(); var services = new List<Service>();
if (IsValid(service)) if (IsValid(service))
{ {

View File

@ -3,24 +3,25 @@ using Microsoft.Extensions.DependencyInjection;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
using System; using System;
using Ocelot.Configuration;
namespace Ocelot.Provider.Kubernetes namespace Ocelot.Provider.Kubernetes
{ {
public static class KubernetesProviderFactory public static class KubernetesProviderFactory
{ {
public static ServiceDiscoveryFinderDelegate Get = (provider, config, name) => public static ServiceDiscoveryFinderDelegate Get = (provider, config, reRoute) =>
{ {
var factory = provider.GetService<IOcelotLoggerFactory>(); var factory = provider.GetService<IOcelotLoggerFactory>();
return GetkubeProvider(provider, config, name, factory); return GetkubeProvider(provider, config, reRoute, factory);
}; };
private static ServiceDiscovery.Providers.IServiceDiscoveryProvider GetkubeProvider(IServiceProvider provider, Configuration.ServiceProviderConfiguration config, string name, IOcelotLoggerFactory factory) private static ServiceDiscovery.Providers.IServiceDiscoveryProvider GetkubeProvider(IServiceProvider provider, Configuration.ServiceProviderConfiguration config, DownstreamReRoute reRoute, IOcelotLoggerFactory factory)
{ {
var kubeClient = provider.GetService<IKubeApiClient>(); var kubeClient = provider.GetService<IKubeApiClient>();
var k8sRegistryConfiguration = new KubeRegistryConfiguration() var k8sRegistryConfiguration = new KubeRegistryConfiguration()
{ {
KeyOfServiceInK8s = name, KeyOfServiceInK8s = reRoute.ServiceName,
KubeNamespace = config.Namespace, KubeNamespace = string.IsNullOrEmpty(reRoute.ServiceNamespace) ? config.Namespace : reRoute.ServiceNamespace
}; };
var k8sServiceDiscoveryProvider = new Kube(k8sRegistryConfiguration, factory, kubeClient); var k8sServiceDiscoveryProvider = new Kube(k8sRegistryConfiguration, factory, kubeClient);

View File

@ -30,6 +30,7 @@ namespace Ocelot.Configuration.Builder
private RateLimitOptions _rateLimitOptions; private RateLimitOptions _rateLimitOptions;
private bool _useServiceDiscovery; private bool _useServiceDiscovery;
private string _serviceName; private string _serviceName;
private string _serviceNamespace;
private List<HeaderFindAndReplace> _upstreamHeaderFindAndReplace; private List<HeaderFindAndReplace> _upstreamHeaderFindAndReplace;
private List<HeaderFindAndReplace> _downstreamHeaderFindAndReplace; private List<HeaderFindAndReplace> _downstreamHeaderFindAndReplace;
private readonly List<DownstreamHostAndPort> _downstreamAddresses; private readonly List<DownstreamHostAndPort> _downstreamAddresses;
@ -186,6 +187,12 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public DownstreamReRouteBuilder WithServiceNamespace(string serviceNamespace)
{
_serviceNamespace = serviceNamespace;
return this;
}
public DownstreamReRouteBuilder WithUpstreamHeaderFindAndReplace(List<HeaderFindAndReplace> upstreamHeaderFindAndReplace) public DownstreamReRouteBuilder WithUpstreamHeaderFindAndReplace(List<HeaderFindAndReplace> upstreamHeaderFindAndReplace)
{ {
_upstreamHeaderFindAndReplace = upstreamHeaderFindAndReplace; _upstreamHeaderFindAndReplace = upstreamHeaderFindAndReplace;
@ -243,6 +250,7 @@ namespace Ocelot.Configuration.Builder
_downstreamHeaderFindAndReplace, _downstreamHeaderFindAndReplace,
_downstreamAddresses, _downstreamAddresses,
_serviceName, _serviceName,
_serviceNamespace,
_httpHandlerOptions, _httpHandlerOptions,
_useServiceDiscovery, _useServiceDiscovery,
_enableRateLimiting, _enableRateLimiting,

View File

@ -126,6 +126,7 @@ namespace Ocelot.Configuration.Creator
.WithRateLimitOptions(rateLimitOption) .WithRateLimitOptions(rateLimitOption)
.WithHttpHandlerOptions(httpHandlerOptions) .WithHttpHandlerOptions(httpHandlerOptions)
.WithServiceName(fileReRoute.ServiceName) .WithServiceName(fileReRoute.ServiceName)
.WithServiceNamespace(fileReRoute.ServiceNamespace)
.WithUseServiceDiscovery(fileReRouteOptions.UseServiceDiscovery) .WithUseServiceDiscovery(fileReRouteOptions.UseServiceDiscovery)
.WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream)
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)

View File

@ -13,6 +13,7 @@ namespace Ocelot.Configuration
List<HeaderFindAndReplace> downstreamHeadersFindAndReplace, List<HeaderFindAndReplace> downstreamHeadersFindAndReplace,
List<DownstreamHostAndPort> downstreamAddresses, List<DownstreamHostAndPort> downstreamAddresses,
string serviceName, string serviceName,
string serviceNamespace,
HttpHandlerOptions httpHandlerOptions, HttpHandlerOptions httpHandlerOptions,
bool useServiceDiscovery, bool useServiceDiscovery,
bool enableEndpointEndpointRateLimiting, bool enableEndpointEndpointRateLimiting,
@ -47,6 +48,7 @@ namespace Ocelot.Configuration
DownstreamHeadersFindAndReplace = downstreamHeadersFindAndReplace ?? new List<HeaderFindAndReplace>(); DownstreamHeadersFindAndReplace = downstreamHeadersFindAndReplace ?? new List<HeaderFindAndReplace>();
DownstreamAddresses = downstreamAddresses ?? new List<DownstreamHostAndPort>(); DownstreamAddresses = downstreamAddresses ?? new List<DownstreamHostAndPort>();
ServiceName = serviceName; ServiceName = serviceName;
ServiceNamespace = serviceNamespace;
HttpHandlerOptions = httpHandlerOptions; HttpHandlerOptions = httpHandlerOptions;
UseServiceDiscovery = useServiceDiscovery; UseServiceDiscovery = useServiceDiscovery;
EnableEndpointEndpointRateLimiting = enableEndpointEndpointRateLimiting; EnableEndpointEndpointRateLimiting = enableEndpointEndpointRateLimiting;
@ -76,6 +78,7 @@ namespace Ocelot.Configuration
public List<HeaderFindAndReplace> DownstreamHeadersFindAndReplace { get; } public List<HeaderFindAndReplace> DownstreamHeadersFindAndReplace { get; }
public List<DownstreamHostAndPort> DownstreamAddresses { get; } public List<DownstreamHostAndPort> DownstreamAddresses { get; }
public string ServiceName { get; } public string ServiceName { get; }
public string ServiceNamespace { get; }
public HttpHandlerOptions HttpHandlerOptions { get; } public HttpHandlerOptions HttpHandlerOptions { get; }
public bool UseServiceDiscovery { get; } public bool UseServiceDiscovery { get; }
public bool EnableEndpointEndpointRateLimiting { get; } public bool EnableEndpointEndpointRateLimiting { get; }

View File

@ -38,6 +38,7 @@ namespace Ocelot.Configuration.File
public FileCacheOptions FileCacheOptions { get; set; } public FileCacheOptions FileCacheOptions { get; set; }
public bool ReRouteIsCaseSensitive { get; set; } public bool ReRouteIsCaseSensitive { get; set; }
public string ServiceName { get; set; } public string ServiceName { get; set; }
public string ServiceNamespace { get; set; }
public string DownstreamScheme { get; set; } public string DownstreamScheme { get; set; }
public FileQoSOptions QoSOptions { get; set; } public FileQoSOptions QoSOptions { get; set; }
public FileLoadBalancerOptions LoadBalancerOptions { get; set; } public FileLoadBalancerOptions LoadBalancerOptions { get; set; }

View File

@ -4,5 +4,5 @@ namespace Ocelot.ServiceDiscovery
using Providers; using Providers;
using System; using System;
public delegate IServiceDiscoveryProvider ServiceDiscoveryFinderDelegate(IServiceProvider provider, ServiceProviderConfiguration config, string key); public delegate IServiceDiscoveryProvider ServiceDiscoveryFinderDelegate(IServiceProvider provider, ServiceProviderConfiguration config, DownstreamReRoute reRoute);
} }

View File

@ -27,7 +27,7 @@ namespace Ocelot.ServiceDiscovery
{ {
if (reRoute.UseServiceDiscovery) if (reRoute.UseServiceDiscovery)
{ {
return GetServiceDiscoveryProvider(serviceConfig, reRoute.ServiceName); return GetServiceDiscoveryProvider(serviceConfig, reRoute);
} }
var services = new List<Service>(); var services = new List<Service>();
@ -42,17 +42,17 @@ namespace Ocelot.ServiceDiscovery
return new OkResponse<IServiceDiscoveryProvider>(new ConfigurationServiceProvider(services)); return new OkResponse<IServiceDiscoveryProvider>(new ConfigurationServiceProvider(services));
} }
private Response<IServiceDiscoveryProvider> GetServiceDiscoveryProvider(ServiceProviderConfiguration config, string key) private Response<IServiceDiscoveryProvider> GetServiceDiscoveryProvider(ServiceProviderConfiguration config, DownstreamReRoute reRoute)
{ {
if (config.Type?.ToLower() == "servicefabric") if (config.Type?.ToLower() == "servicefabric")
{ {
var sfConfig = new ServiceFabricConfiguration(config.Host, config.Port, key); var sfConfig = new ServiceFabricConfiguration(config.Host, config.Port, reRoute.ServiceName);
return new OkResponse<IServiceDiscoveryProvider>(new ServiceFabricServiceDiscoveryProvider(sfConfig)); return new OkResponse<IServiceDiscoveryProvider>(new ServiceFabricServiceDiscoveryProvider(sfConfig));
} }
if (_delegates != null) if (_delegates != null)
{ {
var provider = _delegates?.Invoke(_provider, config, key); var provider = _delegates?.Invoke(_provider, config, reRoute);
if (provider.GetType().Name.ToLower() == config.Type.ToLower()) if (provider.GetType().Name.ToLower() == config.Type.ToLower())
{ {

View File

@ -1,4 +1,6 @@
namespace Ocelot.UnitTests.Consul using Ocelot.Configuration.Builder;
namespace Ocelot.UnitTests.Consul
{ {
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Moq; using Moq;
@ -29,7 +31,11 @@
[Fact] [Fact]
public void should_return_ConsulServiceDiscoveryProvider() public void should_return_ConsulServiceDiscoveryProvider()
{ {
var provider = ConsulProviderFactory.Get(_provider, new ServiceProviderConfiguration("", "", 1, "", "", 1), ""); var reRoute = new DownstreamReRouteBuilder()
.WithServiceName("")
.Build();
var provider = ConsulProviderFactory.Get(_provider, new ServiceProviderConfiguration("", "", 1, "", "", 1), reRoute);
provider.ShouldBeOfType<Consul>(); provider.ShouldBeOfType<Consul>();
} }
@ -37,7 +43,12 @@
public void should_return_PollingConsulServiceDiscoveryProvider() public void should_return_PollingConsulServiceDiscoveryProvider()
{ {
var stopsPollerFromPolling = 10000; var stopsPollerFromPolling = 10000;
var provider = ConsulProviderFactory.Get(_provider, new ServiceProviderConfiguration("pollconsul", "", 1, "", "", stopsPollerFromPolling), "");
var reRoute = new DownstreamReRouteBuilder()
.WithServiceName("")
.Build();
var provider = ConsulProviderFactory.Get(_provider, new ServiceProviderConfiguration("pollconsul", "", 1, "", "", stopsPollerFromPolling), reRoute);
provider.ShouldBeOfType<PollConsul>(); provider.ShouldBeOfType<PollConsul>();
} }
} }

View File

@ -27,7 +27,10 @@
var services = new ServiceCollection(); var services = new ServiceCollection();
services.AddSingleton<IDiscoveryClient>(client.Object); services.AddSingleton<IDiscoveryClient>(client.Object);
var sp = services.BuildServiceProvider(); var sp = services.BuildServiceProvider();
var provider = EurekaProviderFactory.Get(sp, config, null); var reRoute = new DownstreamReRouteBuilder()
.WithServiceName("")
.Build();
var provider = EurekaProviderFactory.Get(sp, config, reRoute);
provider.ShouldBeOfType<Eureka>(); provider.ShouldBeOfType<Eureka>();
} }
} }