From d2bc95d2a77f0580f32d073902e8102390bf9a23 Mon Sep 17 00:00:00 2001 From: Pradeep Chellappan Date: Thu, 18 May 2023 02:02:47 -0700 Subject: [PATCH 1/2] Allow ComosClientOptions to take ApplicationRegion and ApplicationPreferredRegions in multiple region name formats. This is a proposed fix for - https://github.com/Azure/azure-cosmos-dotnet-v3/issues/2330 --- .../src/CosmosClientOptions.cs | 14 ++- .../src/RegionNameMapping.cs | 104 ++++++++++++++++++ .../CosmosClientOptionsUnitTests.cs | 68 ++++++++++++ 3 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/RegionNameMapping.cs diff --git a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs index c826bb90d1..e4b948dd1e 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs @@ -72,6 +72,8 @@ public class CosmosClientOptions private IWebProxy webProxy; private Func httpClientFactory; private string applicationName; + private string applicationRegion; + private IReadOnlyList applicationPreferredRegions; /// /// Creates a new CosmosClientOptions @@ -154,7 +156,11 @@ public string ApplicationName /// /// /// High availability on regional outages - public string ApplicationRegion { get; set; } + public string ApplicationRegion + { + get => this.applicationRegion; + set => this.applicationRegion = RegionNameMapping.GetCosmosDBRegionName(value); + } /// /// Gets and sets the preferred regions for geo-replicated database accounts in the Azure Cosmos DB service. @@ -189,7 +195,11 @@ public string ApplicationName /// /// /// High availability on regional outages - public IReadOnlyList ApplicationPreferredRegions { get; set; } + public IReadOnlyList ApplicationPreferredRegions + { + get => this.applicationPreferredRegions; + set => this.applicationPreferredRegions = value?.Select(RegionNameMapping.GetCosmosDBRegionName).ToList(); + } /// /// Get or set the maximum number of concurrent connections allowed for the target diff --git a/Microsoft.Azure.Cosmos/src/RegionNameMapping.cs b/Microsoft.Azure.Cosmos/src/RegionNameMapping.cs new file mode 100644 index 0000000000..c45c979a55 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/RegionNameMapping.cs @@ -0,0 +1,104 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections.Generic; + + /// + /// Maps a normalized region name to the format that CosmosDB is expecting (for e.g. from 'westus2' to 'West US 2') + /// + internal class RegionNameMapping + { + private static readonly IReadOnlyDictionary normalizedToCosmosDBRegionNameMapping = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {"westus", Regions.WestUS}, + {"westus2", Regions.WestUS2}, + {"westcentralus", Regions.WestCentralUS}, + {"eastus", Regions.EastUS}, + {"eastus2", Regions.EastUS2}, + {"centralus", Regions.CentralUS}, + {"southcentralus", Regions.SouthCentralUS}, + {"northcentralus", Regions.NorthCentralUS}, + {"westeurope", Regions.WestEurope}, + {"northeurope", Regions.NorthEurope}, + {"eastasia", Regions.EastAsia}, + {"southeastasia", Regions.SoutheastAsia}, + {"japaneast", Regions.JapanEast}, + {"japanwest", Regions.JapanWest}, + {"australiaeast", Regions.AustraliaEast}, + {"australiasoutheast", Regions.AustraliaSoutheast}, + {"centralindia", Regions.CentralIndia}, + {"southindia", Regions.SouthIndia}, + {"westindia", Regions.WestIndia}, + {"canadaeast", Regions.CanadaEast}, + {"canadacentral", Regions.CanadaCentral}, + {"germanycentral", Regions.GermanyCentral}, + {"germanynortheast", Regions.GermanyNortheast}, + {"chinanorth", Regions.ChinaNorth}, + {"chinaeast", Regions.ChinaEast}, + {"chinanorth2", Regions.ChinaNorth2}, + {"chinaeast2", Regions.ChinaEast2}, + {"koreasouth", Regions.KoreaSouth}, + {"koreacentral", Regions.KoreaCentral}, + {"ukwest", Regions.UKWest}, + {"uksouth", Regions.UKSouth}, + {"brazilsouth", Regions.BrazilSouth}, + {"usgovarizona", Regions.USGovArizona}, + {"usgovtexas", Regions.USGovTexas}, + {"usgovvirginia", Regions.USGovVirginia}, + {"eastus2euap", Regions.EastUS2EUAP}, + {"centraluseuap", Regions.CentralUSEUAP}, + {"francecentral", Regions.FranceCentral}, + {"francesouth", Regions.FranceSouth}, + {"usdodcentral", Regions.USDoDCentral}, + {"usdodeast", Regions.USDoDEast}, + {"australiacentral", Regions.AustraliaCentral}, + {"australiacentral2", Regions.AustraliaCentral2}, + {"southafricanorth", Regions.SouthAfricaNorth}, + {"southafricawest", Regions.SouthAfricaWest}, + {"uaecentral", Regions.UAECentral}, + {"uaenorth", Regions.UAENorth}, + {"usnateast", Regions.USNatEast}, + {"usnatwest", Regions.USNatWest}, + {"usseceast", Regions.USSecEast}, + {"ussecwest", Regions.USSecWest}, + {"switzerlandnorth", Regions.SwitzerlandNorth}, + {"switzerlandwest", Regions.SwitzerlandWest}, + {"germanynorth", Regions.GermanyNorth}, + {"germanywestcentral", Regions.GermanyWestCentral}, + {"norwayeast", Regions.NorwayEast}, + {"norwaywest", Regions.NorwayWest}, + {"brazilsoutheast", Regions.BrazilSoutheast}, + {"westus3", Regions.WestUS3}, + {"jioindiacentral", Regions.JioIndiaCentral}, + {"jioindiawest", Regions.JioIndiaWest}, + {"eastusslv", Regions.EastUSSLV}, + {"swedencentral", Regions.SwedenCentral}, + {"swedensouth", Regions.SwedenSouth}, + {"qatarcentral", Regions.QatarCentral}, + {"chinanorth3", Regions.ChinaNorth3}, + {"chinaeast3", Regions.ChinaEast3}, + {"polandcentral", Regions.PolandCentral}, + }; + + /// + /// Given a normalized region name, this function retrieves the region name in the format that CosmosDB expects. + /// If the region is not known, the same value as input is returned. + /// + /// An Azure region name in a normalized format. The input is not case sensitive. + /// A string that contains the region name in the format that CosmosDB expects. + public static string GetCosmosDBRegionName(string normalizedRegionName) + { + if (normalizedRegionName != null && normalizedToCosmosDBRegionNameMapping.TryGetValue(normalizedRegionName, out string cosmosDBRegionName)) + { + return cosmosDBRegionName; + } + + return normalizedRegionName; + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs index efb06d7cc5..13528ce380 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs @@ -539,6 +539,74 @@ public void WithQuorumReadWithEventualConsistencyAccount() Assert.IsTrue(cosmosClientOptions.EnableUpgradeConsistencyToLocalQuorum); } + [TestMethod] + public void VerifyRegionNameFormatConversionForApplicationRegion() + { + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions(); + cosmosClientOptions.ApplicationRegion = "westus2"; + Assert.AreEqual(Regions.WestUS2, cosmosClientOptions.ApplicationRegion); + } + + [TestMethod] + public void VerifyRegionNameFormatConversionBypassForApplicationRegion() + { + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions(); + + // No conversion for expected format. + cosmosClientOptions.ApplicationRegion = Regions.AustraliaCentral2; + Assert.AreEqual(Regions.AustraliaCentral2, cosmosClientOptions.ApplicationRegion); + + // Ignore unknown values. + cosmosClientOptions.ApplicationRegion = null; + Assert.IsNull(cosmosClientOptions.ApplicationRegion); + + cosmosClientOptions.ApplicationRegion = string.Empty; + Assert.AreEqual(string.Empty, cosmosClientOptions.ApplicationRegion); + + cosmosClientOptions.ApplicationRegion = "Invalid region"; + Assert.AreEqual("Invalid region", cosmosClientOptions.ApplicationRegion); + } + + [TestMethod] + public void VerifyRegionNameFormatConversionForApplicationPreferredRegions() + { + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions(); + cosmosClientOptions.ApplicationPreferredRegions = new List {"westus2", "usdodcentral", Regions.ChinaNorth3}; + Assert.AreEqual(Regions.WestUS2, cosmosClientOptions.ApplicationPreferredRegions[0]); + Assert.AreEqual(Regions.USDoDCentral, cosmosClientOptions.ApplicationPreferredRegions[1]); + Assert.AreEqual(Regions.ChinaNorth3, cosmosClientOptions.ApplicationPreferredRegions[2]); + } + + [TestMethod] + public void VerifyRegionNameFormatConversionBypassForInvalidApplicationPreferredRegions() + { + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions(); + + // List is null + cosmosClientOptions.ApplicationPreferredRegions = null; + Assert.IsNull(cosmosClientOptions.ApplicationRegion); + + // List is empty + cosmosClientOptions.ApplicationPreferredRegions = new List(); + Assert.AreEqual(0, cosmosClientOptions.ApplicationPreferredRegions.Count); + + // List contains valid and invalid values + cosmosClientOptions.ApplicationPreferredRegions = new List + { + null, + string.Empty, + Regions.JioIndiaCentral, + "westus2", + "Invalid region" + }; + + Assert.IsNull(cosmosClientOptions.ApplicationPreferredRegions[0]); + Assert.AreEqual(string.Empty, cosmosClientOptions.ApplicationPreferredRegions[1]); + Assert.AreEqual(Regions.JioIndiaCentral, cosmosClientOptions.ApplicationPreferredRegions[2]); + Assert.AreEqual(Regions.WestUS2, cosmosClientOptions.ApplicationPreferredRegions[3]); + Assert.AreEqual("Invalid region", cosmosClientOptions.ApplicationPreferredRegions[4]); + } + [TestMethod] public void InvalidApplicationNameCatchTest() { From 7abed776a19b37f61c40cc0589a9b8b7c5f09320 Mon Sep 17 00:00:00 2001 From: Pradeep Chellappan Date: Thu, 18 May 2023 10:30:23 -0700 Subject: [PATCH 2/2] Address PR comment to avoid duplicating list of names. --- .../src/RegionNameMapping.cs | 83 +++---------------- 1 file changed, 12 insertions(+), 71 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/RegionNameMapping.cs b/Microsoft.Azure.Cosmos/src/RegionNameMapping.cs index c45c979a55..d8d18149f0 100644 --- a/Microsoft.Azure.Cosmos/src/RegionNameMapping.cs +++ b/Microsoft.Azure.Cosmos/src/RegionNameMapping.cs @@ -6,84 +6,25 @@ namespace Microsoft.Azure.Cosmos { using System; using System.Collections.Generic; + using System.Reflection; /// /// Maps a normalized region name to the format that CosmosDB is expecting (for e.g. from 'westus2' to 'West US 2') /// internal class RegionNameMapping { - private static readonly IReadOnlyDictionary normalizedToCosmosDBRegionNameMapping = - new Dictionary(StringComparer.OrdinalIgnoreCase) + private static readonly IDictionary normalizedToCosmosDBRegionNameMapping; + + static RegionNameMapping() + { + FieldInfo[] fields = typeof(Regions).GetFields(BindingFlags.Public | BindingFlags.Static); + normalizedToCosmosDBRegionNameMapping = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (FieldInfo field in fields) { - {"westus", Regions.WestUS}, - {"westus2", Regions.WestUS2}, - {"westcentralus", Regions.WestCentralUS}, - {"eastus", Regions.EastUS}, - {"eastus2", Regions.EastUS2}, - {"centralus", Regions.CentralUS}, - {"southcentralus", Regions.SouthCentralUS}, - {"northcentralus", Regions.NorthCentralUS}, - {"westeurope", Regions.WestEurope}, - {"northeurope", Regions.NorthEurope}, - {"eastasia", Regions.EastAsia}, - {"southeastasia", Regions.SoutheastAsia}, - {"japaneast", Regions.JapanEast}, - {"japanwest", Regions.JapanWest}, - {"australiaeast", Regions.AustraliaEast}, - {"australiasoutheast", Regions.AustraliaSoutheast}, - {"centralindia", Regions.CentralIndia}, - {"southindia", Regions.SouthIndia}, - {"westindia", Regions.WestIndia}, - {"canadaeast", Regions.CanadaEast}, - {"canadacentral", Regions.CanadaCentral}, - {"germanycentral", Regions.GermanyCentral}, - {"germanynortheast", Regions.GermanyNortheast}, - {"chinanorth", Regions.ChinaNorth}, - {"chinaeast", Regions.ChinaEast}, - {"chinanorth2", Regions.ChinaNorth2}, - {"chinaeast2", Regions.ChinaEast2}, - {"koreasouth", Regions.KoreaSouth}, - {"koreacentral", Regions.KoreaCentral}, - {"ukwest", Regions.UKWest}, - {"uksouth", Regions.UKSouth}, - {"brazilsouth", Regions.BrazilSouth}, - {"usgovarizona", Regions.USGovArizona}, - {"usgovtexas", Regions.USGovTexas}, - {"usgovvirginia", Regions.USGovVirginia}, - {"eastus2euap", Regions.EastUS2EUAP}, - {"centraluseuap", Regions.CentralUSEUAP}, - {"francecentral", Regions.FranceCentral}, - {"francesouth", Regions.FranceSouth}, - {"usdodcentral", Regions.USDoDCentral}, - {"usdodeast", Regions.USDoDEast}, - {"australiacentral", Regions.AustraliaCentral}, - {"australiacentral2", Regions.AustraliaCentral2}, - {"southafricanorth", Regions.SouthAfricaNorth}, - {"southafricawest", Regions.SouthAfricaWest}, - {"uaecentral", Regions.UAECentral}, - {"uaenorth", Regions.UAENorth}, - {"usnateast", Regions.USNatEast}, - {"usnatwest", Regions.USNatWest}, - {"usseceast", Regions.USSecEast}, - {"ussecwest", Regions.USSecWest}, - {"switzerlandnorth", Regions.SwitzerlandNorth}, - {"switzerlandwest", Regions.SwitzerlandWest}, - {"germanynorth", Regions.GermanyNorth}, - {"germanywestcentral", Regions.GermanyWestCentral}, - {"norwayeast", Regions.NorwayEast}, - {"norwaywest", Regions.NorwayWest}, - {"brazilsoutheast", Regions.BrazilSoutheast}, - {"westus3", Regions.WestUS3}, - {"jioindiacentral", Regions.JioIndiaCentral}, - {"jioindiawest", Regions.JioIndiaWest}, - {"eastusslv", Regions.EastUSSLV}, - {"swedencentral", Regions.SwedenCentral}, - {"swedensouth", Regions.SwedenSouth}, - {"qatarcentral", Regions.QatarCentral}, - {"chinanorth3", Regions.ChinaNorth3}, - {"chinaeast3", Regions.ChinaEast3}, - {"polandcentral", Regions.PolandCentral}, - }; + normalizedToCosmosDBRegionNameMapping[field.Name.ToLowerInvariant()] = field.GetValue(null).ToString(); + } + } /// /// Given a normalized region name, this function retrieves the region name in the format that CosmosDB expects.