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.
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 ServiceDiscoveryFinderDelegate Get = (provider, config, name) =>
public static ServiceDiscoveryFinderDelegate Get = (provider, config, reRoute) =>
{
var factory = provider.GetService<IOcelotLoggerFactory>();
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);

View File

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

View File

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

View File

@ -3,24 +3,25 @@ using Microsoft.Extensions.DependencyInjection;
using Ocelot.Logging;
using Ocelot.ServiceDiscovery;
using System;
using Ocelot.Configuration;
namespace Ocelot.Provider.Kubernetes
{
public static class KubernetesProviderFactory
{
public static ServiceDiscoveryFinderDelegate Get = (provider, config, name) =>
public static ServiceDiscoveryFinderDelegate Get = (provider, config, reRoute) =>
{
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 k8sRegistryConfiguration = new KubeRegistryConfiguration()
{
KeyOfServiceInK8s = name,
KubeNamespace = config.Namespace,
KeyOfServiceInK8s = reRoute.ServiceName,
KubeNamespace = string.IsNullOrEmpty(reRoute.ServiceNamespace) ? config.Namespace : reRoute.ServiceNamespace
};
var k8sServiceDiscoveryProvider = new Kube(k8sRegistryConfiguration, factory, kubeClient);

View File

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

View File

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

View File

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

View File

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

View File

@ -4,5 +4,5 @@ namespace Ocelot.ServiceDiscovery
using Providers;
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)
{
return GetServiceDiscoveryProvider(serviceConfig, reRoute.ServiceName);
return GetServiceDiscoveryProvider(serviceConfig, reRoute);
}
var services = new List<Service>();
@ -42,17 +42,17 @@ namespace Ocelot.ServiceDiscovery
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")
{
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));
}
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())
{

View File

@ -1,4 +1,6 @@
namespace Ocelot.UnitTests.Consul
using Ocelot.Configuration.Builder;
namespace Ocelot.UnitTests.Consul
{
using Microsoft.Extensions.DependencyInjection;
using Moq;
@ -29,7 +31,11 @@
[Fact]
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>();
}
@ -37,7 +43,12 @@
public void should_return_PollingConsulServiceDiscoveryProvider()
{
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>();
}
}

View File

@ -27,7 +27,10 @@
var services = new ServiceCollection();
services.AddSingleton<IDiscoveryClient>(client.Object);
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>();
}
}