-
Notifications
You must be signed in to change notification settings - Fork 0
#1 Create Nacos service discovery provider #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
470bee5
643c478
732fec4
7899622
589cf4d
2100626
ffbf1b5
f39757d
30a0f34
0a3f426
306196c
31308ca
1eed642
de2b498
ece0185
71d81bc
c7c0460
d7c7c33
d962f9e
b82594c
6767b6e
4547c55
bea32f4
7912f62
7ac2cae
1a16f83
eec6bad
950c5ae
dd05752
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # This workflow will build a .NET project | ||
| # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net | ||
|
|
||
| name: Main | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ "main" ] | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| matrix: | ||
| dotnet-version: [ '8.0', '9.0' ] | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Setup .NET ${{ matrix.dotnet-version }} | ||
| uses: actions/setup-dotnet@v3 | ||
| with: | ||
| dotnet-version: ${{ matrix.dotnet-version }}.x | ||
| - name: Restore | ||
| run: dotnet restore ./Ocelot.Discovery.Nacos.sln -p:TargetFramework=net${{ matrix.dotnet-version }} | ||
| - name: Build | ||
| run: dotnet build --no-restore ./Ocelot.Discovery.Nacos.sln --framework net${{ matrix.dotnet-version }} | ||
| - name: Unit Tests | ||
| run: dotnet test --no-restore --no-build --verbosity minimal --framework net${{ matrix.dotnet-version }} ./unit/Ocelot.Discovery.Nacos.UnitTests.csproj |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # This workflow will build a .NET project | ||
| # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net | ||
|
|
||
| name: PR | ||
|
|
||
| on: pull_request | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Setup .NET 9.0 | ||
| uses: actions/setup-dotnet@v3 | ||
| with: | ||
| dotnet-version: 9.0.x | ||
| - name: Restore | ||
| run: dotnet restore ./Ocelot.Discovery.Nacos.sln -p:TargetFramework=net9.0 | ||
| - name: Build | ||
| run: dotnet build --no-restore ./Ocelot.Discovery.Nacos.sln --framework net9.0 | ||
| - name: Unit Tests | ||
| run: dotnet test --no-restore --no-build --verbosity normal --framework net9.0 ./unit/Ocelot.Discovery.Nacos.UnitTests.csproj |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -398,3 +398,5 @@ FodyWeavers.xsd | |
|
|
||
| # JetBrains Rider | ||
| *.sln.iml | ||
| .idea/* | ||
| /Ocelot.Discovery.Nacos/.idea | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| | ||
| Microsoft Visual Studio Solution File, Format Version 12.00 | ||
| # Visual Studio Version 17 | ||
| VisualStudioVersion = 17.12.35527.113 | ||
| MinimumVisualStudioVersion = 10.0.40219.1 | ||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ocelot.Discovery.Nacos", "src\Ocelot.Discovery.Nacos.csproj", "{CEF24699-3E41-D971-ACCA-FEF5CCB2011C}" | ||
| EndProject | ||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ocelot.Discovery.Nacos.UnitTests", "unit\Ocelot.Discovery.Nacos.UnitTests.csproj", "{FAD17C0B-4F8F-99A6-1419-9665E4210346}" | ||
| EndProject | ||
| Global | ||
| GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
| Debug|Any CPU = Debug|Any CPU | ||
| Release|Any CPU = Release|Any CPU | ||
| EndGlobalSection | ||
| GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
| {CEF24699-3E41-D971-ACCA-FEF5CCB2011C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| {CEF24699-3E41-D971-ACCA-FEF5CCB2011C}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| {CEF24699-3E41-D971-ACCA-FEF5CCB2011C}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| {CEF24699-3E41-D971-ACCA-FEF5CCB2011C}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| {FAD17C0B-4F8F-99A6-1419-9665E4210346}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| {FAD17C0B-4F8F-99A6-1419-9665E4210346}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| {FAD17C0B-4F8F-99A6-1419-9665E4210346}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| {FAD17C0B-4F8F-99A6-1419-9665E4210346}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| EndGlobalSection | ||
| GlobalSection(SolutionProperties) = preSolution | ||
| HideSolutionNode = FALSE | ||
| EndGlobalSection | ||
| GlobalSection(ExtensibilityGlobals) = postSolution | ||
| SolutionGuid = {0E583663-7152-4575-9AE5-FF3D78AEAA1C} | ||
| EndGlobalSection | ||
| EndGlobal |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,64 @@ | ||||||
| using Ocelot.Values; | ||||||
| using Service = Ocelot.Values.Service; | ||||||
|
|
||||||
| namespace Ocelot.Discovery.Nacos; | ||||||
|
|
||||||
| public class Nacos : IServiceDiscoveryProvider | ||||||
| { | ||||||
| private readonly INacosNamingService _client; | ||||||
| private readonly string _serviceName; | ||||||
|
|
||||||
| public Nacos(string serviceName,INacosNamingService client) | ||||||
| { | ||||||
| _client = client; | ||||||
| _serviceName = serviceName; | ||||||
| } | ||||||
|
|
||||||
| public async Task<List<Service>> GetAsync() | ||||||
| { | ||||||
| try | ||||||
| { | ||||||
| var instances = await _client.GetAllInstances(_serviceName) | ||||||
| .ConfigureAwait(false); | ||||||
|
|
||||||
| return instances? | ||||||
| .Where(i => i.Healthy && i.Enabled && i.Weight > 0) // 健康检查过滤 | ||||||
| .Select(TransformInstance) | ||||||
| .ToList() ?? new List<Service>(); | ||||||
| } | ||||||
| catch (NacosException ex) | ||||||
| { | ||||||
| throw ex; | ||||||
MiaoShuYo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| } | ||||||
| } | ||||||
|
|
||||||
| private Service TransformInstance(Instance instance) | ||||||
| { | ||||||
| var metadata = instance.Metadata ?? new Dictionary<string, string>(); | ||||||
MiaoShuYo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| return new Service( | ||||||
| id: instance.InstanceId, | ||||||
| hostAndPort: new ServiceHostAndPort(instance.Ip, instance.Port), | ||||||
|
||||||
| hostAndPort: new ServiceHostAndPort(instance.Ip, instance.Port), | |
| hostAndPort: new(instance.Ip, instance.Port), |
What is the scheme? Is the scheme known at this stage of building service?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a client fetches a service, it will access the corresponding service based on the address information at the time of registration, so if the registration is for an HTTPS address, then the fetched service is HTTPS, and vice versa.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, in the Nacos service discovery, scheme (protocol) management is not required, right?
For example, in Kubernetes we can manage the protocol while registering a service instance in the K8s registry.
In the next round of development, we will write acceptance tests that will show real usage in practice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot get this default value. Could you show me in Nacos docs please that default version is marked as "default"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The version value is not explicitly defined as "default" in the Nacos documentation. The "default" value in the code is a custom fallback in case there is no "version" key in the metadata dictionary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You confirmed that this is the issue. I am not certain but these values may be applicable: current, latest, default.
Question: How can the default be consumed by Ocelot or Nacos client?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The version can be customized through the configuration file.
MiaoShuYo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
MiaoShuYo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be better to make this definition public.
Could you provide some links to metadata documentation that explain this?
I suggest writing an XML documentation (in the code) for the definition of the array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the nacos java version of the sdk, these are included by default, but in the nacos .net sdk these are not included by default and need to be added by yourself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make this array publicly accessible, then.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| using Ocelot.Configuration.Repository; | ||
| using Ocelot.Middleware; | ||
|
|
||
| namespace Ocelot.Discovery.Nacos; | ||
|
|
||
| public class NacosMiddlewareConfigurationProvider | ||
| { | ||
| public static OcelotMiddlewareConfigurationDelegate Get { get; } = builder => | ||
| { | ||
| var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>(); | ||
| var log =builder.ApplicationServices.GetService<ILogger<NacosMiddlewareConfigurationProvider>>(); | ||
| var config = internalConfigRepo.Get(); | ||
|
Check warning on line 12 in src/NacosMiddlewareConfigurationProvider.cs
|
||
raman-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if (UsingNacosServiceDiscoveryProvider(config.Data)) | ||
| { | ||
| log.LogInformation("Using Nacos service discovery provider."); | ||
|
Check warning on line 16 in src/NacosMiddlewareConfigurationProvider.cs
|
||
| } | ||
|
|
||
| return Task.CompletedTask; | ||
| }; | ||
|
|
||
| private static bool UsingNacosServiceDiscoveryProvider(IInternalConfiguration configuration) | ||
| { | ||
| return configuration?.ServiceProviderConfiguration != null | ||
| && configuration.ServiceProviderConfiguration.Type?.ToLower() == "nacos"; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| using Ocelot.ServiceDiscovery; | ||
|
|
||
| namespace Ocelot.Discovery.Nacos; | ||
|
|
||
| public static class NacosProviderFactory | ||
| { | ||
| /// <summary> | ||
| /// String constant used for provider type definition. | ||
| /// </summary> | ||
| public const string Nacos = nameof(Discovery.Nacos.Nacos); | ||
|
|
||
| public static ServiceDiscoveryFinderDelegate Get { get; } = CreateProvider; | ||
|
|
||
| private static IServiceDiscoveryProvider CreateProvider(IServiceProvider provider, ServiceProviderConfiguration config, DownstreamRoute route) | ||
| { | ||
| var client = provider.GetService<INacosNamingService>(); | ||
| if (client == null) | ||
| { | ||
| throw new NullReferenceException($"Cannot get an {nameof(INacosNamingService)} service during {nameof(CreateProvider)} operation to instantiate the {nameof(Nacos)} provider!"); | ||
| } | ||
|
|
||
| return Nacos.Equals(config.Type, StringComparison.OrdinalIgnoreCase) | ||
|
Check warning on line 22 in src/NacosProviderFactory.cs
|
||
| ? new Nacos(route.ServiceName, client) | ||
| : null; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFrameworks>net8.0;net9.0</TargetFrameworks> | ||
MiaoShuYo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <PackageReference Include="nacos-sdk-csharp" Version="1.3.10" /> | ||
| <PackageReference Include="nacos-sdk-csharp.AspNetCore" Version="1.3.10" /> | ||
| <PackageReference Include="Ocelot" Version="23.4.3" /> | ||
| </ItemGroup> | ||
| </Project> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| using Ocelot.DependencyInjection; | ||
| using Nacos.AspNetCore.V2; | ||
|
|
||
| namespace Ocelot.Discovery.Nacos; | ||
|
|
||
| public static class OcelotBuilderExtensions | ||
| { | ||
| public static IOcelotBuilder AddNacos(this IOcelotBuilder builder, string section = "nacos") | ||
| { | ||
| builder.Services | ||
| .AddNacosAspNet(builder.Configuration,section) | ||
| .AddSingleton(NacosProviderFactory.Get) | ||
| .AddSingleton(NacosMiddlewareConfigurationProvider.Get); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a word of caution! |
||
| return builder; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| global using Microsoft.Extensions.DependencyInjection; | ||
| global using Microsoft.Extensions.Logging; | ||
| global using Ocelot.Configuration; | ||
| global using Ocelot.ServiceDiscovery.Providers; | ||
| global using Nacos.V2; | ||
| global using Nacos.V2.Exceptions; | ||
| global using Nacos.V2.Naming.Dtos; | ||
| global using System.Net; | ||
| global using System.Collections.Generic; | ||
| global using System.Linq; | ||
| global using System.Threading.Tasks; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| [assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
| using Moq; | ||
| using Ocelot.Discovery.Nacos; | ||
| using Ocelot.Configuration; | ||
| using Nacos.V2; | ||
|
|
||
| [TestClass] | ||
| public class NacosProviderFactoryTests | ||
| { | ||
| [TestMethod] | ||
| public void CreateProvider_ShouldReturnNacosProvider_WhenConfigTypeIsNacos() | ||
| { | ||
| // Arrange | ||
| var serviceProviderMock = new Mock<IServiceProvider>(); | ||
| var nacosNamingServiceMock = new Mock<INacosNamingService>(); | ||
| serviceProviderMock.Setup(sp => sp.GetService(typeof(INacosNamingService))) | ||
| .Returns(nacosNamingServiceMock.Object); | ||
|
|
||
| var config = new ServiceProviderConfiguration( | ||
| type: "nacos", | ||
| scheme: "http", | ||
| host: "localhost", | ||
| port: 8848, | ||
| token: null, | ||
| configurationKey: "nacos", | ||
| pollingInterval: 5000 | ||
| ); | ||
|
|
||
| var route = new DownstreamRoute( | ||
| key: "testKey", | ||
| upstreamPathTemplate: null, | ||
| upstreamHeadersFindAndReplace: null, | ||
| downstreamHeadersFindAndReplace: null, | ||
| downstreamAddresses: null, | ||
| serviceName: "testService", | ||
| serviceNamespace: null, | ||
| httpHandlerOptions: null, | ||
| useServiceDiscovery: false, | ||
| enableEndpointEndpointRateLimiting: false, | ||
| qosOptions: null, | ||
| downstreamScheme: null, | ||
| requestIdKey: null, | ||
| isCached: false, | ||
| cacheOptions: null, | ||
| loadBalancerOptions: null, | ||
| rateLimitOptions: null, | ||
| routeClaimsRequirement: null, | ||
| claimsToQueries: null, | ||
| claimsToHeaders: null, | ||
| claimsToClaims: null, | ||
| claimsToPath: null, | ||
| isAuthenticated: false, | ||
| isAuthorized: false, | ||
| authenticationOptions: null, | ||
| downstreamPathTemplate: null, | ||
| loadBalancerKey: null, | ||
| delegatingHandlers: null, | ||
| addHeadersToDownstream: null, | ||
| addHeadersToUpstream: null, | ||
| dangerousAcceptAnyServerCertificateValidator: false, | ||
| securityOptions: null, | ||
| downstreamHttpMethod: null, | ||
| downstreamHttpVersion: null, | ||
| downstreamHttpVersionPolicy: default, | ||
| upstreamHeaders: null, | ||
| metadataOptions: null | ||
| ); | ||
|
|
||
| // Act | ||
| var provider = NacosProviderFactory.Get(serviceProviderMock.Object, config, route); | ||
|
|
||
| // Assert | ||
| Assert.IsNotNull(provider); | ||
| Assert.IsInstanceOfType(provider, typeof(Ocelot.Discovery.Nacos.Nacos)); | ||
| } | ||
|
|
||
| [TestMethod] | ||
| [ExpectedException(typeof(NullReferenceException))] | ||
| public void CreateProvider_ShouldThrowException_WhenNacosNamingServiceIsNull() | ||
| { | ||
| // Arrange | ||
| var serviceProviderMock = new Mock<IServiceProvider>(); | ||
| serviceProviderMock.Setup(sp => sp.GetService(typeof(INacosNamingService))) | ||
| .Returns(null); | ||
|
|
||
| var config = new ServiceProviderConfiguration( | ||
| type: "nacos", | ||
| scheme: "http", | ||
| host: "localhost", | ||
| port: 8848, | ||
| token: null, | ||
| configurationKey: "nacos", | ||
| pollingInterval: 5000 | ||
| ); | ||
|
|
||
| var route = new DownstreamRoute( | ||
| key: "testKey", | ||
| upstreamPathTemplate: null, | ||
| upstreamHeadersFindAndReplace: null, | ||
| downstreamHeadersFindAndReplace: null, | ||
| downstreamAddresses: null, | ||
| serviceName: "testService", | ||
| serviceNamespace: null, | ||
| httpHandlerOptions: null, | ||
| useServiceDiscovery: false, | ||
| enableEndpointEndpointRateLimiting: false, | ||
| qosOptions: null, | ||
| downstreamScheme: null, | ||
| requestIdKey: null, | ||
| isCached: false, | ||
| cacheOptions: null, | ||
| loadBalancerOptions: null, | ||
| rateLimitOptions: null, | ||
| routeClaimsRequirement: null, | ||
| claimsToQueries: null, | ||
| claimsToHeaders: null, | ||
| claimsToClaims: null, | ||
| claimsToPath: null, | ||
| isAuthenticated: false, | ||
| isAuthorized: false, | ||
| authenticationOptions: null, | ||
| downstreamPathTemplate: null, | ||
| loadBalancerKey: null, | ||
| delegatingHandlers: null, | ||
| addHeadersToDownstream: null, | ||
| addHeadersToUpstream: null, | ||
| dangerousAcceptAnyServerCertificateValidator: false, | ||
| securityOptions: null, | ||
| downstreamHttpMethod: null, | ||
| downstreamHttpVersion: null, | ||
| downstreamHttpVersionPolicy: default, | ||
| upstreamHeaders: null, | ||
| metadataOptions: null | ||
| ); | ||
|
|
||
| // Act | ||
| var provider = NacosProviderFactory.Get(serviceProviderMock.Object, config, route); | ||
| // Assert | ||
| Assert.IsNull(provider); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.