diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderFactory.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderFactory.cs index ef593a7340c..6b5a0e00d58 100644 --- a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderFactory.cs +++ b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderFactory.cs @@ -41,15 +41,15 @@ public bool TryCreateProvider(ServiceEndpointQuery query, [NotNullWhen(true)] ou var srvQuery = optionsValue.ServiceDomainNameCallback != null ? optionsValue.ServiceDomainNameCallback(query) - : DefaultServiceDomainNameCallback(query, optionsValue); + : DefaultServiceDomainNameCallback(query, _querySuffix!); provider = new DnsSrvServiceEndpointProvider(query, srvQuery, hostName: query.ServiceName, options, logger, resolver, timeProvider); return true; } - private static string DefaultServiceDomainNameCallback(ServiceEndpointQuery query, DnsSrvServiceEndpointProviderOptions options) + private static string DefaultServiceDomainNameCallback(ServiceEndpointQuery query, string querySuffix) { var portName = query.EndpointName ?? "default"; - return $"_{portName}._tcp.{query.ServiceName}.{options.QuerySuffix}"; + return $"_{portName}._tcp.{query.ServiceName}.{querySuffix}"; } private static string? GetKubernetesHostDomain() diff --git a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsSrvServiceEndpointResolverTests.cs b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsSrvServiceEndpointResolverTests.cs index ec21bf9fa9c..8a9c01f9fb7 100644 --- a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsSrvServiceEndpointResolverTests.cs +++ b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsSrvServiceEndpointResolverTests.cs @@ -33,6 +33,86 @@ private sealed class FakeDnsResolver : IDnsResolver public ValueTask ResolveServiceAsync(string name, CancellationToken cancellationToken = default) => ResolveServiceAsyncFunc!.Invoke(name, cancellationToken); } + /// + /// Regression test for https://github.com/dotnet/extensions/issues/7175: the query suffix used to build the SRV + /// query must be normalized (leading dot trimmed) before composing the SRV query. + /// + [Theory] + [InlineData(".ns")] + [InlineData("ns")] + public async Task ResolveServiceEndpoint_DnsSrv_UsesQuerySuffix(string querySuffix) + { + string? srvQuery = null; + var dnsClientMock = new FakeDnsResolver + { + ResolveServiceAsyncFunc = (name, cancellationToken) => + { + srvQuery = name; + ServiceResult[] response = [ + new ServiceResult(DateTime.UtcNow.AddSeconds(60), 99, 66, 8888, "srv-a", [new AddressResult(DateTime.UtcNow.AddSeconds(64), IPAddress.Parse("10.10.10.10"))]) + ]; + + return ValueTask.FromResult(response); + } + }; + var services = new ServiceCollection() + .AddSingleton(dnsClientMock) + .AddServiceDiscoveryCore() + .AddDnsSrvServiceEndpointProvider(options => options.QuerySuffix = querySuffix) + .BuildServiceProvider(); + var watcherFactory = services.GetRequiredService(); + ServiceEndpointWatcher watcher; + await using ((watcher = watcherFactory.CreateWatcher("http://basket")).ConfigureAwait(false)) + { + var tcs = new TaskCompletionSource(); + watcher.OnEndpointsUpdated = tcs.SetResult; + watcher.Start(); + var initialResult = await tcs.Task; + Assert.True(initialResult.ResolvedSuccessfully); + } + + Assert.Equal("_default._tcp.basket.ns", srvQuery); + } + + [Fact] + public async Task ResolveServiceEndpoint_DnsSrv_ServiceDomainNameCallbackTakesPrecedenceOverQuerySuffix() + { + string? srvQuery = null; + var dnsClientMock = new FakeDnsResolver + { + ResolveServiceAsyncFunc = (name, cancellationToken) => + { + srvQuery = name; + ServiceResult[] response = [ + new ServiceResult(DateTime.UtcNow.AddSeconds(60), 99, 66, 8888, "srv-a", [new AddressResult(DateTime.UtcNow.AddSeconds(64), IPAddress.Parse("10.10.10.10"))]) + ]; + + return ValueTask.FromResult(response); + } + }; + var services = new ServiceCollection() + .AddSingleton(dnsClientMock) + .AddServiceDiscoveryCore() + .AddDnsSrvServiceEndpointProvider(options => + { + options.QuerySuffix = ".ns"; + options.ServiceDomainNameCallback = query => $"{query.ServiceName}.service.consul"; + }) + .BuildServiceProvider(); + var watcherFactory = services.GetRequiredService(); + ServiceEndpointWatcher watcher; + await using ((watcher = watcherFactory.CreateWatcher("http://basket")).ConfigureAwait(false)) + { + var tcs = new TaskCompletionSource(); + watcher.OnEndpointsUpdated = tcs.SetResult; + watcher.Start(); + var initialResult = await tcs.Task; + Assert.True(initialResult.ResolvedSuccessfully); + } + + Assert.Equal("basket.service.consul", srvQuery); + } + [Fact] public async Task ResolveServiceEndpoint_DnsSrv() {