From d7a954f8ec216f7494e0bc6ff6762e9fce081822 Mon Sep 17 00:00:00 2001 From: Achint-Agrawal <45819170+Achint-Agrawal@users.noreply.github.com> Date: Wed, 31 Jan 2024 06:17:47 +0530 Subject: [PATCH] PriorityBasedExecution: Adds PriorityLevel in CosmosClientOptions (#4262) * Added PriorityLevel in CosmosClientoptions * Addressed comments on PR * Changed remark to remarks * Corrected null check * Updated contracts * reverted changes to PartitionKeyHashBaselineTest.Lists.xml * Update Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs Co-authored-by: Matias Quaranta * Update Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs Co-authored-by: Matias Quaranta * Making priority level as GA contract * fixing the preview contract API * resetting few misc files * Builder and contract changes: --------- Co-authored-by: Achint Agrawal Co-authored-by: Debdatta Kunda <87335885+kundadebdatta@users.noreply.github.com> Co-authored-by: Kiran Kumar Kolli Co-authored-by: Matias Quaranta --- .../src/CosmosClientOptions.cs | 64 ++++++++------ .../src/Fluent/CosmosClientBuilder.cs | 16 ++++ .../src/Handler/ClientPipelineBuilder.cs | 6 +- .../src/Handler/RequestInvokerHandler.cs | 26 +++++- .../src/RequestOptions/RequestOptions.cs | 8 +- .../src/Resource/ClientContextCore.cs | 1 + .../src/Resource/Settings/PriorityLevel.cs | 8 +- .../Contracts/DotNetPreviewSDKAPI.json | 46 ---------- .../Contracts/DotNetSDKAPI.json | 64 ++++++++++++++ .../CosmosClientOptionsUnitTests.cs | 38 ++++++++- .../HandlerTests.cs | 84 +++++++++++++++++-- .../RetryHandlerTests.cs | 15 ++-- 12 files changed, 271 insertions(+), 105 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs index 8700f2e56e..a6da426992 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs @@ -280,6 +280,16 @@ public ConnectionMode ConnectionMode /// public ConsistencyLevel? ConsistencyLevel { get; set; } + /// + /// Sets the priority level for requests created using cosmos client. + /// + /// + /// If priority level is also set at request level in , that priority is used. + /// If is set to true in CosmosClientOptions, priority level set on the CosmosClient is used. + /// + /// + public PriorityLevel? PriorityLevel { get; set; } + /// /// Gets or sets the maximum number of retries in the case where the request fails /// because the Azure Cosmos DB service has applied rate limiting on the client. @@ -731,8 +741,8 @@ internal Protocol ConnectionProtocol /// Flag that controls whether CPU monitoring thread is created to enrich timeout exceptions with additional diagnostic. Default value is true. /// internal bool? EnableCpuMonitor { get; set; } - - /// + + /// /// Flag indicates the value of DisableServerCertificateValidation flag set at connection string level.Default it is false. /// internal bool DisableServerCertificateValidation { get; set; } @@ -852,13 +862,13 @@ internal virtual ConnectionPolicy GetConnectionPolicy(int clientId) return (Documents.ConsistencyLevel)this.ConsistencyLevel.Value; } - internal static string GetAccountEndpoint(string connectionString) - { + internal static string GetAccountEndpoint(string connectionString) + { return CosmosClientOptions.GetValueFromConnectionString(connectionString, CosmosClientOptions.ConnectionStringAccountEndpoint, null); - } - - internal static string GetAccountKey(string connectionString) - { + } + + internal static string GetAccountKey(string connectionString) + { return CosmosClientOptions.GetValueFromConnectionString(connectionString, CosmosClientOptions.ConnectionStringAccountKey, null); } @@ -876,21 +886,21 @@ internal static CosmosClientOptions GetCosmosClientOptionsWithCertificateFlag(st } return clientOptions; - } - + } + private static T GetValueFromConnectionString(string connectionString, string keyName, T defaultValue) - { - if (connectionString == null) - { - throw new ArgumentNullException(nameof(connectionString)); - } - - DbConnectionStringBuilder builder = new DbConnectionStringBuilder { ConnectionString = connectionString }; - if (builder.TryGetValue(keyName, out object value)) - { - string keyNameValue = value as string; - if (!string.IsNullOrEmpty(keyNameValue)) - { + { + if (connectionString == null) + { + throw new ArgumentNullException(nameof(connectionString)); + } + + DbConnectionStringBuilder builder = new DbConnectionStringBuilder { ConnectionString = connectionString }; + if (builder.TryGetValue(keyName, out object value)) + { + string keyNameValue = value as string; + if (!string.IsNullOrEmpty(keyNameValue)) + { try { return (T)Convert.ChangeType(value, typeof(T)); @@ -905,9 +915,9 @@ private static T GetValueFromConnectionString(string connectionString, string if (defaultValue != null) { return defaultValue; - } - - throw new ArgumentException("The connection string is missing a required property: " + keyName); + } + + throw new ArgumentException("The connection string is missing a required property: " + keyName); } private void ValidateLimitToEndpointSettings() @@ -935,8 +945,8 @@ private void ValidatePartitionLevelFailoverSettings() { throw new ArgumentException($"{nameof(this.ApplicationPreferredRegions)} is required when {nameof(this.EnablePartitionLevelFailover)} is enabled."); } - } - + } + private void ValidateAndSetServerCallbackSettings() { if (this.DisableServerCertificateValidation && this.ServerCertificateCustomValidationCallback != null) diff --git a/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs b/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs index e030411338..a4d0381e6c 100644 --- a/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs @@ -442,6 +442,22 @@ public CosmosClientBuilder WithConsistencyLevel(Cosmos.ConsistencyLevel consiste } + /// + /// Sets the priority level for requests created using cosmos client. + /// + /// + /// If priority level is also set at request level in , that priority is used. + /// If is set to true, priority level set on the CosmosClient is used. + /// + /// The desired priority level for the client. + /// The current . + /// + public CosmosClientBuilder WithPriorityLevel(Cosmos.PriorityLevel priorityLevel) + { + this.clientOptions.PriorityLevel = priorityLevel; + return this; + } + /// /// Sets the connection mode to Gateway. This is used by the client when connecting to the Azure Cosmos DB service. /// diff --git a/Microsoft.Azure.Cosmos/src/Handler/ClientPipelineBuilder.cs b/Microsoft.Azure.Cosmos/src/Handler/ClientPipelineBuilder.cs index aa69b770e1..5427700911 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/ClientPipelineBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/ClientPipelineBuilder.cs @@ -15,6 +15,7 @@ internal class ClientPipelineBuilder { private readonly CosmosClient client; private readonly ConsistencyLevel? requestedClientConsistencyLevel; + private readonly PriorityLevel? requestedPriorityLevel; private readonly DiagnosticsHandler diagnosticsHandler; private readonly RequestHandler invalidPartitionExceptionRetryHandler; private readonly RequestHandler transportHandler; @@ -26,11 +27,13 @@ internal class ClientPipelineBuilder public ClientPipelineBuilder( CosmosClient client, ConsistencyLevel? requestedClientConsistencyLevel, + PriorityLevel? requestedClientPriorityLevel, IReadOnlyCollection customHandlers, TelemetryToServiceHelper telemetryToServiceHelper) { this.client = client ?? throw new ArgumentNullException(nameof(client)); this.requestedClientConsistencyLevel = requestedClientConsistencyLevel; + this.requestedPriorityLevel = requestedClientPriorityLevel; this.transportHandler = new TransportHandler(client); Debug.Assert(this.transportHandler.InnerHandler == null, nameof(this.transportHandler)); @@ -149,7 +152,8 @@ public RequestInvokerHandler Build() { RequestInvokerHandler root = new RequestInvokerHandler( this.client, - this.requestedClientConsistencyLevel); + this.requestedClientConsistencyLevel, + this.requestedPriorityLevel); RequestHandler current = root; if (this.CustomHandlers != null && this.CustomHandlers.Any()) diff --git a/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs b/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs index bb322f19b1..74888b5316 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs @@ -27,16 +27,19 @@ internal class RequestInvokerHandler : RequestHandler private readonly CosmosClient client; private readonly Cosmos.ConsistencyLevel? RequestedClientConsistencyLevel; + private readonly Cosmos.PriorityLevel? RequestedClientPriorityLevel; private bool? IsLocalQuorumConsistency; private Cosmos.ConsistencyLevel? AccountConsistencyLevel = null; public RequestInvokerHandler( CosmosClient client, - Cosmos.ConsistencyLevel? requestedClientConsistencyLevel) + Cosmos.ConsistencyLevel? requestedClientConsistencyLevel, + Cosmos.PriorityLevel? requestedClientPriorityLevel) { this.client = client; this.RequestedClientConsistencyLevel = requestedClientConsistencyLevel; + this.RequestedClientPriorityLevel = requestedClientPriorityLevel; } public override async Task SendAsync( @@ -66,6 +69,8 @@ public override async Task SendAsync( } await this.ValidateAndSetConsistencyLevelAsync(request); + this.SetPriorityLevel(request); + (bool isError, ResponseMessage errorResponse) = await this.EnsureValidClientAsync(request, request.Trace); if (isError) { @@ -431,6 +436,25 @@ private async Task ValidateAndSetConsistencyLevelAsync(RequestMessage requestMes } } + /// + /// Set the PriorityLevel in the request headers + /// + /// + private void SetPriorityLevel(RequestMessage requestMessage) + { + Cosmos.PriorityLevel? priorityLevel = this.RequestedClientPriorityLevel; + RequestOptions promotedRequestOptions = requestMessage.RequestOptions; + if (promotedRequestOptions?.PriorityLevel.HasValue == true) + { + priorityLevel = promotedRequestOptions.PriorityLevel.Value; + } + + if (priorityLevel.HasValue) + { + requestMessage.Headers.Set(HttpConstants.HttpHeaders.PriorityLevel, priorityLevel.ToString()); + } + } + internal static bool ShouldSetNoContentResponseHeaders(RequestOptions requestOptions, CosmosClientOptions clientOptions, OperationType operationType, diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/RequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/RequestOptions.cs index 2502c75823..dffda96fab 100644 --- a/Microsoft.Azure.Cosmos/src/RequestOptions/RequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/RequestOptions/RequestOptions.cs @@ -47,6 +47,7 @@ public class RequestOptions /// /// Setting priority level only has an effect if Priority Based Execution is enabled. /// If it is not enabled, the priority level is ignored by the backend. + /// If is set to true on CosmosClient, priority level set in RequestOptions is ignored. /// Default PriorityLevel for each request is treated as High. It can be explicitly set to Low for some requests. /// When Priority based execution is enabled, if there are more requests than the configured RU/S in a second, /// then Cosmos DB will throttle low priority requests to allow high priority requests to execute. @@ -55,12 +56,7 @@ public class RequestOptions /// configured RU/s, low priority requests start getting throttled first to allow execution of mission critical workloads. /// /// -#if PREVIEW - public -#else - internal -#endif - PriorityLevel? PriorityLevel { get; set; } + public PriorityLevel? PriorityLevel { get; set; } /// /// Threshold values for Distributed Tracing. diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index 9d1b534c07..c9331afacd 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -120,6 +120,7 @@ internal static CosmosClientContext Create( ClientPipelineBuilder clientPipelineBuilder = new ClientPipelineBuilder( cosmosClient, clientOptions.ConsistencyLevel, + clientOptions.PriorityLevel, clientOptions.CustomHandlers, telemetryToServiceHelper: documentClient.telemetryToServiceHelper); diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/PriorityLevel.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/PriorityLevel.cs index 6644d0931f..cfbddb0a86 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/PriorityLevel.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/PriorityLevel.cs @@ -18,13 +18,7 @@ namespace Microsoft.Azure.Cosmos /// configured RU/s, low priority requests start getting throttled first to allow execution of mission critical workloads. /// /// - -#if PREVIEW - public -#else - internal -#endif - enum PriorityLevel + public enum PriorityLevel { /// /// High Priority diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index c102e8075e..fc09ffd7cf 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -466,52 +466,6 @@ } }, "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.PriorityLevel;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { - "Subclasses": {}, - "Members": { - "Int32 value__": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" - }, - "Microsoft.Azure.Cosmos.PriorityLevel High": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.PriorityLevel High;IsInitOnly:False;IsStatic:True;" - }, - "Microsoft.Azure.Cosmos.PriorityLevel Low": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.PriorityLevel Low;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.RequestOptions;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] get_PriorityLevel()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] get_PriorityLevel();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] PriorityLevel": { - "Type": "Property", - "Attributes": [], - "MethodInfo": "System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] PriorityLevel;CanRead:True;CanWrite:True;System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] get_PriorityLevel();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_PriorityLevel(System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Void set_PriorityLevel(System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Void set_PriorityLevel(System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - } - }, - "NestedTypes": {} } }, "Members": {}, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index f49a470177..3ce587dcd3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -2854,6 +2854,18 @@ "Attributes": [], "MethodInfo": "System.Nullable`1[Microsoft.Azure.Cosmos.PortReuseMode] PortReuseMode;CanRead:True;CanWrite:True;System.Nullable`1[Microsoft.Azure.Cosmos.PortReuseMode] get_PortReuseMode();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_PortReuseMode(System.Nullable`1[Microsoft.Azure.Cosmos.PortReuseMode]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] get_PriorityLevel()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] get_PriorityLevel();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] PriorityLevel": { + "Type": "Property", + "Attributes": [], + "MethodInfo": "System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] PriorityLevel;CanRead:True;CanWrite:True;System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] get_PriorityLevel();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_PriorityLevel(System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "System.Nullable`1[System.Boolean] EnableContentResponseOnWrite": { "Type": "Property", "Attributes": [], @@ -3096,6 +3108,13 @@ "Attributes": [], "MethodInfo": "Void set_PortReuseMode(System.Nullable`1[Microsoft.Azure.Cosmos.PortReuseMode]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "Void set_PriorityLevel(System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_PriorityLevel(System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Void set_RequestTimeout(System.TimeSpan)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ @@ -4599,6 +4618,11 @@ "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithLimitToEndpoint(Boolean);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithPriorityLevel(Microsoft.Azure.Cosmos.PriorityLevel)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithPriorityLevel(Microsoft.Azure.Cosmos.PriorityLevel);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithRequestTimeout(System.TimeSpan)": { "Type": "Method", "Attributes": [], @@ -6396,6 +6420,27 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.PriorityLevel;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { + "Subclasses": {}, + "Members": { + "Int32 value__": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" + }, + "Microsoft.Azure.Cosmos.PriorityLevel High": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.PriorityLevel High;IsInitOnly:False;IsStatic:True;" + }, + "Microsoft.Azure.Cosmos.PriorityLevel Low": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.PriorityLevel Low;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.QueryDefinition;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { @@ -8046,6 +8091,18 @@ ], "MethodInfo": "System.Collections.Generic.List`1[System.String] get_ExcludeRegions();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] get_PriorityLevel()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] get_PriorityLevel();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] PriorityLevel": { + "Type": "Property", + "Attributes": [], + "MethodInfo": "System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] PriorityLevel;CanRead:True;CanWrite:True;System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel] get_PriorityLevel();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_PriorityLevel(System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "System.String get_IfMatchEtag()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ @@ -8110,6 +8167,13 @@ ], "MethodInfo": "Void set_IfNoneMatchEtag(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "Void set_PriorityLevel(System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_PriorityLevel(System.Nullable`1[Microsoft.Azure.Cosmos.PriorityLevel]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Void set_Properties(System.Collections.Generic.IReadOnlyDictionary`2[System.String,System.Object])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ 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 b0b6b8be35..53ed1c343d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs @@ -54,6 +54,7 @@ public void VerifyCosmosConfigurationPropertiesGetUpdated() Cosmos.PortReuseMode portReuseMode = Cosmos.PortReuseMode.PrivatePortPool; IWebProxy webProxy = new TestWebProxy(); Cosmos.ConsistencyLevel consistencyLevel = Cosmos.ConsistencyLevel.ConsistentPrefix; + Cosmos.PriorityLevel priorityLevel = Cosmos.PriorityLevel.Low; CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder( accountEndpoint: endpoint, @@ -82,6 +83,7 @@ public void VerifyCosmosConfigurationPropertiesGetUpdated() Assert.IsTrue(clientOptions.EnableTcpConnectionEndpointRediscovery); Assert.IsNull(clientOptions.HttpClientFactory); Assert.AreNotEqual(consistencyLevel, clientOptions.ConsistencyLevel); + Assert.AreNotEqual(priorityLevel, clientOptions.PriorityLevel); Assert.IsFalse(clientOptions.EnablePartitionLevelFailover); Assert.IsFalse(clientOptions.EnableAdvancedReplicaSelectionForTcp.HasValue); @@ -117,7 +119,8 @@ public void VerifyCosmosConfigurationPropertiesGetUpdated() .WithThrottlingRetryOptions(maxRetryWaitTime, maxRetryAttemptsOnThrottledRequests) .WithBulkExecution(true) .WithSerializerOptions(cosmosSerializerOptions) - .WithConsistencyLevel(consistencyLevel); + .WithConsistencyLevel(consistencyLevel) + .WithPriorityLevel(priorityLevel); cosmosClient = cosmosClientBuilder.Build(new MockDocumentClient()); clientOptions = cosmosClient.ClientOptions; @@ -140,6 +143,7 @@ public void VerifyCosmosConfigurationPropertiesGetUpdated() Assert.IsTrue(object.ReferenceEquals(webProxy, clientOptions.WebProxy)); Assert.IsTrue(clientOptions.AllowBulkExecution); Assert.AreEqual(consistencyLevel, clientOptions.ConsistencyLevel); + Assert.AreEqual(priorityLevel, clientOptions.PriorityLevel); Assert.IsFalse(clientOptions.EnablePartitionLevelFailover); Assert.IsTrue(clientOptions.EnableAdvancedReplicaSelectionForTcp.HasValue && clientOptions.EnableAdvancedReplicaSelectionForTcp.Value); @@ -238,6 +242,8 @@ public void CosmosClientOptions_WhenPartitionLevelFailoverEnabledAndPreferredReg }; Cosmos.ConsistencyLevel consistencyLevel = Cosmos.ConsistencyLevel.ConsistentPrefix; + Cosmos.PriorityLevel priorityLevel = Cosmos.PriorityLevel.Low; + CosmosClientBuilder cosmosClientBuilder = new( accountEndpoint: endpoint, authKeyOrResourceToken: key); @@ -250,7 +256,8 @@ public void CosmosClientOptions_WhenPartitionLevelFailoverEnabledAndPreferredReg .WithApiType(apiType) .WithThrottlingRetryOptions(maxRetryWaitTime, maxRetryAttemptsOnThrottledRequests) .WithSerializerOptions(cosmosSerializerOptions) - .WithConsistencyLevel(consistencyLevel); + .WithConsistencyLevel(consistencyLevel) + .WithPriorityLevel(priorityLevel); if (!useEnvironmentVariable) { @@ -302,6 +309,7 @@ public void CosmosClientOptions_WhenPartitionLevelFailoverEnabledAndPreferredReg }; Cosmos.ConsistencyLevel consistencyLevel = Cosmos.ConsistencyLevel.ConsistentPrefix; + Cosmos.PriorityLevel priorityLevel = Cosmos.PriorityLevel.Low; CosmosClientBuilder cosmosClientBuilder = new( accountEndpoint: endpoint, authKeyOrResourceToken: key); @@ -315,6 +323,7 @@ public void CosmosClientOptions_WhenPartitionLevelFailoverEnabledAndPreferredReg .WithThrottlingRetryOptions(maxRetryWaitTime, maxRetryAttemptsOnThrottledRequests) .WithSerializerOptions(cosmosSerializerOptions) .WithConsistencyLevel(consistencyLevel) + .WithPriorityLevel(priorityLevel) .WithPartitionLevelFailoverEnabled() .WithApplicationPreferredRegions( new List() @@ -372,6 +381,31 @@ public void VerifyConsisentencyLevels() Assert.IsNull(cosmosClientOptionsNull.GetDocumentsConsistencyLevel()); } + + [TestMethod] + public void VerifyPriorityLevels() + { + List cosmosLevels = Enum.GetValues(typeof(Cosmos.PriorityLevel)).Cast().ToList(); + List documentLevels = Enum.GetValues(typeof(Documents.PriorityLevel)).Cast().ToList(); + CollectionAssert.AreEqual(cosmosLevels, documentLevels, new EnumComparer(), "Document priority level is different from cosmos priority level"); + + foreach (Cosmos.PriorityLevel priorityLevel in cosmosLevels) + { + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions() + { + PriorityLevel = priorityLevel + }; + + Assert.AreEqual(priorityLevel, cosmosClientOptions.PriorityLevel); + } + + CosmosClientOptions cosmosClientOptionsNull = new CosmosClientOptions() + { + PriorityLevel = null + }; + + Assert.IsNull(cosmosClientOptionsNull.PriorityLevel); + } [TestMethod] public void VerifyPortReuseModeIsSyncedWithDirect() diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs index d3d0e45653..fa4dd942a3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs @@ -126,7 +126,7 @@ public async Task RequestOptionsHandlerCanHandleRequestOptions() CosmosClient client = MockCosmosUtil.CreateMockCosmosClient(); - RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null) + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null, requestedClientPriorityLevel: null) { InnerHandler = testHandler }; @@ -176,7 +176,7 @@ public async Task RequestOptionsConsistencyLevel() return TestHandler.ReturnSuccess(); }); - RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null) + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null, requestedClientPriorityLevel: null) { InnerHandler = testHandler }; @@ -227,7 +227,7 @@ public async Task QueryRequestOptionsDedicatedGatewayRequestOptions() using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient(); - RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null) + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null, requestedClientPriorityLevel: null) { InnerHandler = testHandler }; @@ -257,7 +257,7 @@ public async Task QueryRequestOptionsSessionToken() using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient(); - RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null) + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null, requestedClientPriorityLevel: null) { InnerHandler = testHandler }; @@ -285,7 +285,7 @@ public async Task ConsistencyLevelClient() return TestHandler.ReturnSuccess(); }); - RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: client.ClientOptions.ConsistencyLevel) + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: client.ClientOptions.ConsistencyLevel, requestedClientPriorityLevel: null) { InnerHandler = testHandler }; @@ -301,6 +301,73 @@ public async Task ConsistencyLevelClient() } } + [TestMethod] + public async Task PriorityLevelClient() + { + List cosmosLevels = Enum.GetValues(typeof(Cosmos.PriorityLevel)).Cast().ToList(); + foreach (Cosmos.PriorityLevel clientLevel in cosmosLevels) + { + using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient( + accountConsistencyLevel: null, + customizeClientBuilder: builder => builder.WithPriorityLevel(clientLevel)); + + TestHandler testHandler = new TestHandler((request, cancellationToken) => + { + Assert.AreEqual(clientLevel.ToString(), request.Headers[HttpConstants.HttpHeaders.PriorityLevel]); + return TestHandler.ReturnSuccess(); + }); + + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null, requestedClientPriorityLevel: client.ClientOptions.PriorityLevel) + { + InnerHandler = testHandler + }; + + RequestMessage requestMessage = new RequestMessage(HttpMethod.Get, new System.Uri("https://dummy.documents.azure.com:443/dbs")) + { + ResourceType = ResourceType.Document + }; + requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]"); + requestMessage.OperationType = OperationType.Read; + + await invoker.SendAsync(requestMessage, new CancellationToken()); + } + } + + [TestMethod] + public async Task TestRequestPriorityLevelTakesPrecedence() + { + Cosmos.PriorityLevel clientLevel = Cosmos.PriorityLevel.Low; + Cosmos.PriorityLevel requestLevel = Cosmos.PriorityLevel.High; + + using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient( + accountConsistencyLevel: null, + customizeClientBuilder: builder => builder.WithPriorityLevel(clientLevel)); + + TestHandler testHandler = new TestHandler((request, cancellationToken) => + { + Assert.AreEqual(requestLevel.ToString(), request.Headers[HttpConstants.HttpHeaders.PriorityLevel]); + return TestHandler.ReturnSuccess(); + }); + + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null, requestedClientPriorityLevel: client.ClientOptions.PriorityLevel) + { + InnerHandler = testHandler + }; + + RequestMessage requestMessage = new RequestMessage(HttpMethod.Get, new System.Uri("https://dummy.documents.azure.com:443/dbs")) + { + ResourceType = ResourceType.Document + }; + requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]"); + requestMessage.OperationType = OperationType.Read; + requestMessage.RequestOptions = new RequestOptions + { + PriorityLevel = requestLevel + }; + + await invoker.SendAsync(requestMessage, new CancellationToken()); + } + [TestMethod] public async Task ConsistencyLevelClientAndRequestOption() { @@ -315,7 +382,7 @@ public async Task ConsistencyLevelClientAndRequestOption() return TestHandler.ReturnSuccess(); }); - RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null) + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null, requestedClientPriorityLevel: null) { InnerHandler = testHandler }; @@ -353,7 +420,7 @@ public async Task RequestOptionsHandlerCanHandleDataPlaneRequestOptions() using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient(); - RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null) + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null, requestedClientPriorityLevel: null) { InnerHandler = testHandler }; @@ -544,7 +611,8 @@ private static async Task TestResolveFeedRangeBasedOnPrefixAsync( RequestInvokerHandler invoker = new( client: client, - requestedClientConsistencyLevel: default); + requestedClientConsistencyLevel: default, + requestedClientPriorityLevel: default); Cosmos.FeedRange feedRange = await RequestInvokerHandler.ResolveFeedRangeBasedOnPrefixContainerAsync( feedRange: inputFeedRange, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/RetryHandlerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/RetryHandlerTests.cs index 109b5c5ebe..6e5bd4bac3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/RetryHandlerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/RetryHandlerTests.cs @@ -33,7 +33,7 @@ public async Task RetryHandlerDoesNotRetryOnSuccess() }); retryHandler.InnerHandler = testHandler; - RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null); + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null, requestedClientPriorityLevel: null); invoker.InnerHandler = retryHandler; RequestMessage requestMessage = new RequestMessage(HttpMethod.Delete, RetryHandlerTests.TestUri); requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]"); @@ -63,7 +63,7 @@ public async Task RetryHandlerRetriesOn429() }); retryHandler.InnerHandler = testHandler; - RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null); + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null, requestedClientPriorityLevel: null); invoker.InnerHandler = retryHandler; RequestMessage requestMessage = new RequestMessage(HttpMethod.Delete, RetryHandlerTests.TestUri); requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]"); @@ -93,7 +93,7 @@ public async Task RetryHandlerDoesNotRetryOnException() }); retryHandler.InnerHandler = testHandler; - RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null); + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null, requestedClientPriorityLevel: null); invoker.InnerHandler = retryHandler; RequestMessage requestMessage = new RequestMessage(HttpMethod.Get, new System.Uri("https://dummy.documents.azure.com:443/dbs")); requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]"); @@ -135,7 +135,7 @@ public async Task RetryHandlerHttpClientExceptionRefreshesLocations() }); retryHandler.InnerHandler = testHandler; - RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null); + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null, requestedClientPriorityLevel: null); invoker.InnerHandler = retryHandler; RequestMessage requestMessage = new RequestMessage(HttpMethod.Get, new System.Uri("https://dummy.documents.azure.com:443/dbs")); requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]"); @@ -189,7 +189,7 @@ private async Task RetryHandlerDontRetryOnStatusCode( RetryHandler retryHandler = new RetryHandler(client); retryHandler.InnerHandler = testHandler; - RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null); + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null, requestedClientPriorityLevel: null); invoker.InnerHandler = retryHandler; RequestMessage requestMessage = new RequestMessage(HttpMethod.Delete, RetryHandlerTests.TestUri); requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]"); @@ -217,7 +217,8 @@ public async Task InvalidPartitionExceptionRetryHandlerDoesNotRetryOnSuccess() retryHandler.InnerHandler = testHandler; RequestInvokerHandler invoker = new RequestInvokerHandler( client, - requestedClientConsistencyLevel: null) + requestedClientConsistencyLevel: null, + requestedClientPriorityLevel: null) { InnerHandler = retryHandler }; @@ -249,7 +250,7 @@ public async Task InvalidPartitionExceptionRetryHandlerDoesNotRetryOn410() }); retryHandler.InnerHandler = testHandler; - RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null); + RequestInvokerHandler invoker = new RequestInvokerHandler(client, requestedClientConsistencyLevel: null, requestedClientPriorityLevel: null); invoker.InnerHandler = retryHandler; RequestMessage requestMessage = new RequestMessage(HttpMethod.Get, new Uri("https://dummy.documents.azure.com:443/dbs"));