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 15 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
19 changes: 11 additions & 8 deletions Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -779,14 +779,17 @@ internal virtual ConnectionPolicy GetConnectionPolicy(int clientId)
connectionPolicy.EnableClientTelemetry = this.EnableClientTelemetry.Value;
}

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

if (this.ApplicationPreferredRegions != null)
{
connectionPolicy.SetPreferredLocations(this.ApplicationPreferredRegions);
RegionNameMapper mapper = new RegionNameMapper();
if (!string.IsNullOrEmpty(this.ApplicationRegion))
{
connectionPolicy.SetCurrentLocation(mapper.GetCosmosDBRegionName(this.ApplicationRegion));
}

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

connectionPolicy.SetPreferredLocations(mappedRegions);
}

if (this.MaxRetryAttemptsOnRateLimitedRequests != null)
Expand Down
53 changes: 53 additions & 0 deletions Microsoft.Azure.Cosmos/src/RegionNameMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//------------------------------------------------------------
// 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 sealed class RegionNameMapper
{
private readonly Dictionary<string, string> normalizedToCosmosDBRegionNameMapping;

public RegionNameMapper()
{
FieldInfo[] fields = typeof(Regions).GetFields(BindingFlags.Public | BindingFlags.Static);

this.normalizedToCosmosDBRegionNameMapping = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
kirankumarkolli marked this conversation as resolved.
Show resolved Hide resolved

foreach (FieldInfo field in fields)
{
this.normalizedToCosmosDBRegionNameMapping[field.Name] = field.GetValue(null).ToString();
}
}

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

normalizedRegionName = normalizedRegionName.Replace(" ", string.Empty);
if (this.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.NorthCentralUS;

ConnectionPolicy policy = cosmosClientOptions.GetConnectionPolicy(0);

Assert.AreEqual(Regions.NorthCentralUS, policy.PreferredLocations[0]);

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

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 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()
{
RegionNameMapper mapper = new RegionNameMapper();

// Test normalized name
Assert.AreEqual(Regions.WestUS2, mapper.GetCosmosDBRegionName("westus2"));

// Test with spaces
Assert.AreEqual(Regions.WestUS2, mapper.GetCosmosDBRegionName("west us 2"));

// Test for case insenstive
Assert.AreEqual(Regions.WestUS2, mapper.GetCosmosDBRegionName("wEsTuS2"));
}

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