Skip to content
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

Support multiple formats of Azure region names for CosmosClientOptions #3857

Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ public class CosmosClientOptions
private IWebProxy webProxy;
private Func<HttpClient> httpClientFactory;
private string applicationName;
private string applicationRegion;
private IReadOnlyList<string> applicationPreferredRegions;

/// <summary>
/// Creates a new CosmosClientOptions
Expand Down Expand Up @@ -154,7 +156,11 @@ public string ApplicationName
/// </example>
/// <seealso cref="CosmosClientBuilder.WithApplicationRegion(string)"/>
/// <seealso href="https://docs.microsoft.com/azure/cosmos-db/high-availability#high-availability-with-cosmos-db-in-the-event-of-regional-outages">High availability on regional outages</seealso>
public string ApplicationRegion { get; set; }
public string ApplicationRegion
{
get => this.applicationRegion;
set => this.applicationRegion = RegionNameMapping.GetCosmosDBRegionName(value);
Copy link
Member

@kirankumarkolli kirankumarkolli Jul 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Application input is updated right on setter. Functionally works.
One other alternative is to keep original values as Application configured and then later during CosmosClient initialization do conversation.

@ealsur, @FabianMeiswinkel any preferences?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer also having the original UserAgent input in the diagnostics - so, both the normalized and original values. Keeping the original value in ApplicationRegion and then client on initialization normalizing it is porbably the easiest way to get above.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No preference. On the Diagnostics we log the value of the public getter:

this.ConsistencyConfig = new ConsistencyConfig(cosmosClientContext.ClientOptions.ConsistencyLevel,
cosmosClientContext.ClientOptions.ApplicationPreferredRegions, cosmosClientContext.ClientOptions.ApplicationRegion);

The only thing with this approach is that the getter does not return the same value that was set, so if anyone is validating that if I set West US, I get West US, the test will fail.

Making the conversion internally on:

if (this.ApplicationRegion != null)
{
connectionPolicy.SetCurrentLocation(this.ApplicationRegion);
}
if (this.ApplicationPreferredRegions != null)
{
connectionPolicy.SetPreferredLocations(this.ApplicationPreferredRegions);
}

Would achieve the same thing while keeping the getter value matching the user input.

}

/// <summary>
/// Gets and sets the preferred regions for geo-replicated database accounts in the Azure Cosmos DB service.
Expand Down Expand Up @@ -189,7 +195,11 @@ public string ApplicationName
/// </code>
/// </example>
/// <seealso href="https://docs.microsoft.com/azure/cosmos-db/high-availability#high-availability-with-cosmos-db-in-the-event-of-regional-outages">High availability on regional outages</seealso>
public IReadOnlyList<string> ApplicationPreferredRegions { get; set; }
public IReadOnlyList<string> ApplicationPreferredRegions
{
get => this.applicationPreferredRegions;
set => this.applicationPreferredRegions = value?.Select(RegionNameMapping.GetCosmosDBRegionName).ToList();
}

/// <summary>
/// Get or set the maximum number of concurrent connections allowed for the target
Expand Down
104 changes: 104 additions & 0 deletions Microsoft.Azure.Cosmos/src/RegionNameMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos
{
using System;
using System.Collections.Generic;

/// <summary>
/// Maps a normalized region name to the format that CosmosDB is expecting (for e.g. from 'westus2' to 'West US 2')
/// </summary>
internal class RegionNameMapping
{
private static readonly IReadOnlyDictionary<string, string> normalizedToCosmosDBRegionNameMapping =
new Dictionary<string, string>(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},
};

/// <summary>
/// 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.
/// </summary>
/// <param name="normalizedRegionName">An Azure region name in a normalized format. The input is not case sensitive.</param>
/// <returns>A string that contains the region name in the format that CosmosDB expects.</returns>
public static string GetCosmosDBRegionName(string normalizedRegionName)
{
if (normalizedRegionName != null && normalizedToCosmosDBRegionNameMapping.TryGetValue(normalizedRegionName, out string cosmosDBRegionName))
{
return cosmosDBRegionName;
}

return normalizedRegionName;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {"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<string>();
Assert.AreEqual(0, cosmosClientOptions.ApplicationPreferredRegions.Count);

// List contains valid and invalid values
cosmosClientOptions.ApplicationPreferredRegions = new List<string>
{
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()
{
Expand Down