diff --git a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs index b074e6d059..8d845d9297 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs @@ -68,7 +68,6 @@ public class CosmosClientOptions /// public CosmosClientOptions() { - this.UserAgentContainer = new Cosmos.UserAgentContainer(); this.GatewayModeMaxConnectionLimit = ConnectionPolicy.Default.MaxConnectionLimit; this.RequestTimeout = ConnectionPolicy.Default.RequestTimeout; this.ConnectionMode = CosmosClientOptions.DefaultConnectionMode; @@ -83,11 +82,7 @@ public CosmosClientOptions() /// /// Setting this property after sending any request won't have any effect. /// - public string ApplicationName - { - get => this.UserAgentContainer.Suffix; - set => this.UserAgentContainer.Suffix = value; - } + public string ApplicationName { get; set; } /// /// Get or set the preferred geo-replicated region to be used for Azure Cosmos DB service interaction. @@ -398,8 +393,6 @@ internal Protocol ConnectionProtocol } } - internal UserAgentContainer UserAgentContainer { get; private set; } - /// /// The event handler to be invoked before the request is sent. /// @@ -509,13 +502,15 @@ internal ConnectionPolicy GetConnectionPolicy() { this.ValidateDirectTCPSettings(); this.ValidateLimitToEndpointSettings(); + UserAgentContainer userAgent = this.BuildUserAgentContainer(); + ConnectionPolicy connectionPolicy = new ConnectionPolicy() { MaxConnectionLimit = this.GatewayModeMaxConnectionLimit, RequestTimeout = this.RequestTimeout, ConnectionMode = this.ConnectionMode, ConnectionProtocol = this.ConnectionProtocol, - UserAgentContainer = this.UserAgentContainer, + UserAgentContainer = userAgent, UseMultipleWriteLocations = true, IdleTcpConnectionTimeout = this.IdleTcpConnectionTimeout, OpenTcpConnectionTimeout = this.OpenTcpConnectionTimeout, @@ -657,6 +652,40 @@ private void ValidateDirectTCPSettings() } } + internal UserAgentContainer BuildUserAgentContainer() + { + UserAgentContainer userAgent = new UserAgentContainer(); + string features = this.GetUserAgentFeatures(); + + if (!string.IsNullOrEmpty(features)) + { + userAgent.SetFeatures(features.ToString()); + } + + if (!string.IsNullOrEmpty(this.ApplicationName)) + { + userAgent.Suffix = this.ApplicationName; + } + + return userAgent; + } + + private string GetUserAgentFeatures() + { + CosmosClientOptionsFeatures features = CosmosClientOptionsFeatures.NoFeatures; + if (this.AllowBulkExecution) + { + features |= CosmosClientOptionsFeatures.AllowBulkExecution; + } + + if (features == CosmosClientOptionsFeatures.NoFeatures) + { + return null; + } + + return Convert.ToString((int)features, 2).PadLeft(8, '0'); + } + /// /// Serialize the current configuration into a JSON string /// diff --git a/Microsoft.Azure.Cosmos/src/CosmosClientOptionsFeatures.cs b/Microsoft.Azure.Cosmos/src/CosmosClientOptionsFeatures.cs new file mode 100644 index 0000000000..02eef19178 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/CosmosClientOptionsFeatures.cs @@ -0,0 +1,15 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + + [Flags] + internal enum CosmosClientOptionsFeatures + { + NoFeatures = 0, + AllowBulkExecution = 1 + } +} diff --git a/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs b/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs index c40617c2d6..eb70d570ae 100644 --- a/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs @@ -370,7 +370,7 @@ public CosmosClientBuilder WithCustomSerializer(CosmosSerializer cosmosJsonSeria /// Whether is enabled. /// The object /// - public CosmosClientBuilder WithBulkexecution(bool enabled) + public CosmosClientBuilder WithBulkExecution(bool enabled) { this.clientOptions.AllowBulkExecution = enabled; return this; diff --git a/Microsoft.Azure.Cosmos/src/UserAgentContainer.cs b/Microsoft.Azure.Cosmos/src/UserAgentContainer.cs index 3333d304a8..0b8f597f87 100644 --- a/Microsoft.Azure.Cosmos/src/UserAgentContainer.cs +++ b/Microsoft.Azure.Cosmos/src/UserAgentContainer.cs @@ -31,7 +31,13 @@ internal override string BaseUserAgent } } - private string CreateBaseUserAgentString() + internal void SetFeatures(string features) + { + // Regenerate base user agent to account for features + this.cosmosBaseUserAgent = this.CreateBaseUserAgentString(features); + } + + private string CreateBaseUserAgentString(string features = null) { EnvironmentInformation environmentInformation = new EnvironmentInformation(); string operatingSystem = environmentInformation.OperatingSystem; @@ -42,7 +48,14 @@ private string CreateBaseUserAgentString() // Regex replaces all special characters with empty space except . - | since they do not cause format exception for the user agent string. // Do not change the cosmos-netstandard-sdk as it is required for reporting - return $"cosmos-netstandard-sdk/{environmentInformation.ClientVersion}" + Regex.Replace($"|{environmentInformation.DirectVersion}|{environmentInformation.ClientId}|{environmentInformation.ProcessArchitecture}|{operatingSystem}|{environmentInformation.RuntimeFramework}|", @"[^0-9a-zA-Z\.\|\-]+", " "); + string baseUserAgent = $"cosmos-netstandard-sdk/{environmentInformation.ClientVersion}" + Regex.Replace($"|{environmentInformation.DirectVersion}|{environmentInformation.ClientId}|{environmentInformation.ProcessArchitecture}|{operatingSystem}|{environmentInformation.RuntimeFramework}|", @"[^0-9a-zA-Z\.\|\-]+", " "); + + if (!string.IsNullOrEmpty(features)) + { + baseUserAgent += $"F {features}|"; + } + + return baseUserAgent; } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/UserAgentTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/UserAgentTests.cs index 6d207ffad5..8d728b43ba 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/UserAgentTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/UserAgentTests.cs @@ -102,6 +102,44 @@ public void VerifyUserAgentContent(bool useMacOs) Assert.AreEqual(envInfo.RuntimeFramework, values[5]); } + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public async Task VerifyUserAgentWithFeatures(bool useMacOs) + { + this.SetEnvironmentInformation(useMacOs); + + const string suffix = " UserApplicationName/1.0"; + + string features = Convert.ToString((int)CosmosClientOptionsFeatures.AllowBulkExecution, 2).PadLeft(8, '0'); + + using (CosmosClient client = TestCommon.CreateCosmosClient(builder => builder.WithApplicationName(suffix).WithBulkExecution(true))) + { + Cosmos.UserAgentContainer userAgentContainer = client.ClientOptions.GetConnectionPolicy().UserAgentContainer; + + string userAgentString = userAgentContainer.UserAgent; + Assert.IsTrue(userAgentString.Contains(suffix)); + Assert.IsTrue(userAgentString.Contains($"|F {features}")); + if (useMacOs) + { + Assert.IsTrue(userAgentString.Contains("Darwin 18.0.0")); + } + + Cosmos.Database db = await client.CreateDatabaseIfNotExistsAsync(Guid.NewGuid().ToString()); + Assert.IsNotNull(db); + await db.DeleteAsync(); + } + + using (CosmosClient client = TestCommon.CreateCosmosClient(builder => builder.WithApplicationName(suffix).WithBulkExecution(false))) + { + Cosmos.UserAgentContainer userAgentContainer = client.ClientOptions.GetConnectionPolicy().UserAgentContainer; + + string userAgentString = userAgentContainer.UserAgent; + Assert.IsTrue(userAgentString.Contains(suffix)); + Assert.IsFalse(userAgentString.Contains($"|F {features}")); + } + } + private void SetEnvironmentInformation(bool useMacOs) { //This changes the runtime information to simulate a max os x response. Windows user agent are tested by every other emulator test. diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs index 3bc589aabf..3400a62a6f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs @@ -89,7 +89,7 @@ public void VerifyCosmosConfigurationPropertiesGetUpdated() .AddCustomHandlers(preProcessHandler) .WithApiType(apiType) .WithThrottlingRetryOptions(maxRetryWaitTime, maxRetryAttemptsOnThrottledRequests) - .WithBulkexecution(true) + .WithBulkExecution(true) .WithSerializerOptions(cosmosSerializerOptions); cosmosClient = cosmosClientBuilder.Build(new MockDocumentClient()); @@ -180,9 +180,10 @@ public void UserAgentContainsEnvironmentInformation() string userAgentSuffix = "testSuffix"; cosmosClientOptions.ApplicationName = userAgentSuffix; Assert.AreEqual(userAgentSuffix, cosmosClientOptions.ApplicationName); - Assert.AreEqual(userAgentSuffix, cosmosClientOptions.UserAgentContainer.Suffix); - Assert.IsTrue(cosmosClientOptions.UserAgentContainer.UserAgent.StartsWith(expectedValue)); - Assert.IsTrue(cosmosClientOptions.UserAgentContainer.UserAgent.EndsWith(userAgentSuffix)); + UserAgentContainer userAgentContainer = cosmosClientOptions.BuildUserAgentContainer(); + Assert.AreEqual(userAgentSuffix, userAgentContainer.Suffix); + Assert.IsTrue(userAgentContainer.UserAgent.StartsWith(expectedValue)); + Assert.IsTrue(userAgentContainer.UserAgent.EndsWith(userAgentSuffix)); ConnectionPolicy connectionPolicy = cosmosClientOptions.GetConnectionPolicy(); Assert.AreEqual(userAgentSuffix, connectionPolicy.UserAgentSuffix); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json index 61a51c246f..d7de0a7688 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json @@ -1415,9 +1415,11 @@ "Attributes": [], "MethodInfo": null }, - "System.String get_ApplicationName()": { + "System.String get_ApplicationName()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", - "Attributes": [], + "Attributes": [ + "CompilerGeneratedAttribute" + ], "MethodInfo": "System.String get_ApplicationName()" }, "System.String get_ApplicationRegion()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { @@ -1451,9 +1453,11 @@ ], "MethodInfo": "Void set_AllowBulkExecution(Boolean)" }, - "Void set_ApplicationName(System.String)": { + "Void set_ApplicationName(System.String)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", - "Attributes": [], + "Attributes": [ + "CompilerGeneratedAttribute" + ], "MethodInfo": "Void set_ApplicationName(System.String)" }, "Void set_ApplicationRegion(System.String)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { @@ -2390,10 +2394,10 @@ "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithApplicationRegion(System.String)" }, - "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithBulkexecution(Boolean)": { + "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithBulkExecution(Boolean)": { "Type": "Method", "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithBulkexecution(Boolean)" + "MethodInfo": "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithBulkExecution(Boolean)" }, "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithConnectionModeDirect()": { "Type": "Method", diff --git a/changelog.md b/changelog.md index cc85828149..8053d760d5 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - [#923](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/923) Bulk Support is now public +- [#922](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/922) Included information of bulk support usage in user agent ## [3.3.2](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.3.2) - 2019-10-16