CosmosClientOptions: Adds support for multiple formats of Azure region names (#4016)
* 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 * Address PR comment to avoid duplicating list of names. * Remove the map table cache The map table is only used on initialization, so there's no need to keep a cache of it for the lifetime of the application * Only convert the region names when the client is initializing The cache is created before converting all the names, so it only needs created once, but doesn't remain for the entire lifetime of the application * Update tests * Make RegionNameMapper an instantiable class Instead of having a prepare/clear cache system on a static class, make RegionNameMapper a class that gets instantiated for use and let the ctor handle it. * Remove debugging * Update tests to actually test things --------- Co-authored-by: Pradeep Chellappan <Pradeep.Chellappan@docusign.com> Co-authored-by: Pradeep Chellappan <94089783+pradeep-chellappan@users.noreply.github.com> Co-authored-by: Matias Quaranta <ealsur@users.noreply.github.com> Co-authored-by: Kiran Kumar Kolli <kirankk@microsoft.com>
This commit is contained in:
Родитель
88fd4cdb9b
Коммит
e2fb347a4a
|
@ -785,14 +785,17 @@ namespace Microsoft.Azure.Cosmos
|
|||
connectionPolicy.CosmosClientTelemetryOptions = this.CosmosClientTelemetryOptions;
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
@ -556,6 +557,170 @@ namespace Microsoft.Azure.Cosmos.Tests
|
|||
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()
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче