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:
iain 2023-10-10 16:59:39 +01:00 коммит произвёл GitHub
Родитель 88fd4cdb9b
Коммит e2fb347a4a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 230 добавлений и 9 удалений

Просмотреть файл

@ -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()
{