From 11ef77d768866dae8cdbc599711ccda0c28abc33 Mon Sep 17 00:00:00 2001 From: Philo Date: Mon, 25 Nov 2024 04:47:52 -0800 Subject: [PATCH] Automatically detect and tune for Azure Managed Redis caches (#2818) Detect when connecting to a new Azure Managed Redis cache, and adjust default connection settings to align with their capabilities. More about Azure Managed Redis: https://learn.microsoft.com/azure/azure-cache-for-redis/managed-redis/managed-redis-overview --- docs/ReleaseNotes.md | 1 + .../Configuration/AzureOptionsProvider.cs | 35 +++++++++++--- .../StackExchange.Redis.Tests/ConfigTests.cs | 46 ++++++------------- .../DefaultOptionsTests.cs | 16 +++++++ 4 files changed, 60 insertions(+), 38 deletions(-) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index ace3e31d8..fc5cf3134 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -10,6 +10,7 @@ Current package versions: - Format IPv6 endpoints correctly when rewriting configration strings ([#2813 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2813)) - Update default Redis version from 4.0.0 to 6.0.0 for Azure Redis resources ([#2810 by philon-msft](https://github.com/StackExchange/StackExchange.Redis/pull/2810)) +- Detect Azure Managed Redis caches and tune default connection settings for them ([#2818 by philon-msft](https://github.com/StackExchange/StackExchange.Redis/pull/2818)) ## 2.8.16 diff --git a/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs b/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs index dcdb9f26a..fb01f0704 100644 --- a/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs +++ b/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs @@ -21,7 +21,7 @@ public class AzureOptionsProvider : DefaultOptionsProvider public override Version DefaultVersion => RedisFeatures.v6_0_0; /// - /// List of domains known to be Azure Redis, so we can light up some helpful functionality + /// Lists of domains known to be Azure Redis, so we can light up some helpful functionality /// for minimizing downtime during maintenance events and such. /// private static readonly string[] azureRedisDomains = new[] @@ -29,23 +29,40 @@ public class AzureOptionsProvider : DefaultOptionsProvider ".redis.cache.windows.net", ".redis.cache.chinacloudapi.cn", ".redis.cache.usgovcloudapi.net", - ".redis.cache.cloudapi.de", ".redisenterprise.cache.azure.net", }; + private static readonly string[] azureManagedRedisDomains = new[] + { + ".redis.azure.net", + ".redis.chinacloudapi.cn", + ".redis.usgovcloudapi.net", + }; + /// public override bool IsMatch(EndPoint endpoint) { if (endpoint is DnsEndPoint dnsEp) { - foreach (var host in azureRedisDomains) + if (IsHostInDomains(dnsEp.Host, azureRedisDomains) || IsHostInDomains(dnsEp.Host, azureManagedRedisDomains)) { - if (dnsEp.Host.EndsWith(host, StringComparison.InvariantCultureIgnoreCase)) - { - return true; - } + return true; } } + + return false; + } + + private bool IsHostInDomains(string hostName, string[] domains) + { + foreach (var domain in domains) + { + if (hostName.EndsWith(domain, StringComparison.InvariantCultureIgnoreCase)) + { + return true; + } + } + return false; } @@ -65,6 +82,10 @@ public override bool GetDefaultSsl(EndPointCollection endPoints) { return true; } + if (dns.Port == 10000 && IsHostInDomains(dns.Host, azureManagedRedisDomains)) + { + return true; // SSL is enabled by default on AMR caches + } break; case IPEndPoint ip: if (ip.Port == 6380) diff --git a/tests/StackExchange.Redis.Tests/ConfigTests.cs b/tests/StackExchange.Redis.Tests/ConfigTests.cs index 75f6d25f4..4db6b1163 100644 --- a/tests/StackExchange.Redis.Tests/ConfigTests.cs +++ b/tests/StackExchange.Redis.Tests/ConfigTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.IO.Pipelines; @@ -25,7 +26,6 @@ public class ConfigTests : TestBase public ConfigTests(ITestOutputHelper output, SharedConnectionFixture fixture) : base(output, fixture) { } public Version DefaultVersion = new(3, 0, 0); - public Version DefaultAzureVersion = new(6, 0, 0); [Fact] public void ExpectedFields() @@ -132,12 +132,21 @@ public void SslProtocols_InvalidValue() Assert.Throws(() => ConfigurationOptions.Parse("myhost,sslProtocols=InvalidSslProtocol")); } - [Fact] - public void ConfigurationOptionsDefaultForAzure() - { - var options = ConfigurationOptions.Parse("contoso.redis.cache.windows.net"); - Assert.True(options.DefaultVersion.Equals(DefaultAzureVersion)); + [Theory] + [InlineData("contoso.redis.cache.windows.net:6380", true)] + [InlineData("contoso.REDIS.CACHE.chinacloudapi.cn:6380", true)] // added a few upper case chars to validate comparison + [InlineData("contoso.redis.cache.usgovcloudapi.net:6380", true)] + [InlineData("contoso.redisenterprise.cache.azure.net:10000", false)] + [InlineData("contoso.redis.azure.net:10000", true)] + [InlineData("contoso.redis.chinacloudapi.cn:10000", true)] + [InlineData("contoso.redis.usgovcloudapi.net:10000", true)] + public void ConfigurationOptionsDefaultForAzure(string hostAndPort, bool sslShouldBeEnabled) + { + Version defaultAzureVersion = new(6, 0, 0); + var options = ConfigurationOptions.Parse(hostAndPort); + Assert.True(options.DefaultVersion.Equals(defaultAzureVersion)); Assert.False(options.AbortOnConnectFail); + Assert.Equal(sslShouldBeEnabled, options.Ssl); } [Fact] @@ -148,31 +157,6 @@ public void ConfigurationOptionsForAzureWhenSpecified() Assert.True(options.AbortOnConnectFail); } - [Fact] - public void ConfigurationOptionsDefaultForAzureChina() - { - // added a few upper case chars to validate comparison - var options = ConfigurationOptions.Parse("contoso.REDIS.CACHE.chinacloudapi.cn"); - Assert.True(options.DefaultVersion.Equals(DefaultAzureVersion)); - Assert.False(options.AbortOnConnectFail); - } - - [Fact] - public void ConfigurationOptionsDefaultForAzureGermany() - { - var options = ConfigurationOptions.Parse("contoso.redis.cache.cloudapi.de"); - Assert.True(options.DefaultVersion.Equals(DefaultAzureVersion)); - Assert.False(options.AbortOnConnectFail); - } - - [Fact] - public void ConfigurationOptionsDefaultForAzureUSGov() - { - var options = ConfigurationOptions.Parse("contoso.redis.cache.usgovcloudapi.net"); - Assert.True(options.DefaultVersion.Equals(DefaultAzureVersion)); - Assert.False(options.AbortOnConnectFail); - } - [Fact] public void ConfigurationOptionsDefaultForNonAzure() { diff --git a/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs b/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs index 74e52b96d..8c35ab3e1 100644 --- a/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs +++ b/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.Linq; using System.Net; using System.Threading; @@ -65,6 +66,21 @@ public void IsMatchOnDomain() Assert.IsType(provider); } + [Theory] + [InlineData("contoso.redis.cache.windows.net")] + [InlineData("contoso.REDIS.CACHE.chinacloudapi.cn")] // added a few upper case chars to validate comparison + [InlineData("contoso.redis.cache.usgovcloudapi.net")] + [InlineData("contoso.redisenterprise.cache.azure.net")] + [InlineData("contoso.redis.azure.net")] + [InlineData("contoso.redis.chinacloudapi.cn")] + [InlineData("contoso.redis.usgovcloudapi.net")] + public void IsMatchOnAzureDomain(string hostName) + { + var epc = new EndPointCollection(new List() { new DnsEndPoint(hostName, 0) }); + var provider = DefaultOptionsProvider.GetProvider(epc); + Assert.IsType(provider); + } + [Fact] public void AllOverridesFromDefaultsProp() {