From 3525fda8ade2aabdab96a08eaa858d2a3fd54ede Mon Sep 17 00:00:00 2001 From: geffzhang Date: Sun, 13 Jan 2019 19:23:12 +0800 Subject: [PATCH] feat: Kubernetes ServiceDiscoveryProvider --- Ocelot.sln | 7 +++ .../IKubeApiClientFactory.cs | 12 ++++ .../KubeApiClientFactory.cs | 22 +++++++ .../KubeProvider.cs | 59 +++++++++++++++++++ .../KubeRegistryConfiguration.cs | 22 +++++++ .../KubernetesProviderFactory.cs | 31 ++++++++++ .../Ocelot.Provider.Kubernetes.csproj | 15 +++++ .../OcelotBuilderExtensions.cs | 20 +++++++ .../ServiceProviderConfigurationBuilder.cs | 9 ++- .../ServiceProviderConfigurationCreator.cs | 2 + .../File/FileServiceDiscoveryProvider.cs | 1 + .../ServiceProviderConfiguration.cs | 10 +++- .../ServiceDiscoveryTests.cs | 3 +- .../ServiceProviderCreatorTests.cs | 7 ++- 14 files changed, 215 insertions(+), 5 deletions(-) create mode 100644 src/Ocelot.Provider.Kubernetes/IKubeApiClientFactory.cs create mode 100644 src/Ocelot.Provider.Kubernetes/KubeApiClientFactory.cs create mode 100644 src/Ocelot.Provider.Kubernetes/KubeProvider.cs create mode 100644 src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs create mode 100644 src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs create mode 100644 src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj create mode 100644 src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs diff --git a/Ocelot.sln b/Ocelot.sln index f4b3aef3..088d7be0 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -56,6 +56,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Rafty", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Tracing.Butterfly", "src\Ocelot.Tracing.Butterfly\Ocelot.Tracing.Butterfly.csproj", "{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ocelot.Provider.Kubernetes", "src\Ocelot.Provider.Kubernetes\Ocelot.Provider.Kubernetes.csproj", "{72C8E528-B4F5-45CE-8A06-CD3787364856}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -114,6 +116,10 @@ Global {6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Debug|Any CPU.Build.0 = Debug|Any CPU {6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Release|Any CPU.ActiveCfg = Release|Any CPU {6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Release|Any CPU.Build.0 = Release|Any CPU + {72C8E528-B4F5-45CE-8A06-CD3787364856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72C8E528-B4F5-45CE-8A06-CD3787364856}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72C8E528-B4F5-45CE-8A06-CD3787364856}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72C8E528-B4F5-45CE-8A06-CD3787364856}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -132,6 +138,7 @@ Global {1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} {AC153C67-EF18-47E6-A230-F0D3CF5F0A98} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} {6045E23D-669C-4F27-AF8E-8EEE6DB3557F} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} + {72C8E528-B4F5-45CE-8A06-CD3787364856} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48} diff --git a/src/Ocelot.Provider.Kubernetes/IKubeApiClientFactory.cs b/src/Ocelot.Provider.Kubernetes/IKubeApiClientFactory.cs new file mode 100644 index 00000000..bd9089d0 --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/IKubeApiClientFactory.cs @@ -0,0 +1,12 @@ +using KubeClient; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.Provider.Kubernetes +{ + public interface IKubeApiClientFactory + { + IKubeApiClient Get(KubeRegistryConfiguration config); + } +} diff --git a/src/Ocelot.Provider.Kubernetes/KubeApiClientFactory.cs b/src/Ocelot.Provider.Kubernetes/KubeApiClientFactory.cs new file mode 100644 index 00000000..fbb832ba --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/KubeApiClientFactory.cs @@ -0,0 +1,22 @@ +using KubeClient; + +namespace Ocelot.Provider.Kubernetes +{ + public class KubeApiClientFactory : IKubeApiClientFactory + { + public IKubeApiClient Get(KubeRegistryConfiguration config) + { + var option = new KubeClientOptions + { + ApiEndPoint = config.ApiEndPoint + }; + if(!string.IsNullOrEmpty(config?.AccessToken)) + { + option.AccessToken = config.AccessToken; + option.AuthStrategy = config.AuthStrategy; + option.AllowInsecure = config.AllowInsecure; + } + return KubeApiClient.Create(option); + } + } +} diff --git a/src/Ocelot.Provider.Kubernetes/KubeProvider.cs b/src/Ocelot.Provider.Kubernetes/KubeProvider.cs new file mode 100644 index 00000000..72c627de --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/KubeProvider.cs @@ -0,0 +1,59 @@ +using KubeClient; +using KubeClient.Models; +using Ocelot.Logging; +using Ocelot.ServiceDiscovery.Providers; +using Ocelot.Values; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Provider.Kubernetes +{ + public class KubeProvider : IServiceDiscoveryProvider + { + private KubeRegistryConfiguration kubeRegistryConfiguration; + private IOcelotLoggerFactory factory; + private IKubeApiClient kubeApi; + private IKubeApiClientFactory kubeClientFactory; + + public KubeProvider(KubeRegistryConfiguration kubeRegistryConfiguration, IOcelotLoggerFactory factory, IKubeApiClientFactory kubeClientFactory) + { + this.kubeRegistryConfiguration = kubeRegistryConfiguration; + this.factory = factory; + this.kubeApi = kubeClientFactory.Get(kubeRegistryConfiguration); + } + + public async Task> Get() + { + var service = await kubeApi.ServicesV1().Get(kubeRegistryConfiguration.KeyOfServiceInK8s, kubeRegistryConfiguration.KubeNamespace); + var services = new List(); + if (IsValid(service)) + { + services.Add(BuildService(service)); + } + return services; + } + + private bool IsValid(ServiceV1 service) + { + if (string.IsNullOrEmpty(service.Spec.ClusterIP) || service.Spec.Ports.Count <= 0) + { + return false; + } + + return true; + } + + private Service BuildService(ServiceV1 serviceEntry) + { + var servicePort = serviceEntry.Spec.Ports.FirstOrDefault(); + return new Service( + serviceEntry.Metadata.Name, + new ServiceHostAndPort(serviceEntry.Spec.ClusterIP, servicePort.Port), + serviceEntry.Metadata.Uid, + string.Empty, + Enumerable.Empty()); + } + } +} diff --git a/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs b/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs new file mode 100644 index 00000000..929c6e32 --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs @@ -0,0 +1,22 @@ +using KubeClient; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.Provider.Kubernetes +{ + public class KubeRegistryConfiguration + { + public Uri ApiEndPoint { get; set; } + + public string KubeNamespace { get; set; } + + public string KeyOfServiceInK8s { get; set; } + + public KubeAuthStrategy AuthStrategy { get; set; } + + public string AccessToken { get; set; } + + public bool AllowInsecure { get; set; } + } +} diff --git a/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs b/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs new file mode 100644 index 00000000..528a9115 --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs @@ -0,0 +1,31 @@ +using KubeClient; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Logging; +using Ocelot.ServiceDiscovery; +using System; + +namespace Ocelot.Provider.Kubernetes +{ + public static class KubernetesProviderFactory + { + public static ServiceDiscoveryFinderDelegate Get = (provider, config, name) => + { + var factory = provider.GetService(); + + var kubeClientFactory = provider.GetService(); + + var k8sRegistryConfiguration = new KubeRegistryConfiguration() { + ApiEndPoint = new Uri($"http://{config.Host}:{config.Port}"), + KeyOfServiceInK8s = name, + KubeNamespace = config.Namesapce, + AuthStrategy = KubeAuthStrategy.BearerToken, + AccessToken = config.Token, + AllowInsecure = true // Don't validate server certificate + }; + + var k8sServiceDiscoveryProvider = new KubeProvider(k8sRegistryConfiguration, factory, kubeClientFactory); + + return k8sServiceDiscoveryProvider; + }; + } +} diff --git a/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj b/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj new file mode 100644 index 00000000..b97b8c82 --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + + + + + + + + + + + diff --git a/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs b/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs new file mode 100644 index 00000000..d206a6c7 --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs @@ -0,0 +1,20 @@ +using KubeClient; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.DependencyInjection; +using Ocelot.ServiceDiscovery; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.Provider.Kubernetes +{ + public static class OcelotBuilderExtensions + { + public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder) + { + builder.Services.AddSingleton(KubernetesProviderFactory.Get); + builder.Services.AddSingleton(); + return builder; + } + } +} diff --git a/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs b/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs index 1ea18067..e9cc947f 100644 --- a/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs @@ -8,6 +8,7 @@ namespace Ocelot.Configuration.Builder private string _token; private string _configurationKey; private int _pollingInterval; + private string _namespace; public ServiceProviderConfigurationBuilder WithHost(string serviceDiscoveryProviderHost) { @@ -45,9 +46,15 @@ namespace Ocelot.Configuration.Builder return this; } + public ServiceProviderConfigurationBuilder WithNamesapce(string @namesapce) + { + _namespace = @namesapce; + return this; + } + public ServiceProviderConfiguration Build() { - return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort, _token, _configurationKey, _pollingInterval); + return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort, _token, _configurationKey, _pollingInterval, _namespace); } } } diff --git a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs index a236fb22..88137a8b 100644 --- a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs @@ -13,6 +13,7 @@ namespace Ocelot.Configuration.Creator ? globalConfiguration?.ServiceDiscoveryProvider?.Type : "consul"; var pollingInterval = globalConfiguration?.ServiceDiscoveryProvider?.PollingInterval ?? 0; + var k8snamesapce = globalConfiguration?.ServiceDiscoveryProvider?.Namespace ?? string.Empty; return new ServiceProviderConfigurationBuilder() .WithHost(host) @@ -21,6 +22,7 @@ namespace Ocelot.Configuration.Creator .WithToken(globalConfiguration?.ServiceDiscoveryProvider?.Token) .WithConfigurationKey(globalConfiguration?.ServiceDiscoveryProvider?.ConfigurationKey) .WithPollingInterval(pollingInterval) + .WithNamesapce(k8snamesapce) .Build(); } } diff --git a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs index e6edeee7..153145bd 100644 --- a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs +++ b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs @@ -8,5 +8,6 @@ namespace Ocelot.Configuration.File public string Token { get; set; } public string ConfigurationKey { get; set; } public int PollingInterval { get; set; } + public string Namespace { get; set; } } } diff --git a/src/Ocelot/Configuration/ServiceProviderConfiguration.cs b/src/Ocelot/Configuration/ServiceProviderConfiguration.cs index 72c3abfb..05ee07d4 100644 --- a/src/Ocelot/Configuration/ServiceProviderConfiguration.cs +++ b/src/Ocelot/Configuration/ServiceProviderConfiguration.cs @@ -2,7 +2,7 @@ { public class ServiceProviderConfiguration { - public ServiceProviderConfiguration(string type, string host, int port, string token, string configurationKey, int pollingInterval) + public ServiceProviderConfiguration(string type, string host, int port, string token, string configurationKey, int pollingInterval, string @namespace = "") { ConfigurationKey = configurationKey; Host = host; @@ -10,13 +10,21 @@ Token = token; Type = type; PollingInterval = pollingInterval; + Namesapce = @namespace; } public string Host { get; } + public int Port { get; } + public string Type { get; } + public string Token { get; } + public string ConfigurationKey { get; } + public int PollingInterval { get; } + + public string Namesapce { get; } } } diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index 4bdf014b..d93e6911 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -434,7 +434,8 @@ Host = "localhost", Port = consulPort, Type = "PollConsul", - PollingInterval = 0 + PollingInterval = 0, + Namespace = string.Empty } } }; diff --git a/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs index f1ec3a07..fcf0c52a 100644 --- a/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs @@ -30,7 +30,8 @@ namespace Ocelot.UnitTests.Configuration Port = 1234, Type = "ServiceFabric", Token = "testtoken", - ConfigurationKey = "woo" + ConfigurationKey = "woo", + Namespace ="default" } }; @@ -40,13 +41,14 @@ namespace Ocelot.UnitTests.Configuration .WithType("ServiceFabric") .WithToken("testtoken") .WithConfigurationKey("woo") + .WithNamesapce("default") .Build(); this.Given(x => x.GivenTheFollowingGlobalConfig(globalConfig)) .When(x => x.WhenICreate()) .Then(x => x.ThenTheConfigIs(expected)) .BDDfy(); - } + } private void GivenTheFollowingGlobalConfig(FileGlobalConfiguration fileGlobalConfig) { @@ -64,6 +66,7 @@ namespace Ocelot.UnitTests.Configuration _result.Port.ShouldBe(expected.Port); _result.Token.ShouldBe(expected.Token); _result.Type.ShouldBe(expected.Type); + _result.Namesapce.ShouldBe(expected.Namesapce); _result.ConfigurationKey.ShouldBe(expected.ConfigurationKey); } }