diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsResolverOptions.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsResolverOptions.cs
new file mode 100644
index 00000000000..0cc256bd3c3
--- /dev/null
+++ b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsResolverOptions.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Net;
+
+namespace Microsoft.Extensions.ServiceDiscovery.Dns;
+
+///
+/// Provides configuration options for DNS resolution, including server endpoints, retry attempts, and timeout settings.
+///
+public class DnsResolverOptions
+{
+ ///
+ /// Gets or sets the collection of server endpoints used for network connections.
+ ///
+ public IList Servers { get; set; } = new List();
+
+ ///
+ /// Gets or sets the maximum number of attempts per server.
+ ///
+ public int MaxAttempts { get; set; } = 2;
+
+ ///
+ /// Gets or sets the maximum duration per attempt to wait before timing out.
+ ///
+ ///
+ /// The maximum time for resolving a query is * count * .
+ ///
+ public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(3);
+
+ // override for testing purposes
+ internal Func, int, int>? _transportOverride;
+}
diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsResolverOptionsValidator.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsResolverOptionsValidator.cs
new file mode 100644
index 00000000000..d61da5e5e0b
--- /dev/null
+++ b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsResolverOptionsValidator.cs
@@ -0,0 +1,41 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.ServiceDiscovery.Dns;
+
+internal sealed class DnsResolverOptionsValidator : IValidateOptions
+{
+ // CancellationTokenSource.CancelAfter has a maximum timeout of Int32.MaxValue milliseconds.
+ private static readonly TimeSpan s_maxTimeout = TimeSpan.FromMilliseconds(int.MaxValue);
+
+ public ValidateOptionsResult Validate(string? name, DnsResolverOptions options)
+ {
+ if (options.Servers is null)
+ {
+ return ValidateOptionsResult.Fail($"{nameof(options.Servers)} must not be null.");
+ }
+
+ if (options.MaxAttempts < 1)
+ {
+ return ValidateOptionsResult.Fail($"{nameof(options.MaxAttempts)} must be one or greater.");
+ }
+
+ if (options.Timeout != Timeout.InfiniteTimeSpan)
+ {
+ if (options.Timeout <= TimeSpan.Zero)
+ {
+ return ValidateOptionsResult.Fail($"{nameof(options.Timeout)} must not be negative or zero.");
+ }
+
+ if (options.Timeout > s_maxTimeout)
+ {
+ return ValidateOptionsResult.Fail($"{nameof(options.Timeout)} must not be greater than {s_maxTimeout.TotalMilliseconds} milliseconds.");
+ }
+ }
+
+ return ValidateOptionsResult.Success;
+ }
+}
diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderFactory.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderFactory.cs
index 57820560a63..ef593a7340c 100644
--- a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderFactory.cs
+++ b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderFactory.cs
@@ -22,6 +22,8 @@ internal sealed partial class DnsSrvServiceEndpointProviderFactory(
///
public bool TryCreateProvider(ServiceEndpointQuery query, [NotNullWhen(true)] out IServiceEndpointProvider? provider)
{
+ var optionsValue = options.CurrentValue;
+
// If a default namespace is not specified, then this provider will attempt to infer the namespace from the service name, but only when running inside Kubernetes.
// Kubernetes DNS spec: https://github.com/kubernetes/dns/blob/master/docs/specification.md
// SRV records are available for headless services with named ports.
@@ -30,19 +32,26 @@ public bool TryCreateProvider(ServiceEndpointQuery query, [NotNullWhen(true)] ou
// Otherwise, the namespace can be read from /var/run/secrets/kubernetes.io/serviceaccount/namespace and combined with an assumed suffix of "svc.cluster.local".
// The protocol is assumed to be "tcp".
// The portName is the name of the port in the service definition. If the serviceName parses as a URI, we use the scheme as the port name, otherwise "default".
- if (string.IsNullOrWhiteSpace(_querySuffix))
+ if (optionsValue.ServiceDomainNameCallback == null && string.IsNullOrWhiteSpace(_querySuffix))
{
DnsServiceEndpointProviderBase.Log.NoDnsSuffixFound(logger, query.ToString()!);
provider = default;
return false;
}
- var portName = query.EndpointName ?? "default";
- var srvQuery = $"_{portName}._tcp.{query.ServiceName}.{_querySuffix}";
+ var srvQuery = optionsValue.ServiceDomainNameCallback != null
+ ? optionsValue.ServiceDomainNameCallback(query)
+ : DefaultServiceDomainNameCallback(query, optionsValue);
provider = new DnsSrvServiceEndpointProvider(query, srvQuery, hostName: query.ServiceName, options, logger, resolver, timeProvider);
return true;
}
+ private static string DefaultServiceDomainNameCallback(ServiceEndpointQuery query, DnsSrvServiceEndpointProviderOptions options)
+ {
+ var portName = query.EndpointName ?? "default";
+ return $"_{portName}._tcp.{query.ServiceName}.{options.QuerySuffix}";
+ }
+
private static string? GetKubernetesHostDomain()
{
// Check that we are running in Kubernetes first.
diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderOptions.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderOptions.cs
index c908c56d770..c1d64136cc9 100644
--- a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderOptions.cs
+++ b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderOptions.cs
@@ -36,6 +36,11 @@ public class DnsSrvServiceEndpointProviderOptions
///
public string? QuerySuffix { get; set; }
+ ///
+ /// Gets or sets a delegate that generates a DNS SRV query from a specified instance.
+ ///
+ public Func? ServiceDomainNameCallback { get; set; }
+
///
/// Gets or sets a delegate used to determine whether to apply host name metadata to each resolved endpoint. Defaults to false.
///
diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/DnsResolver.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/DnsResolver.cs
index bc290c6b907..511e8fdb1c9 100644
--- a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/DnsResolver.cs
+++ b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/DnsResolver.cs
@@ -11,6 +11,7 @@
using System.Security.Cryptography;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.ServiceDiscovery.Dns.Resolver;
@@ -19,43 +20,40 @@ internal sealed partial class DnsResolver : IDnsResolver, IDisposable
private const int IPv4Length = 4;
private const int IPv6Length = 16;
- // CancellationTokenSource.CancelAfter has a maximum timeout of Int32.MaxValue milliseconds.
- private static readonly TimeSpan s_maxTimeout = TimeSpan.FromMilliseconds(int.MaxValue);
-
private bool _disposed;
- private readonly ResolverOptions _options;
+ private readonly DnsResolverOptions _options;
private readonly CancellationTokenSource _pendingRequestsCts = new();
private readonly TimeProvider _timeProvider;
private readonly ILogger _logger;
- public DnsResolver(TimeProvider timeProvider, ILogger logger) : this(timeProvider, logger, OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() ? ResolvConf.GetOptions() : NetworkInfo.GetOptions())
- {
- }
-
- internal DnsResolver(TimeProvider timeProvider, ILogger logger, ResolverOptions options)
+ public DnsResolver(TimeProvider timeProvider, ILogger logger, IOptions options)
{
_timeProvider = timeProvider;
_logger = logger;
- _options = options;
- Debug.Assert(_options.Servers.Count > 0);
+ _options = options.Value;
- if (options.Timeout != Timeout.InfiniteTimeSpan)
+ if (_options.Servers.Count == 0)
{
- ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(options.Timeout, TimeSpan.Zero);
- ArgumentOutOfRangeException.ThrowIfGreaterThan(options.Timeout, s_maxTimeout);
- }
- }
-
- internal DnsResolver(ResolverOptions options) : this(TimeProvider.System, NullLogger.Instance, options)
- {
- }
+ foreach (var server in OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()
+ ? ResolvConf.GetServers()
+ : NetworkInfo.GetServers())
+ {
+ _options.Servers.Add(server);
+ }
- internal DnsResolver(IEnumerable servers) : this(new ResolverOptions(servers.ToArray()))
- {
+ if (_options.Servers.Count == 0)
+ {
+ throw new ArgumentException("At least one DNS server is required.", nameof(options));
+ }
+ }
}
- internal DnsResolver(IPEndPoint server) : this(new ResolverOptions(server))
+ // This constructor is for unit testing only. Does not auto-add system DNS servers.
+ internal DnsResolver(DnsResolverOptions options)
{
+ _timeProvider = TimeProvider.System;
+ _logger = NullLogger.Instance;
+ _options = options;
}
public ValueTask ResolveServiceAsync(string name, CancellationToken cancellationToken = default)
@@ -365,7 +363,7 @@ internal struct SendQueryResult
{
IPEndPoint serverEndPoint = _options.Servers[index];
- for (int attempt = 1; attempt <= _options.Attempts; attempt++)
+ for (int attempt = 1; attempt <= _options.MaxAttempts; attempt++)
{
DnsResponse response = default;
try
diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/NetworkInfo.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/NetworkInfo.cs
index c2ef13f922e..24b5155a1c8 100644
--- a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/NetworkInfo.cs
+++ b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/NetworkInfo.cs
@@ -8,8 +8,8 @@ namespace Microsoft.Extensions.ServiceDiscovery.Dns.Resolver;
internal static class NetworkInfo
{
- // basic option to get DNS serves via NetworkInfo. We may get it directly later via proper APIs.
- public static ResolverOptions GetOptions()
+ // basic option to get DNS servers via NetworkInfo. We may get it directly later via proper APIs.
+ public static IList GetServers()
{
List servers = new List();
@@ -31,6 +31,6 @@ public static ResolverOptions GetOptions()
}
}
- return new ResolverOptions(servers);
+ return servers;
}
}
diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/ResolvConf.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/ResolvConf.cs
index fbfdc5ae027..de68e88c18d 100644
--- a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/ResolvConf.cs
+++ b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/ResolvConf.cs
@@ -10,12 +10,12 @@ internal static class ResolvConf
{
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("osx")]
- public static ResolverOptions GetOptions()
+ public static IList GetServers()
{
- return GetOptions(new StreamReader("/etc/resolv.conf"));
+ return GetServers(new StreamReader("/etc/resolv.conf"));
}
- public static ResolverOptions GetOptions(TextReader reader)
+ public static IList GetServers(TextReader reader)
{
List serverList = new();
@@ -40,9 +40,9 @@ public static ResolverOptions GetOptions(TextReader reader)
if (serverList.Count == 0)
{
// If no nameservers are configured, fall back to the default behavior of using the system resolver configuration.
- return NetworkInfo.GetOptions();
+ return NetworkInfo.GetServers();
}
- return new ResolverOptions(serverList);
+ return serverList;
}
}
diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/ResolverOptions.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/ResolverOptions.cs
deleted file mode 100644
index 51d03f64bfd..00000000000
--- a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/Resolver/ResolverOptions.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Net;
-
-namespace Microsoft.Extensions.ServiceDiscovery.Dns.Resolver;
-
-internal sealed class ResolverOptions
-{
- public IReadOnlyList Servers;
- public int Attempts = 2;
- public TimeSpan Timeout = TimeSpan.FromSeconds(3);
-
- // override for testing purposes
- internal Func, int, int>? _transportOverride;
-
- public ResolverOptions(IReadOnlyList servers)
- {
- if (servers.Count == 0)
- {
- throw new ArgumentException("At least one DNS server is required.", nameof(servers));
- }
-
- Servers = servers;
- }
-
- public ResolverOptions(IPEndPoint server)
- {
- Servers = new IPEndPoint[] { server };
- }
-}
diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/ServiceDiscoveryDnsServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/ServiceDiscoveryDnsServiceCollectionExtensions.cs
index 42f220445b1..313e68b3b8a 100644
--- a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/ServiceDiscoveryDnsServiceCollectionExtensions.cs
+++ b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/ServiceDiscoveryDnsServiceCollectionExtensions.cs
@@ -3,6 +3,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
using Microsoft.Extensions.ServiceDiscovery;
using Microsoft.Extensions.ServiceDiscovery.Dns;
using Microsoft.Extensions.ServiceDiscovery.Dns.Resolver;
@@ -59,24 +60,10 @@ public static IServiceCollection AddDnsSrvServiceEndpointProvider(this IServiceC
services.AddSingleton();
var options = services.AddOptions();
- options.Configure(o => configureOptions?.Invoke(o));
+ options.Configure(configureOptions);
+
return services;
- static bool GetDnsClientFallbackFlag()
- {
- if (AppContext.TryGetSwitch("Microsoft.Extensions.ServiceDiscovery.Dns.UseDnsClientFallback", out var value))
- {
- return value;
- }
-
- var envVar = Environment.GetEnvironmentVariable("MICROSOFT_EXTENSIONS_SERVICE_DISCOVERY_DNS_USE_DNSCLIENT_FALLBACK");
- if (envVar is not null && (envVar.Equals("true", StringComparison.OrdinalIgnoreCase) || envVar.Equals("1")))
- {
- return true;
- }
-
- return false;
- }
}
///
@@ -109,9 +96,55 @@ public static IServiceCollection AddDnsServiceEndpointProvider(this IServiceColl
ArgumentNullException.ThrowIfNull(configureOptions);
services.AddServiceDiscoveryCore();
+
+ if (!GetDnsClientFallbackFlag())
+ {
+ services.TryAddSingleton();
+ }
+ else
+ {
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ }
+
services.AddSingleton();
var options = services.AddOptions();
- options.Configure(o => configureOptions?.Invoke(o));
+ options.Configure(configureOptions);
+
+ return services;
+ }
+
+ ///
+ /// Configures the DNS resolver used for service discovery.
+ ///
+ /// The service collection.
+ /// The DNS resolver options.
+ /// The provided .
+ public static IServiceCollection ConfigureDnsResolver(this IServiceCollection services, Action configureOptions)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(configureOptions);
+
+ var options = services.AddOptions();
+ options.Configure(configureOptions);
+ services.AddTransient, DnsResolverOptionsValidator>();
return services;
}
+
+ private static bool GetDnsClientFallbackFlag()
+ {
+ if (AppContext.TryGetSwitch("Microsoft.Extensions.ServiceDiscovery.Dns.UseDnsClientFallback", out var value))
+ {
+ return value;
+ }
+
+ var envVar = Environment.GetEnvironmentVariable("MICROSOFT_EXTENSIONS_SERVICE_DISCOVERY_DNS_USE_DNSCLIENT_FALLBACK");
+ if (envVar is not null && (envVar.Equals("true", StringComparison.OrdinalIgnoreCase) || envVar.Equals("1")))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
}
diff --git a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests.Fuzzing/Fuzzers/DnsResponseFuzzer.cs b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests.Fuzzing/Fuzzers/DnsResponseFuzzer.cs
index 1b180d74b9d..2e4658e3ba2 100644
--- a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests.Fuzzing/Fuzzers/DnsResponseFuzzer.cs
+++ b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests.Fuzzing/Fuzzers/DnsResponseFuzzer.cs
@@ -19,10 +19,11 @@ public void FuzzTarget(ReadOnlySpan data)
if (_resolver == null)
{
_buffer = new byte[4096];
- _resolver = new DnsResolver(new ResolverOptions(new IPEndPoint(IPAddress.Loopback, 53))
+ _resolver = new DnsResolver(new DnsResolverOptions
{
+ Servers = [new IPEndPoint(IPAddress.Loopback, 53)],
Timeout = TimeSpan.FromSeconds(5),
- Attempts = 1,
+ MaxAttempts = 1,
_transportOverride = (buffer, length) =>
{
// the first two bytes are the random transaction ID, so we keep that
@@ -41,4 +42,4 @@ public void FuzzTarget(ReadOnlySpan data)
Debug.Assert(task.IsCompleted, "Task should be completed synchronously");
task.GetAwaiter().GetResult();
}
-}
\ No newline at end of file
+}
diff --git a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsServicePublicApiTests.cs b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsServicePublicApiTests.cs
index e347deb9822..69bb6e0e510 100644
--- a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsServicePublicApiTests.cs
+++ b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsServicePublicApiTests.cs
@@ -68,12 +68,23 @@ public void AddDnsServiceEndpointProviderWithConfigureOptionsShouldThrowWhenServ
}
[Fact]
- public void AddDnsServiceEndpointProviderWithConfigureOptionsShouldThrowWhenConfigureOptionsIsNull()
+ public void ConfigureDnsResolverShouldThrowWhenServicesIsNull()
+ {
+ IServiceCollection services = null!;
+
+ var action = () => services.ConfigureDnsResolver(_ => { });
+
+ var exception = Assert.Throws(action);
+ Assert.Equal(nameof(services), exception.ParamName);
+ }
+
+ [Fact]
+ public void ConfigureDnsResolverShouldThrowWhenConfigureOptionsIsNull()
{
IServiceCollection services = new ServiceCollection();
- Action configureOptions = null!;
+ Action configureOptions = null!;
- var action = () => services.AddDnsServiceEndpointProvider(configureOptions);
+ var action = () => services.ConfigureDnsResolver(configureOptions);
var exception = Assert.Throws(action);
Assert.Equal(nameof(configureOptions), exception.ParamName);
diff --git a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/LoopbackDnsTestBase.cs b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/LoopbackDnsTestBase.cs
index 14abd659029..6d2aba6cb64 100644
--- a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/LoopbackDnsTestBase.cs
+++ b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/LoopbackDnsTestBase.cs
@@ -5,6 +5,8 @@
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
using Microsoft.Extensions.ServiceDiscovery.Dns.Tests;
using Microsoft.Extensions.Time.Testing;
using Xunit.Abstractions;
@@ -18,7 +20,7 @@ public abstract class LoopbackDnsTestBase : IDisposable
internal LoopbackDnsServer DnsServer { get; }
private readonly Lazy _resolverLazy;
internal DnsResolver Resolver => _resolverLazy.Value;
- internal ResolverOptions Options { get; }
+ internal DnsResolverOptions Options { get; }
protected readonly FakeTimeProvider TimeProvider;
public LoopbackDnsTestBase(ITestOutputHelper output)
@@ -26,10 +28,11 @@ public LoopbackDnsTestBase(ITestOutputHelper output)
Output = output;
DnsServer = new();
TimeProvider = new();
- Options = new([DnsServer.DnsEndPoint])
+ Options = new()
{
+ Servers = [DnsServer.DnsEndPoint],
Timeout = TimeSpan.FromSeconds(5),
- Attempts = 1,
+ MaxAttempts = 1,
};
_resolverLazy = new(InitializeResolver);
}
@@ -39,8 +42,7 @@ DnsResolver InitializeResolver()
ServiceCollection services = new();
services.AddXunitLogging(Output);
- // construct DnsResolver manually via internal constructor which accepts ResolverOptions
- var resolver = new DnsResolver(TimeProvider, services.BuildServiceProvider().GetRequiredService>(), Options);
+ var resolver = new DnsResolver(TimeProvider, NullLogger.Instance, new OptionsWrapper(Options));
return resolver;
}
diff --git a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/ResolvConfTests.cs b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/ResolvConfTests.cs
index 281ffbecd24..4c2bcadd8a5 100644
--- a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/ResolvConfTests.cs
+++ b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/ResolvConfTests.cs
@@ -9,7 +9,7 @@ namespace Microsoft.Extensions.ServiceDiscovery.Dns.Resolver.Tests;
public class ResolvConfTests
{
[Fact]
- public void GetOptions()
+ public void GetServers()
{
var contents = @"
nameserver 10.96.0.10
@@ -18,9 +18,9 @@ search default.svc.cluster.local svc.cluster.local cluster.local
@";
var reader = new StringReader(contents);
- ResolverOptions options = ResolvConf.GetOptions(reader);
+ var servers = ResolvConf.GetServers(reader);
- IPEndPoint ipAddress = Assert.Single(options.Servers);
+ IPEndPoint ipAddress = Assert.Single(servers);
Assert.Equal(new IPEndPoint(IPAddress.Parse("10.96.0.10"), 53), ipAddress);
}
}
diff --git a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/RetryTests.cs b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/RetryTests.cs
index 49985846570..3d6f3724484 100644
--- a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/RetryTests.cs
+++ b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/RetryTests.cs
@@ -11,7 +11,7 @@ public class RetryTests : LoopbackDnsTestBase
{
public RetryTests(ITestOutputHelper output) : base(output)
{
- Options.Attempts = 3;
+ Options.MaxAttempts = 3;
}
private Task SetupUdpProcessFunction(LoopbackDnsServer server, Func func)
@@ -49,7 +49,7 @@ public async Task Retry_Simple_Success()
Task t = SetupUdpProcessFunction(builder =>
{
attempt++;
- if (attempt == Options.Attempts)
+ if (attempt == Options.MaxAttempts)
{
builder.Answers.AddAddress(hostName, 3600, address);
}
@@ -214,7 +214,7 @@ public async Task ExhaustedRetries_FailoverToNextServer()
return Task.CompletedTask;
});
- Assert.Equal(Options.Attempts, primaryAttempt);
+ Assert.Equal(Options.MaxAttempts, primaryAttempt);
Assert.Equal(1, secondaryAttempt);
AddressResult res = Assert.Single(results);
diff --git a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/TcpFailoverTests.cs b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/TcpFailoverTests.cs
index cbdb5e282e9..b2891cfb512 100644
--- a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/TcpFailoverTests.cs
+++ b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/Resolver/TcpFailoverTests.cs
@@ -42,7 +42,7 @@ public async Task TcpFailover_Simple_Success()
public async Task TcpFailover_ServerClosesWithoutData_EmptyResult()
{
string hostName = "tcp-server-closes.test";
- Options.Attempts = 1;
+ Options.MaxAttempts = 1;
Options.Timeout = TimeSpan.FromSeconds(60);
_ = DnsServer.ProcessUdpRequest(builder =>
@@ -66,7 +66,7 @@ public async Task TcpFailover_ServerClosesWithoutData_EmptyResult()
public async Task TcpFailover_TcpNotAvailable_EmptyResult()
{
string hostName = "tcp-not-available.test";
- Options.Attempts = 1;
+ Options.MaxAttempts = 1;
Options.Timeout = TimeSpan.FromMilliseconds(100000);
_ = DnsServer.ProcessUdpRequest(builder =>
diff --git a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/ServiceDiscoveryDnsServiceCollectionExtensionsTests.cs b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/ServiceDiscoveryDnsServiceCollectionExtensionsTests.cs
new file mode 100644
index 00000000000..3631c2e8085
--- /dev/null
+++ b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/ServiceDiscoveryDnsServiceCollectionExtensionsTests.cs
@@ -0,0 +1,92 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.ServiceDiscovery.Dns.Tests;
+
+public class ServiceDiscoveryDnsServiceCollectionExtensionsTests
+{
+ [Fact]
+ public void AddDnsServiceEndpointProviderShouldRegisterDependentServices()
+ {
+ var services = new ServiceCollection();
+ services.AddDnsServiceEndpointProvider();
+
+ using var serviceProvider = services.BuildServiceProvider(true);
+
+ var exception = Record.Exception(() => serviceProvider.GetServices());
+ Assert.Null(exception);
+ }
+
+ [Fact]
+ public void AddDnsSrvServiceEndpointProviderShouldRegisterDependentServices()
+ {
+ var services = new ServiceCollection();
+ services.AddDnsSrvServiceEndpointProvider();
+
+ using var serviceProvider = services.BuildServiceProvider(true);
+
+ var exception = Record.Exception(() => serviceProvider.GetServices());
+ Assert.Null(exception);
+ }
+
+ [Fact]
+ public void ConfigureDnsResolverShouldThrowWhenServersIsNull()
+ {
+ var services = new ServiceCollection();
+ services.ConfigureDnsResolver(options => options.Servers = null!);
+
+ using var serviceProvider = services.BuildServiceProvider();
+ var options = serviceProvider.GetRequiredService>();
+
+ var exception = Assert.Throws(() => options.Value);
+ Assert.Equal("Servers must not be null.", exception.Message);
+ }
+
+ [Fact]
+ public void ConfigureDnsResolverShouldThrowWhenMaxAttemptsIsZero()
+ {
+ var services = new ServiceCollection();
+ services.ConfigureDnsResolver(options => options.MaxAttempts = 0);
+
+ using var serviceProvider = services.BuildServiceProvider();
+ var options = serviceProvider.GetRequiredService>();
+
+ var exception = Assert.Throws(() => options.Value);
+ Assert.Equal("MaxAttempts must be one or greater.", exception.Message);
+ }
+
+ [Fact]
+ public void ConfigureDnsResolverShouldThrowWhenTimeoutIsZero()
+ {
+ var services = new ServiceCollection();
+ services.ConfigureDnsResolver(options => options.Timeout = TimeSpan.Zero);
+
+ using var serviceProvider = services.BuildServiceProvider();
+ var options = serviceProvider.GetRequiredService>();
+
+ var exception = Assert.Throws(() => options.Value);
+ Assert.Equal("Timeout must not be negative or zero.", exception.Message);
+ }
+
+ [Fact]
+ public void ConfigureDnsResolverShouldThrowWhenTimeoutExceedsMaximum()
+ {
+ var services = new ServiceCollection();
+ services.ConfigureDnsResolver(options => options.Timeout = TimeSpan.FromMilliseconds(1L + int.MaxValue));
+
+ using var serviceProvider = services.BuildServiceProvider();
+ var options = serviceProvider.GetRequiredService>();
+
+ var exception = Assert.Throws(() => options.Value);
+ Assert.Equal("Timeout must not be greater than 2147483647 milliseconds.", exception.Message);
+ }
+}
diff --git a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Yarp.Tests/YarpServiceDiscoveryTests.cs b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Yarp.Tests/YarpServiceDiscoveryTests.cs
index c2751823c65..d38814621c4 100644
--- a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Yarp.Tests/YarpServiceDiscoveryTests.cs
+++ b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Yarp.Tests/YarpServiceDiscoveryTests.cs
@@ -1,16 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Xunit;
-using Yarp.ReverseProxy.Configuration;
using System.Net;
using System.Net.Sockets;
-using Microsoft.Extensions.ServiceDiscovery.Dns.Resolver;
using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Options;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.ServiceDiscovery.Dns;
+using Microsoft.Extensions.ServiceDiscovery.Dns.Resolver;
+using Yarp.ReverseProxy.Configuration;
namespace Microsoft.Extensions.ServiceDiscovery.Yarp.Tests;
@@ -232,7 +232,7 @@ public async Task ServiceDiscoveryDestinationResolverTests_Configuration_Disallo
[Fact]
public async Task ServiceDiscoveryDestinationResolverTests_Dns()
{
- DnsResolver resolver = new DnsResolver(TimeProvider.System, NullLogger.Instance);
+ DnsResolver resolver = new DnsResolver(TimeProvider.System, NullLogger.Instance, new OptionsWrapper(new DnsResolverOptions()));
await using var services = new ServiceCollection()
.AddSingleton(resolver)