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

CosmosClientOptions: Adds support for multiple formats of Azure region names #4016

Merged
merged 17 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
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
24 changes: 17 additions & 7 deletions Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -779,14 +779,24 @@ internal virtual ConnectionPolicy GetConnectionPolicy(int clientId)
connectionPolicy.EnableClientTelemetry = this.EnableClientTelemetry.Value;
}

if (this.ApplicationRegion != null)
{
connectionPolicy.SetCurrentLocation(this.ApplicationRegion);
RegionNameMapping.PrepareCache();
try
{
if (!string.IsNullOrEmpty(this.ApplicationRegion))
{
connectionPolicy.SetCurrentLocation(RegionNameMapping.GetCosmosDBRegionName(this.ApplicationRegion));
}

if (this.ApplicationPreferredRegions != null)
{
List<string> mappedRegions = this.ApplicationPreferredRegions.Select(s => RegionNameMapping.GetCosmosDBRegionName(s)).ToList();

connectionPolicy.SetPreferredLocations(mappedRegions);
}
}

if (this.ApplicationPreferredRegions != null)
{
connectionPolicy.SetPreferredLocations(this.ApplicationPreferredRegions);
finally
{
RegionNameMapping.ClearCache();
}

if (this.MaxRetryAttemptsOnRateLimitedRequests != null)
Expand Down
60 changes: 60 additions & 0 deletions Microsoft.Azure.Cosmos/src/RegionNameMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

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

/// <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 Dictionary<string, string> normalizedToCosmosDBRegionNameMapping;

internal static void PrepareCache()
{
FieldInfo[] fields = typeof(Regions).GetFields(BindingFlags.Public | BindingFlags.Static);
normalizedToCosmosDBRegionNameMapping = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

foreach (FieldInfo field in fields)
{
normalizedToCosmosDBRegionNameMapping[field.Name.ToLowerInvariant()] = field.GetValue(null).ToString();
iainx marked this conversation as resolved.
Show resolved Hide resolved
iainx marked this conversation as resolved.
Show resolved Hide resolved
}
}

internal static void ClearCache()
iainx marked this conversation as resolved.
Show resolved Hide resolved
{
normalizedToCosmosDBRegionNameMapping = null;
}

/// <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 (string.IsNullOrEmpty(normalizedRegionName))
{
return string.Empty;
}

if (normalizedToCosmosDBRegionNameMapping == null)
iainx marked this conversation as resolved.
Show resolved Hide resolved
{
throw new ApplicationException("Name mapping cache has not been initialized");
}

if (normalizedToCosmosDBRegionNameMapping.TryGetValue(normalizedRegionName, out string cosmosDBRegionName))
{
return cosmosDBRegionName;
}

return normalizedRegionName;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ namespace Microsoft.Azure.Cosmos.Tests
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Net.Http;
Expand Down Expand Up @@ -544,6 +545,170 @@ public void WithQuorumReadWithEventualConsistencyAccount()
Assert.IsTrue(cosmosClientOptions.EnableUpgradeConsistencyToLocalQuorum);
}

[TestMethod]
public void VerifyRegionNameFormatConversionForApplicationRegion()
{
CosmosClientOptions cosmosClientOptions = new CosmosClientOptions();
cosmosClientOptions.ApplicationRegion = "westus2";

ConnectionPolicy policy = cosmosClientOptions.GetConnectionPolicy(0);

// Need to see Regions.WestUS2 in the list, but not "westus2"
bool seenWestUS2 = false;
bool seenNormalized = false;

foreach (string region in policy.PreferredLocations)
{
if (region == "westus2")
{
seenNormalized = true;
}

if (region == Regions.WestUS2)
{
seenWestUS2 = true;
}
}
Assert.IsTrue(seenWestUS2);
Assert.IsFalse(seenNormalized);
}

[TestMethod]
public void VerifyRegionNameFormatConversionBypassForApplicationRegion()
{
CosmosClientOptions cosmosClientOptions = new CosmosClientOptions();

// No conversion for expected format.
cosmosClientOptions.ApplicationRegion = Regions.AustraliaCentral2;
Assert.AreEqual(Regions.AustraliaCentral2, cosmosClientOptions.ApplicationRegion);
ealsur marked this conversation as resolved.
Show resolved Hide resolved

// Ignore unknown values.
cosmosClientOptions.ApplicationRegion = null;

ConnectionPolicy policy = cosmosClientOptions.GetConnectionPolicy(0);

Assert.AreEqual(0, policy.PreferredLocations.Count);

cosmosClientOptions.ApplicationRegion = string.Empty;
policy = cosmosClientOptions.GetConnectionPolicy(0);

Assert.AreEqual(0, policy.PreferredLocations.Count);

cosmosClientOptions.ApplicationRegion = "Invalid region";
Assert.ThrowsException<ArgumentException>(() => cosmosClientOptions.GetConnectionPolicy(0));
}

[TestMethod]
public void VerifyRegionNameFormatConversionForApplicationPreferredRegions()
{
CosmosClientOptions cosmosClientOptions = new CosmosClientOptions();
cosmosClientOptions.ApplicationPreferredRegions = new List<string> {"westus2", "usdodcentral", Regions.ChinaNorth3};

ConnectionPolicy policy = cosmosClientOptions.GetConnectionPolicy(0);

bool seenUSDodCentral = false;
bool seenWestUS2 = false;
bool seenChinaNorth3 = false;
bool seenNormalizedUSDodCentral = false;
bool seenNormalizedWestUS2 = false;

foreach (string region in policy.PreferredLocations)
{
if (region == Regions.USDoDCentral)
{
seenUSDodCentral = true;
}

if (region == Regions.WestUS2)
{
seenWestUS2 = true;
}

if (region == Regions.ChinaNorth3)
{
seenChinaNorth3 = true;
}

if (region == "westus2")
{
seenNormalizedWestUS2 = true;
}

if (region == "usdodcentral")
{
seenNormalizedUSDodCentral = true;
}
}

Assert.IsTrue(seenChinaNorth3);
Assert.IsTrue(seenWestUS2);
Assert.IsTrue(seenUSDodCentral);
Assert.IsFalse(seenNormalizedUSDodCentral);
Assert.IsFalse(seenNormalizedWestUS2);
}

[TestMethod]
public void VerifyRegionNameFormatConversionBypassForInvalidApplicationPreferredRegions()
{
CosmosClientOptions cosmosClientOptions = new CosmosClientOptions();

// List is null
cosmosClientOptions.ApplicationPreferredRegions = null;
Assert.IsNull(cosmosClientOptions.ApplicationRegion);
ealsur marked this conversation as resolved.
Show resolved Hide resolved

// 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"
};

ConnectionPolicy policy = cosmosClientOptions.GetConnectionPolicy(0);

bool seenJioIndiaCentral = false;
bool seenWestUS2 = false;
bool seenNormalized = false;

foreach (string region in policy.PreferredLocations)
{
if (region == Regions.JioIndiaCentral)
{
seenJioIndiaCentral = true;
}

if (region == Regions.WestUS2)
{
seenWestUS2 = true;
}

if (region == "westus2")
{
seenNormalized = true;
}
}

Assert.IsTrue(seenJioIndiaCentral);
Assert.IsTrue(seenWestUS2);
Assert.IsFalse(seenNormalized);
}

[TestMethod]
public void RegionNameMappingTest()
{
RegionNameMapping.PrepareCache();

Assert.AreEqual(Regions.WestUS2, RegionNameMapping.GetCosmosDBRegionName("westus2"));
iainx marked this conversation as resolved.
Show resolved Hide resolved

RegionNameMapping.ClearCache();
}

[TestMethod]
public void InvalidApplicationNameCatchTest()
{
Expand Down
Loading