Skip to content
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();
}
}

internal static void ClearCache()
{
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)
{
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);

// 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);

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

RegionNameMapping.ClearCache();
}

[TestMethod]
public void InvalidApplicationNameCatchTest()
{
Expand Down