Skip to content

Commit

Permalink
UserAgent: Adds flag to user agent to show if region failover is conf…
Browse files Browse the repository at this point in the history
…igured (#2487)

Do to recent outages and customer issues a flag is being added to the user agent to show if a client is properly configure to failover to new region in the case of a region outage.

D - Endpoint discovery disabled
S - Single application region is configured
L - List of regions specified
N - No regions specified
  • Loading branch information
j82w authored May 20, 2021
1 parent 2af3a6d commit f594027
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 9 deletions.
31 changes: 26 additions & 5 deletions Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -828,21 +828,42 @@ internal void SetUserAgentFeatures(UserAgentContainer userAgent)
features |= CosmosClientOptionsFeatures.HttpClientFactory;
}

string featureString = null;
if (features != CosmosClientOptionsFeatures.NoFeatures)
{
string featureString = Convert.ToString((int)features, 2).PadLeft(8, '0');
if (!string.IsNullOrEmpty(featureString))
{
userAgent.SetFeatures(featureString);
}
featureString = Convert.ToString((int)features, 2).PadLeft(8, '0');
}

string regionConfiguration = this.GetRegionConfiguration();
userAgent.SetFeatures(featureString, regionConfiguration);

if (!string.IsNullOrEmpty(this.ApplicationName))
{
userAgent.Suffix = this.ApplicationName;
}
}

/// <summary>
/// This generates a key that added to the user agent to make it
/// possible to determine if the SDK has region failover enabled.
/// </summary>
/// <returns>Format Reg-{D (Disabled discovery)}-S(application region)|L(List of preferred regions)|N(None, user did not configure it)</returns>
private string GetRegionConfiguration()
{
string regionConfig = this.LimitToEndpoint ? "D" : string.Empty;
if (!string.IsNullOrEmpty(this.ApplicationRegion))
{
return regionConfig + "S";
}

if (this.ApplicationPreferredRegions != null)
{
return regionConfig + "L";
}

return regionConfig + "N";
}

/// <summary>
/// Serialize the current configuration into a JSON string
/// </summary>
Expand Down
17 changes: 14 additions & 3 deletions Microsoft.Azure.Cosmos/src/UserAgentContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@ internal override string BaseUserAgent
}
}

internal void SetFeatures(string features)
internal void SetFeatures(
string features,
string regionConfiguration)
{
// Regenerate base user agent to account for features
this.cosmosBaseUserAgent = this.CreateBaseUserAgentString(features);
this.cosmosBaseUserAgent = this.CreateBaseUserAgentString(
features: features,
regionConfiguration: regionConfiguration);
this.Suffix = string.Empty;
}

Expand All @@ -55,7 +59,9 @@ protected virtual void GetEnvironmentInformation(
runtimeFramework = environmentInformation.RuntimeFramework;
}

private string CreateBaseUserAgentString(string features = null)
private string CreateBaseUserAgentString(
string features = null,
string regionConfiguration = null)
{
this.GetEnvironmentInformation(
out string clientVersion,
Expand All @@ -74,6 +80,11 @@ private string CreateBaseUserAgentString(string features = null)
// Do not change the cosmos-netstandard-sdk as it is required for reporting
string baseUserAgent = $"cosmos-netstandard-sdk/{clientVersion}" + Regex.Replace($"|{directVersion}|{clientId}|{processArchitecture}|{operatingSystem}|{runtimeFramework}|", @"[^0-9a-zA-Z\.\|\-]+", " ");

if (!string.IsNullOrEmpty(regionConfiguration))
{
baseUserAgent += $"{regionConfiguration}|";
}

if (!string.IsNullOrEmpty(features))
{
baseUserAgent += $"F {features}|";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,105 @@ public void VerifyUserAgentContent()
Assert.AreEqual(envInfo.RuntimeFramework, values[5]);
}

[TestMethod]
public async Task VerifyUserAgentWithRegionConfiguration()
{
string databaseName = Guid.NewGuid().ToString();
string containerName = Guid.NewGuid().ToString();

{
CosmosClientOptions cosmosClientOptions = new CosmosClientOptions();

// N - None. The user did not configure anything
string userAgentContentToValidate = "|N|";
await this.ValidateUserAgentStringAsync(
cosmosClientOptions,
userAgentContentToValidate,
databaseName,
containerName);
}

{
CosmosClientOptions cosmosClientOptions = new CosmosClientOptions
{
LimitToEndpoint = true
};
// D - Disabled endpoint discovery, N - None. The user did not configure anything
string userAgentContentToValidate = "|DN|";
await this.ValidateUserAgentStringAsync(
cosmosClientOptions,
userAgentContentToValidate,
databaseName,
containerName);
}

{
CosmosClientOptions cosmosClientOptions = new CosmosClientOptions
{
ApplicationRegion = Regions.EastUS
};

// S - Single application region is set
string userAgentContentToValidate = "|S|";
await this.ValidateUserAgentStringAsync(
cosmosClientOptions,
userAgentContentToValidate,
databaseName,
containerName);
}

{
CosmosClientOptions cosmosClientOptions = new CosmosClientOptions
{
LimitToEndpoint = false,
ApplicationRegion = null,
ApplicationPreferredRegions = new List<string>()
{
Regions.EastUS,
Regions.WestUS
}
};

// L - List of region is set
string userAgentContentToValidate = "|L|";
await this.ValidateUserAgentStringAsync(
cosmosClientOptions,
userAgentContentToValidate,
databaseName,
containerName);
}

using (CosmosClient client = TestCommon.CreateCosmosClient())
{
await client.GetDatabase(databaseName).DeleteStreamAsync();
}
}

private async Task ValidateUserAgentStringAsync(
CosmosClientOptions cosmosClientOptions,
string userAgentContentToValidate,
string databaseName,
string containerName)
{
HttpClientHandlerHelper httpClientHandlerHelper = new HttpClientHandlerHelper()
{
RequestCallBack = (request, cancellationToken) =>
{
string userAgent = request.Headers.UserAgent.ToString();
Assert.IsTrue(userAgent.Contains(userAgentContentToValidate));
return null;
}
};

cosmosClientOptions.HttpClientFactory = () => new HttpClient(httpClientHandlerHelper);

using (CosmosClient client = TestCommon.CreateCosmosClient(cosmosClientOptions))
{
Cosmos.Database db = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await db.CreateContainerIfNotExistsAsync(containerName, "/pk");
}
}

[TestMethod]
[DataRow(true, true)]
[DataRow(true, false)]
Expand Down Expand Up @@ -174,7 +273,7 @@ internal override ConnectionPolicy GetConnectionPolicy()
{
ConnectionPolicy connectionPolicy = base.GetConnectionPolicy();
MacOsUserAgentContainer userAgent = new MacOsUserAgentContainer();

this.SetUserAgentFeatures(userAgent);

connectionPolicy.UserAgentContainer = userAgent;
Expand Down

0 comments on commit f594027

Please sign in to comment.