diff --git a/Microsoft.Azure.Cosmos/src/ConnectionPolicy.cs b/Microsoft.Azure.Cosmos/src/ConnectionPolicy.cs index f77d95b856..6af3af91be 100644 --- a/Microsoft.Azure.Cosmos/src/ConnectionPolicy.cs +++ b/Microsoft.Azure.Cosmos/src/ConnectionPolicy.cs @@ -338,6 +338,16 @@ public bool EnablePartitionLevelCircuitBreaker set; } + /// + /// Gets or sets a value indicating whether to disable Per Partition Automatic Failover (PPAF) explicitly. + /// When set to true, this will be used to disable PPAF irrespective of the account settings. + /// + public bool DisablePartitionLevelFailover + { + get; + set; + } + /// /// Gets or sets the certificate validation callback. /// diff --git a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs index 3f60995b4a..77566e5351 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs @@ -766,12 +766,19 @@ bool EnableRemoteRegionPreferredForSessionRetry } /// - /// Enable partition level circuit breaker (aka PPCB). For compute gateway use case, by default per partition automatic failover will be disabled, so does the PPCB. - /// If compute gateway chooses to enable PPAF, then the .NET SDK will enable PPCB by default, which will improve the read availability and latency. This would mean + /// Enable partition level circuit breaker (aka PPCB). For compute gateway use case, by default per partition automatic failover will be disabled, so does the PPCB. + /// If compute gateway chooses to enable PPAF, then the .NET SDK will enable PPCB by default, which will improve the read availability and latency. This would mean /// when PPAF is enabled, the SDK will automatically enable PPCB as well. /// internal bool EnablePartitionLevelCircuitBreaker { get; set; } = ConfigurationManager.IsPartitionLevelCircuitBreakerEnabled(defaultValue: false); + /// + /// Internal option to disable Per Partition Automatic Failover (PPAF) explicitly. + /// When set to true, this will be used to disable PPAF irrespective of the account settings. + /// The default value for this parameter is 'false'. + /// + internal bool DisablePartitionLevelFailover { get; set; } = false; + /// /// Quorum Read allowed with eventual consistency account or consistent prefix account. /// @@ -1030,6 +1037,7 @@ internal virtual ConnectionPolicy GetConnectionPolicy(int clientId) MaxTcpConnectionsPerEndpoint = this.MaxTcpConnectionsPerEndpoint, EnableEndpointDiscovery = !this.LimitToEndpoint, EnablePartitionLevelCircuitBreaker = this.EnablePartitionLevelCircuitBreaker, + DisablePartitionLevelFailover = this.DisablePartitionLevelFailover, PortReuseMode = this.portReuseMode, EnableTcpConnectionEndpointRediscovery = this.EnableTcpConnectionEndpointRediscovery, EnableAdvancedReplicaSelectionForTcp = this.EnableAdvancedReplicaSelectionForTcp, diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs index e2ae2509a9..3bedaf5d86 100644 --- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs +++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs @@ -1056,13 +1056,11 @@ private async Task GetInitializationTaskAsync(IStoreClientFactory storeCli this.EnsureValidOverwrite(this.desiredConsistencyLevel.Value); } - bool isPPafEnabled = ConfigurationManager.IsPartitionLevelFailoverEnabled(defaultValue: false); - if (this.accountServiceConfiguration != null && this.accountServiceConfiguration.AccountProperties.EnablePartitionLevelFailover.HasValue) - { - isPPafEnabled = this.accountServiceConfiguration.AccountProperties.EnablePartitionLevelFailover.Value; - } - - this.ConnectionPolicy.EnablePartitionLevelFailover = isPPafEnabled; + // Apply the DisablePartitionLevelFailover setting to override PPAF if explicitly disabled + this.ConnectionPolicy.EnablePartitionLevelFailover = !this.ConnectionPolicy.DisablePartitionLevelFailover && + this.accountServiceConfiguration != null && + this.accountServiceConfiguration.AccountProperties.EnablePartitionLevelFailover.HasValue && + this.accountServiceConfiguration.AccountProperties.EnablePartitionLevelFailover.Value; this.ConnectionPolicy.EnablePartitionLevelCircuitBreaker |= this.ConnectionPolicy.EnablePartitionLevelFailover; this.ConnectionPolicy.UserAgentContainer.AppendFeatures(this.GetUserAgentFeatures()); this.InitializePartitionLevelFailoverWithDefaultHedging(); diff --git a/Microsoft.Azure.Cosmos/src/Routing/GlobalEndpointManager.cs b/Microsoft.Azure.Cosmos/src/Routing/GlobalEndpointManager.cs index 10367d11b1..f2eb5bc891 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/GlobalEndpointManager.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/GlobalEndpointManager.cs @@ -602,13 +602,10 @@ public virtual void InitializeAccountPropertiesAndStartBackgroundRefresh(Account return; } - bool isPPafEnabled = ConfigurationManager.IsPartitionLevelFailoverEnabled(defaultValue: false); - if (databaseAccount.EnablePartitionLevelFailover.HasValue) - { - isPPafEnabled = databaseAccount.EnablePartitionLevelFailover.Value; + if (!this.connectionPolicy.DisablePartitionLevelFailover && databaseAccount.EnablePartitionLevelFailover.HasValue) + { + this.connectionPolicy.EnablePartitionLevelFailover = databaseAccount.EnablePartitionLevelFailover.Value; } - - this.connectionPolicy.EnablePartitionLevelFailover = isPPafEnabled; GlobalEndpointManager.ParseThinClientLocationsFromAdditionalProperties(databaseAccount); this.locationCache.OnDatabaseAccountRead(databaseAccount); diff --git a/Microsoft.Azure.Cosmos/src/Routing/GlobalPartitionEndpointManagerCore.cs b/Microsoft.Azure.Cosmos/src/Routing/GlobalPartitionEndpointManagerCore.cs index 3818fe70ad..4b6c715211 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/GlobalPartitionEndpointManagerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/GlobalPartitionEndpointManagerCore.cs @@ -608,7 +608,7 @@ public PartitionKeyRangeFailoverInfo( this.ConsecutiveWriteRequestFailureCount = 0; this.ReadRequestFailureCounterThreshold = ConfigurationManager.GetCircuitBreakerConsecutiveFailureCountForReads(10); this.WriteRequestFailureCounterThreshold = ConfigurationManager.GetCircuitBreakerConsecutiveFailureCountForWrites(5); - this.TimeoutCounterResetWindowInMinutes = TimeSpan.FromMinutes(1); + this.TimeoutCounterResetWindowInMinutes = TimeSpan.FromMinutes(ConfigurationManager.GetCircuitBreakerTimeoutCounterResetWindowInMinutes(5)); this.FirstRequestFailureTime = DateTime.UtcNow; this.LastRequestFailureTime = DateTime.UtcNow; } diff --git a/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs b/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs index f9e8dbe93b..085ff768d9 100644 --- a/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs +++ b/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs @@ -16,11 +16,10 @@ internal static class ConfigurationManager internal static readonly string ReplicaConnectivityValidationEnabled = "AZURE_COSMOS_REPLICA_VALIDATION_ENABLED"; /// - /// A read-only string containing the environment variable name for enabling per partition automatic failover. - /// This will eventually be removed once per partition automatic failover is enabled by default for both preview - /// and GA. + /// A read-only string containing the environment variable name for capturing the PPCB timeout counter reset window time + /// in minutes. The default value for this window is 5 minutes. /// - internal static readonly string PartitionLevelFailoverEnabled = "AZURE_COSMOS_PARTITION_LEVEL_FAILOVER_ENABLED"; + internal static readonly string CircuitBreakerTimeoutCounterResetWindowInMinutes = "AZURE_COSMOS_PPCB_TIMEOUT_COUNTER_RESET_WINDOW_IN_MINUTES"; /// /// A read-only string containing the environment variable name for enabling per partition circuit breaker. The default value @@ -160,19 +159,19 @@ public static bool IsReplicaAddressValidationEnabled( } /// - /// Gets the boolean value of the partition level failover environment variable. Note that, partition level failover - /// is disabled by default for both preview and GA releases. The user can set the respective environment variable - /// 'AZURE_COSMOS_PARTITION_LEVEL_FAILOVER_ENABLED' to override the value for both preview and GA. The method will - /// eventually be removed, once partition level failover is enabled by default for both preview and GA. + /// Gets the PPCB timeout counter reset window in minutes. + /// The default value for this window is 5 minutes. The user can set the respective + /// environment variable 'AZURE_COSMOS_PPCB_TIMEOUT_COUNTER_RESET_WINDOW_IN_MINUTES' + /// to override the value. /// - /// A boolean field containing the default value for partition level failover. - /// A boolean flag indicating if partition level failover is enabled. - public static bool IsPartitionLevelFailoverEnabled( - bool defaultValue) + /// An integer containing the default value for the timeout counter reset window in minutes. + /// An integer representing the timeout counter reset window in minutes. + public static int GetCircuitBreakerTimeoutCounterResetWindowInMinutes( + int defaultValue) { return ConfigurationManager .GetEnvironmentVariable( - variable: ConfigurationManager.PartitionLevelFailoverEnabled, + variable: ConfigurationManager.CircuitBreakerTimeoutCounterResetWindowInMinutes, defaultValue: defaultValue); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ConfigurationManagerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ConfigurationManagerTests.cs new file mode 100644 index 0000000000..a3936b8888 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ConfigurationManagerTests.cs @@ -0,0 +1,49 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Tests +{ + using System; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ConfigurationManagerTests + { + [TestMethod] + public void GetCircuitBreakerTimeoutCounterResetWindowInMinutes_DefaultValue() + { + // Test that the default value is returned when environment variable is not set + int result = ConfigurationManager.GetCircuitBreakerTimeoutCounterResetWindowInMinutes(5); + Assert.AreEqual(5, result); + } + + [TestMethod] + public void GetCircuitBreakerTimeoutCounterResetWindowInMinutes_CustomDefaultValue() + { + // Test that custom default values are respected + int result = ConfigurationManager.GetCircuitBreakerTimeoutCounterResetWindowInMinutes(10); + Assert.AreEqual(10, result); + } + + [TestMethod] + public void GetCircuitBreakerTimeoutCounterResetWindowInMinutes_EnvironmentVariableOverride() + { + // Test that environment variable overrides the default value + const string envVarName = "AZURE_COSMOS_PPCB_TIMEOUT_COUNTER_RESET_WINDOW_IN_MINUTES"; + const string testValue = "15"; + + try + { + Environment.SetEnvironmentVariable(envVarName, testValue); + int result = ConfigurationManager.GetCircuitBreakerTimeoutCounterResetWindowInMinutes(5); + Assert.AreEqual(15, result); + } + finally + { + // Clean up environment variable + Environment.SetEnvironmentVariable(envVarName, null); + } + } + } +} \ No newline at end of file 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 fc2108e9b5..12d219e25d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs @@ -225,143 +225,129 @@ public void VerifyCosmosConfigurationPropertiesGetUpdated() CollectionAssert.AreEqual(regionalEndpoints.ToArray(), policy.AccountInitializationCustomEndpoints.ToArray()); } - /// - /// Test to validate that when the partition level failover is enabled with the preferred regions list is missing, then the client - /// initialization should succeed. This should hold true for both environment variable and CosmosClientOptions. - /// - [TestMethod] - [Owner("dkunda")] - public void CosmosClientOptions_WhenPartitionLevelFailoverEnabledAndPreferredRegionsNotSet_ShouldInitializeCosmosClientSuccessfully() - { - try - { - Environment.SetEnvironmentVariable(ConfigurationManager.PartitionLevelFailoverEnabled, "True"); - - string endpoint = AccountEndpoint; - string key = MockCosmosUtil.RandomInvalidCorrectlyFormatedAuthKey; - TimeSpan requestTimeout = TimeSpan.FromDays(1); - string userAgentSuffix = "testSuffix"; - RequestHandler preProcessHandler = new TestHandler(); - ApiType apiType = ApiType.Sql; - int maxRetryAttemptsOnThrottledRequests = 9999; - TimeSpan maxRetryWaitTime = TimeSpan.FromHours(6); - CosmosSerializationOptions cosmosSerializerOptions = new CosmosSerializationOptions() - { - IgnoreNullValues = true, - PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase, - }; - - Cosmos.ConsistencyLevel consistencyLevel = Cosmos.ConsistencyLevel.ConsistentPrefix; - Cosmos.PriorityLevel priorityLevel = Cosmos.PriorityLevel.Low; - int throughputBucket = 3; - - CosmosClientBuilder cosmosClientBuilder = new( - accountEndpoint: endpoint, - authKeyOrResourceToken: key); - - cosmosClientBuilder - .WithConnectionModeDirect() - .WithRequestTimeout(requestTimeout) - .WithApplicationName(userAgentSuffix) - .AddCustomHandlers(preProcessHandler) - .WithApiType(apiType) - .WithThrottlingRetryOptions(maxRetryWaitTime, maxRetryAttemptsOnThrottledRequests) - .WithSerializerOptions(cosmosSerializerOptions) - .WithConsistencyLevel(consistencyLevel) - .WithPriorityLevel(priorityLevel) - .WithThroughputBucket(throughputBucket); - - CosmosClient cosmosClient = cosmosClientBuilder.Build(); - - Assert.IsNotNull(cosmosClient, - message: "ApplicationPreferredRegions or ApplicationRegion is no longer mandatory fields, hence the client initialization should succeed."); - } - finally - { - Environment.SetEnvironmentVariable(ConfigurationManager.PartitionLevelFailoverEnabled, null); - } + /// + /// Test to validate that when the partition level failover is enabled, client + /// initialization should succeed. Environment variable support was removed. + /// + [TestMethod] + [Owner("dkunda")] + public void CosmosClientOptions_WhenPartitionLevelFailoverEnabledAndPreferredRegionsNotSet_ShouldInitializeCosmosClientSuccessfully() + { + // Note: Environment variable for PPAF was removed. This test now verifies the new approach. + + string endpoint = AccountEndpoint; + string key = MockCosmosUtil.RandomInvalidCorrectlyFormatedAuthKey; + TimeSpan requestTimeout = TimeSpan.FromDays(1); + string userAgentSuffix = "testSuffix"; + RequestHandler preProcessHandler = new TestHandler(); + ApiType apiType = ApiType.Sql; + int maxRetryAttemptsOnThrottledRequests = 9999; + TimeSpan maxRetryWaitTime = TimeSpan.FromHours(6); + CosmosSerializationOptions cosmosSerializerOptions = new CosmosSerializationOptions() + { + IgnoreNullValues = true, + PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase, + }; + + Cosmos.ConsistencyLevel consistencyLevel = Cosmos.ConsistencyLevel.ConsistentPrefix; + Cosmos.PriorityLevel priorityLevel = Cosmos.PriorityLevel.Low; + int throughputBucket = 3; + + CosmosClientBuilder cosmosClientBuilder = new( + accountEndpoint: endpoint, + authKeyOrResourceToken: key); + + cosmosClientBuilder + .WithConnectionModeDirect() + .WithRequestTimeout(requestTimeout) + .WithApplicationName(userAgentSuffix) + .AddCustomHandlers(preProcessHandler) + .WithApiType(apiType) + .WithThrottlingRetryOptions(maxRetryWaitTime, maxRetryAttemptsOnThrottledRequests) + .WithSerializerOptions(cosmosSerializerOptions) + .WithConsistencyLevel(consistencyLevel) + .WithPriorityLevel(priorityLevel) + .WithThroughputBucket(throughputBucket); + + CosmosClient cosmosClient = cosmosClientBuilder.Build(); + + Assert.IsNotNull(cosmosClient, + message: "ApplicationPreferredRegions or ApplicationRegion is no longer mandatory fields, hence the client initialization should succeed."); } - /// - /// Test to validate that when the partition level failover is enabled with the preferred regions list is provided, then the client - /// initialization should be successful. This holds true for both environment variable and CosmosClientOptions. - /// - [TestMethod] - [Owner("dkunda")] - public void CosmosClientOptions_WhenPartitionLevelFailoverEnabledAndPreferredRegionsSet_ShouldInitializeSuccessfully() - { - try - { - Environment.SetEnvironmentVariable(ConfigurationManager.PartitionLevelFailoverEnabled, "True"); - - string endpoint = AccountEndpoint; - string key = MockCosmosUtil.RandomInvalidCorrectlyFormatedAuthKey; - TimeSpan requestTimeout = TimeSpan.FromDays(1); - string userAgentSuffix = "testSuffix"; - RequestHandler preProcessHandler = new TestHandler(); - ApiType apiType = ApiType.Sql; - int maxRetryAttemptsOnThrottledRequests = 9999; - TimeSpan maxRetryWaitTime = TimeSpan.FromHours(6); - CosmosSerializationOptions cosmosSerializerOptions = new CosmosSerializationOptions() - { - IgnoreNullValues = true, - PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase, - }; - - Cosmos.ConsistencyLevel consistencyLevel = Cosmos.ConsistencyLevel.ConsistentPrefix; - Cosmos.PriorityLevel priorityLevel = Cosmos.PriorityLevel.Low; - int throughputBucket = 3; - CosmosClientBuilder cosmosClientBuilder = new( - accountEndpoint: endpoint, - authKeyOrResourceToken: key); - - cosmosClientBuilder - .WithConnectionModeDirect() - .WithRequestTimeout(requestTimeout) - .WithApplicationName(userAgentSuffix) - .AddCustomHandlers(preProcessHandler) - .WithApiType(apiType) - .WithThrottlingRetryOptions(maxRetryWaitTime, maxRetryAttemptsOnThrottledRequests) - .WithSerializerOptions(cosmosSerializerOptions) - .WithConsistencyLevel(consistencyLevel) - .WithPriorityLevel(priorityLevel) - .WithThroughputBucket(throughputBucket) - .WithApplicationPreferredRegions( - new List() - { - Regions.NorthCentralUS, - Regions.WestUS, - Regions.EastAsia, - }) - .WithCustomAccountEndpoints( - new HashSet() - { - new Uri("https://testfed2.documents-test.windows-int.net:443/"), - new Uri("https://testfed3.documents-test.windows-int.net:443/"), - new Uri("https://testfed4.documents-test.windows-int.net:443/"), - }); - - CosmosClientOptions clientOptions = cosmosClientBuilder.Build().ClientOptions; - - Assert.AreEqual(ConnectionMode.Direct, clientOptions.ConnectionMode); - Assert.AreEqual(requestTimeout, clientOptions.RequestTimeout); - Assert.AreEqual(userAgentSuffix, clientOptions.ApplicationName); - Assert.AreEqual(preProcessHandler, clientOptions.CustomHandlers[0]); - Assert.AreEqual(apiType, clientOptions.ApiType); - Assert.AreEqual(maxRetryAttemptsOnThrottledRequests, clientOptions.MaxRetryAttemptsOnRateLimitedRequests); - Assert.AreEqual(maxRetryWaitTime, clientOptions.MaxRetryWaitTimeOnRateLimitedRequests); - Assert.AreEqual(cosmosSerializerOptions.IgnoreNullValues, clientOptions.SerializerOptions.IgnoreNullValues); - Assert.AreEqual(cosmosSerializerOptions.PropertyNamingPolicy, clientOptions.SerializerOptions.PropertyNamingPolicy); - Assert.AreEqual(cosmosSerializerOptions.Indented, clientOptions.SerializerOptions.Indented); - Assert.IsFalse(clientOptions.AllowBulkExecution); - Assert.AreEqual(consistencyLevel, clientOptions.ConsistencyLevel); - Assert.IsNotNull(clientOptions.ApplicationPreferredRegions); - Assert.IsNotNull(clientOptions.AccountInitializationCustomEndpoints); - } - finally - { - Environment.SetEnvironmentVariable(ConfigurationManager.PartitionLevelFailoverEnabled, null); - } + /// + /// Test to validate that when the partition level failover is enabled with the preferred regions list is provided, then the client + /// initialization should be successful. Environment variable support was removed. + /// + [TestMethod] + [Owner("dkunda")] + public void CosmosClientOptions_WhenPartitionLevelFailoverEnabledAndPreferredRegionsSet_ShouldInitializeSuccessfully() + { + // Note: Environment variable for PPAF was removed. This test now verifies the new approach. + + string endpoint = AccountEndpoint; + string key = MockCosmosUtil.RandomInvalidCorrectlyFormatedAuthKey; + TimeSpan requestTimeout = TimeSpan.FromDays(1); + string userAgentSuffix = "testSuffix"; + RequestHandler preProcessHandler = new TestHandler(); + ApiType apiType = ApiType.Sql; + int maxRetryAttemptsOnThrottledRequests = 9999; + TimeSpan maxRetryWaitTime = TimeSpan.FromHours(6); + CosmosSerializationOptions cosmosSerializerOptions = new CosmosSerializationOptions() + { + IgnoreNullValues = true, + PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase, + }; + + Cosmos.ConsistencyLevel consistencyLevel = Cosmos.ConsistencyLevel.ConsistentPrefix; + Cosmos.PriorityLevel priorityLevel = Cosmos.PriorityLevel.Low; + int throughputBucket = 3; + CosmosClientBuilder cosmosClientBuilder = new( + accountEndpoint: endpoint, + authKeyOrResourceToken: key); + + cosmosClientBuilder + .WithConnectionModeDirect() + .WithRequestTimeout(requestTimeout) + .WithApplicationName(userAgentSuffix) + .AddCustomHandlers(preProcessHandler) + .WithApiType(apiType) + .WithThrottlingRetryOptions(maxRetryWaitTime, maxRetryAttemptsOnThrottledRequests) + .WithSerializerOptions(cosmosSerializerOptions) + .WithConsistencyLevel(consistencyLevel) + .WithPriorityLevel(priorityLevel) + .WithThroughputBucket(throughputBucket) + .WithApplicationPreferredRegions( + new List() + { + Regions.NorthCentralUS, + Regions.WestUS, + Regions.EastAsia, + }) + .WithCustomAccountEndpoints( + new HashSet() + { + new Uri("https://testfed2.documents-test.windows-int.net:443/"), + new Uri("https://testfed3.documents-test.windows-int.net:443/"), + new Uri("https://testfed4.documents-test.windows-int.net:443/"), + }); + + CosmosClientOptions clientOptions = cosmosClientBuilder.Build().ClientOptions; + + Assert.AreEqual(ConnectionMode.Direct, clientOptions.ConnectionMode); + Assert.AreEqual(requestTimeout, clientOptions.RequestTimeout); + Assert.AreEqual(userAgentSuffix, clientOptions.ApplicationName); + Assert.AreEqual(preProcessHandler, clientOptions.CustomHandlers[0]); + Assert.AreEqual(apiType, clientOptions.ApiType); + Assert.AreEqual(maxRetryAttemptsOnThrottledRequests, clientOptions.MaxRetryAttemptsOnRateLimitedRequests); + Assert.AreEqual(maxRetryWaitTime, clientOptions.MaxRetryWaitTimeOnRateLimitedRequests); + Assert.AreEqual(cosmosSerializerOptions.IgnoreNullValues, clientOptions.SerializerOptions.IgnoreNullValues); + Assert.AreEqual(cosmosSerializerOptions.PropertyNamingPolicy, clientOptions.SerializerOptions.PropertyNamingPolicy); + Assert.AreEqual(cosmosSerializerOptions.Indented, clientOptions.SerializerOptions.Indented); + Assert.IsFalse(clientOptions.AllowBulkExecution); + Assert.AreEqual(consistencyLevel, clientOptions.ConsistencyLevel); + Assert.IsNotNull(clientOptions.ApplicationPreferredRegions); + Assert.IsNotNull(clientOptions.AccountInitializationCustomEndpoints); } [TestMethod] @@ -1212,8 +1198,26 @@ public void TestServerCertificatesValidationWithDisableSSLFlagTrue(string connSt RemoteCertificateValidationCallback? httpClientRemoreCertValidationCallback = socketsHttpHandler.SslOptions.RemoteCertificateValidationCallback; Assert.IsNotNull(httpClientRemoreCertValidationCallback); #nullable disable - } - + } + + [TestMethod] + public void DisablePartitionLevelFailoverOptionTest() + { + // Test that the DisablePartitionLevelFailover option defaults to false + CosmosClientOptions clientOptions = new CosmosClientOptions(); + Assert.IsFalse(clientOptions.DisablePartitionLevelFailover); + + // Test that setting DisablePartitionLevelFailover to true is reflected in the connection policy + clientOptions.DisablePartitionLevelFailover = true; + ConnectionPolicy policy = clientOptions.GetConnectionPolicy(clientId: 0); + Assert.IsTrue(policy.DisablePartitionLevelFailover); + + // Test that setting DisablePartitionLevelFailover to false is reflected in the connection policy + clientOptions.DisablePartitionLevelFailover = false; + policy = clientOptions.GetConnectionPolicy(clientId: 0); + Assert.IsFalse(policy.DisablePartitionLevelFailover); + } + private class TestWebProxy : IWebProxy { public ICredentials Credentials { get; set; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/GlobalPartitionEndpointManagerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/GlobalPartitionEndpointManagerTests.cs index 519f8ac757..39e6f8d1bf 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/GlobalPartitionEndpointManagerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/GlobalPartitionEndpointManagerTests.cs @@ -398,106 +398,96 @@ public async Task TestPPAFClientAndServerEnablementCombinationScenariosAsync( bool ppafEnabledFromClient, bool? ppafEnabledFromService) { - if (ppafEnabledFromClient) - { - Environment.SetEnvironmentVariable(ConfigurationManager.PartitionLevelFailoverEnabled, "True"); - } + // Note: Environment variable for PPAF was removed as per acceptance criteria. + // PPAF is now controlled exclusively by account metadata and client options. - try - { - GlobalPartitionEndpointManagerTests.SetupAccountAndCacheOperations( - shouldEnablePPAF: ppafEnabledFromService, - out string secondaryRegionNameForUri, - out string globalEndpoint, - out string secondaryRegionEndpiont, - out string databaseName, - out string containerName, - out ResourceId containerResourceId, - out Mock mockHttpHandler, - out IReadOnlyList primaryRegionPartitionKeyRangeIds, - out TransportAddressUri primaryRegionprimaryReplicaUri); - - Mock mockTransport = new Mock(MockBehavior.Strict); - - MockSetupsHelper.SetupServiceUnavailableException( - mockTransport, - primaryRegionprimaryReplicaUri); - - // Partition key ranges are the same in both regions so the SDK - // does not need to go the secondary to get the partition key ranges. - // Only the addresses need to be mocked on the secondary - MockSetupsHelper.SetupAddresses( - mockHttpHandler: mockHttpHandler, - partitionKeyRangeId: primaryRegionPartitionKeyRangeIds.First(), - regionEndpoint: secondaryRegionEndpiont, - regionName: secondaryRegionNameForUri, - containerResourceId: containerResourceId, - primaryReplicaUri: out TransportAddressUri secondaryRegionPrimaryReplicaUri); + GlobalPartitionEndpointManagerTests.SetupAccountAndCacheOperations( + shouldEnablePPAF: ppafEnabledFromService, + out string secondaryRegionNameForUri, + out string globalEndpoint, + out string secondaryRegionEndpiont, + out string databaseName, + out string containerName, + out ResourceId containerResourceId, + out Mock mockHttpHandler, + out IReadOnlyList primaryRegionPartitionKeyRangeIds, + out TransportAddressUri primaryRegionprimaryReplicaUri); - MockSetupsHelper.SetupCreateItemResponse( - mockTransport, - secondaryRegionPrimaryReplicaUri); + Mock mockTransport = new Mock(MockBehavior.Strict); - CosmosClientOptions cosmosClientOptions = new CosmosClientOptions() - { - ConsistencyLevel = Cosmos.ConsistencyLevel.Strong, - ApplicationPreferredRegions = new List() - { - Regions.EastUS, - Regions.WestUS - }, - HttpClientFactory = () => new HttpClient(new HttpHandlerHelper(mockHttpHandler.Object)), - TransportClientHandlerFactory = (original) => mockTransport.Object, - }; + MockSetupsHelper.SetupServiceUnavailableException( + mockTransport, + primaryRegionprimaryReplicaUri); - using CosmosClient customClient = new CosmosClient( - globalEndpoint, - Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())), - cosmosClientOptions); + // Partition key ranges are the same in both regions so the SDK + // does not need to go the secondary to get the partition key ranges. + // Only the addresses need to be mocked on the secondary + MockSetupsHelper.SetupAddresses( + mockHttpHandler: mockHttpHandler, + partitionKeyRangeId: primaryRegionPartitionKeyRangeIds.First(), + regionEndpoint: secondaryRegionEndpiont, + regionName: secondaryRegionNameForUri, + containerResourceId: containerResourceId, + primaryReplicaUri: out TransportAddressUri secondaryRegionPrimaryReplicaUri); - Container container = customClient.GetContainer(databaseName, containerName); + MockSetupsHelper.SetupCreateItemResponse( + mockTransport, + secondaryRegionPrimaryReplicaUri); - ToDoActivity toDoActivity = new ToDoActivity() - { - Id = "TestItem", - Pk = "TestPk" - }; + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions() + { + ConsistencyLevel = Cosmos.ConsistencyLevel.Strong, + ApplicationPreferredRegions = new List() + { + Regions.EastUS, + Regions.WestUS + }, + HttpClientFactory = () => new HttpClient(new HttpHandlerHelper(mockHttpHandler.Object)), + TransportClientHandlerFactory = (original) => mockTransport.Object, + }; - if ((!ppafEnabledFromService.HasValue && ppafEnabledFromClient) - || (ppafEnabledFromService.HasValue && ppafEnabledFromService.Value)) - { - ItemResponse response = await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.Pk)); + using CosmosClient customClient = new CosmosClient( + globalEndpoint, + Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())), + cosmosClientOptions); - Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); - Assert.IsTrue(response.Diagnostics.GetContactedRegions().Count > 1); + Container container = customClient.GetContainer(databaseName, containerName); - mockTransport.VerifyAll(); - mockHttpHandler.VerifyAll(); - } - else - { - try - { - await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.Pk)); - Assert.Fail("Should throw an exception"); - } - catch (CosmosException ce) - { - // Clears all the setups. No network calls should be done on the next operation. - Assert.IsNotNull(ce); - Assert.AreEqual(HttpStatusCode.ServiceUnavailable, ce.StatusCode); - } - } + ToDoActivity toDoActivity = new ToDoActivity() + { + Id = "TestItem", + Pk = "TestPk" + }; - mockHttpHandler.Reset(); - mockTransport.Reset(); - mockTransport.Setup(x => x.Dispose()); + if ((!ppafEnabledFromService.HasValue && ppafEnabledFromClient) + || (ppafEnabledFromService.HasValue && ppafEnabledFromService.Value)) + { + ItemResponse response = await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.Pk)); + + Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); + Assert.IsTrue(response.Diagnostics.GetContactedRegions().Count > 1); + + mockTransport.VerifyAll(); + mockHttpHandler.VerifyAll(); } - finally + else { - // Reset the environment variable to avoid affecting other tests. - Environment.SetEnvironmentVariable(ConfigurationManager.PartitionLevelFailoverEnabled, null); + try + { + await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.Pk)); + Assert.Fail("Should throw an exception"); + } + catch (CosmosException ce) + { + // Clears all the setups. No network calls should be done on the next operation. + Assert.IsNotNull(ce); + Assert.AreEqual(HttpStatusCode.ServiceUnavailable, ce.StatusCode); + } } + + mockHttpHandler.Reset(); + mockTransport.Reset(); + mockTransport.Setup(x => x.Dispose()); } private static void SetupAccountAndCacheOperations( diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/GlobalPartitionEndpointManagerUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/GlobalPartitionEndpointManagerUnitTests.cs index dc702d37f4..b3be917fc7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/GlobalPartitionEndpointManagerUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/GlobalPartitionEndpointManagerUnitTests.cs @@ -394,6 +394,47 @@ private async Task OpenConnectionToUnhealthyEndpointsAsync( } } + [TestMethod] + public void TestCircuitBreakerTimeoutCounterResetWindowConfiguration() + { + // Test that the timeout counter reset window uses the configuration method with default value + const string envVarName = "AZURE_COSMOS_PPCB_TIMEOUT_COUNTER_RESET_WINDOW_IN_MINUTES"; + + try + { + // Clean up any existing environment variable + Environment.SetEnvironmentVariable(envVarName, null); + + Mock mockEndpointManager = new Mock(MockBehavior.Strict); + GlobalPartitionEndpointManagerCore failoverManager = new GlobalPartitionEndpointManagerCore( + mockEndpointManager.Object, + isPartitionLevelFailoverEnabled: false, + isPartitionLevelCircuitBreakerEnabled: true); + + // Use reflection to verify the TimeoutCounterResetWindowInMinutes field is set correctly + Type failoverManagerType = typeof(GlobalPartitionEndpointManagerCore); + Type nestedType = failoverManagerType.GetNestedType("PartitionKeyRangeFailoverInfo", + System.Reflection.BindingFlags.NonPublic); + Assert.IsNotNull(nestedType, "PartitionKeyRangeFailoverInfo nested class should exist"); + + // Test with environment variable set to custom value + Environment.SetEnvironmentVariable(envVarName, "10"); + + GlobalPartitionEndpointManagerCore customFailoverManager = new GlobalPartitionEndpointManagerCore( + mockEndpointManager.Object, + isPartitionLevelFailoverEnabled: false, + isPartitionLevelCircuitBreakerEnabled: true); + + // If we reach here without exceptions, the configuration is working + Assert.IsNotNull(customFailoverManager); + } + finally + { + // Clean up environment variable + Environment.SetEnvironmentVariable(envVarName, null); + } + } + private static void SimulateConsecutiveFailures( GlobalPartitionEndpointManagerCore failoverManager, DocumentServiceRequest requestMessage)