|
5 | 5 | namespace Microsoft.Azure.Cosmos.Tests |
6 | 6 | { |
7 | 7 | using System; |
| 8 | + using System.Collections.Concurrent; |
8 | 9 | using System.Collections.Generic; |
| 10 | + using System.Collections.ObjectModel; |
9 | 11 | using System.IO; |
10 | 12 | using System.Linq; |
11 | 13 | using System.Net; |
12 | 14 | using System.Net.Http; |
| 15 | + using System.Reflection; |
| 16 | + using System.Security.AccessControl; |
13 | 17 | using System.Text; |
14 | 18 | using System.Threading; |
15 | 19 | using System.Threading.Tasks; |
16 | 20 | using Microsoft.Azure.Cosmos.Diagnostics; |
| 21 | + using Microsoft.Azure.Cosmos.Routing; |
| 22 | + using Microsoft.Azure.Cosmos.Serialization.HybridRow; |
17 | 23 | using Microsoft.Azure.Documents; |
18 | 24 | using Microsoft.VisualStudio.TestTools.UnitTesting; |
19 | 25 | using Moq; |
@@ -212,6 +218,7 @@ public async Task TestHttpRequestExceptionScenarioAsync() |
212 | 218 | } |
213 | 219 |
|
214 | 220 | [TestMethod] |
| 221 | + [Owner("dkunda")] |
215 | 222 | [DataRow(false, DisplayName = "Read Item Scenario without PPAF and PPCB.")] |
216 | 223 | [DataRow(true, DisplayName = "Read Item Scenario with PPAF and PPCB.")] |
217 | 224 | public async Task ReadItemAsync_WithThinClientEnabledAndServiceUnavailableReceived_ShouldRetryOnNextPreferredRegions( |
@@ -358,6 +365,152 @@ public async Task ReadItemAsync_WithThinClientEnabledAndServiceUnavailableReceiv |
358 | 365 | } |
359 | 366 |
|
360 | 367 | [TestMethod] |
| 368 | + [Owner("dkunda")] |
| 369 | + public async Task ReadItemAsync_WithThinClientEnabledAndHttpRequestExceptionReceived_ShouldMarkEndpointUnavailable() |
| 370 | + { |
| 371 | + try |
| 372 | + { |
| 373 | + // testhost.dll.config sets it to 2 seconds which causes it to always expire before retrying. Remove the override. |
| 374 | + System.Configuration.ConfigurationManager.AppSettings["UnavailableLocationsExpirationTimeInSeconds"] = "500"; |
| 375 | + |
| 376 | + Environment.SetEnvironmentVariable(ConfigurationManager.ThinClientModeEnabled, "True"); |
| 377 | + string accountName = nameof(TestHttpRequestExceptionScenarioAsync); |
| 378 | + string primaryRegionNameForUri = "eastus"; |
| 379 | + string secondaryRegionNameForUri = "westus"; |
| 380 | + string globalEndpoint = $"https://{accountName}.documents.azure.com:443/"; |
| 381 | + Uri globalEndpointUri = new Uri(globalEndpoint); |
| 382 | + string primaryRegionEndpoint = $"https://{accountName}-{primaryRegionNameForUri}.documents.azure.com"; |
| 383 | + string secondaryRegionEndpoint = $"https://{accountName}-{secondaryRegionNameForUri}.documents.azure.com"; |
| 384 | + string databaseName = "testDb"; |
| 385 | + string containerName = "testContainer"; |
| 386 | + string containerRid = "ccZ1ANCszwk="; |
| 387 | + ResourceId containerResourceId = ResourceId.Parse(containerRid); |
| 388 | + |
| 389 | + List<AccountRegion> writeRegion = new List<AccountRegion>() |
| 390 | + { |
| 391 | + new AccountRegion() |
| 392 | + { |
| 393 | + Name = "East US", |
| 394 | + Endpoint = $"{primaryRegionEndpoint}:443/" |
| 395 | + } |
| 396 | + }; |
| 397 | + |
| 398 | + List<AccountRegion> readRegions = new List<AccountRegion>() |
| 399 | + { |
| 400 | + new AccountRegion() |
| 401 | + { |
| 402 | + Name = "East US", |
| 403 | + Endpoint = $"{primaryRegionEndpoint}:443/" |
| 404 | + }, |
| 405 | + new AccountRegion() |
| 406 | + { |
| 407 | + Name = "West US", |
| 408 | + Endpoint = $"{secondaryRegionEndpoint}:443/" |
| 409 | + } |
| 410 | + }; |
| 411 | + |
| 412 | + // Create a mock http handler to inject proxy responses. |
| 413 | + // MockBehavior.Strict ensures that only the mocked APIs get called |
| 414 | + List<string> regionsVisited = new List<string>(); |
| 415 | + Mock<IHttpHandler> mockHttpHandler = new Mock<IHttpHandler>(MockBehavior.Strict); |
| 416 | + string readResponseHexStringWith200Status = "2a000000C80000000000000000000000000000000000000035000201000000000000011c000200000000480000007b22636f6465223a2022343039222c226d657373616765223a2022416e206572726f72206f63637572726564207768696c6520726f7574696e67207468652072657175657374227d"; |
| 417 | + mockHttpHandler.Setup(x => x.SendAsync( |
| 418 | + It.Is<HttpRequestMessage>(m => m.RequestUri == globalEndpointUri || m.RequestUri.ToString().Contains(primaryRegionNameForUri) || m.RequestUri.ToString().Contains(secondaryRegionNameForUri)), |
| 419 | + It.IsAny<CancellationToken>())) |
| 420 | + .Returns<HttpRequestMessage, CancellationToken>((request, cancellationToken) => |
| 421 | + { |
| 422 | + if (request.Version == new Version(2, 0)) |
| 423 | + { |
| 424 | + if (request.RequestUri.ToString().Contains("eastus")) |
| 425 | + { |
| 426 | + throw new HttpRequestException(); |
| 427 | + } |
| 428 | + else if (request.RequestUri.ToString().Contains("westus")) |
| 429 | + { |
| 430 | + regionsVisited.Add(Regions.WestUS); |
| 431 | + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) |
| 432 | + { |
| 433 | + RequestMessage = request, |
| 434 | + Content = new StreamContent(new MemoryStream(Convert.FromHexString(readResponseHexStringWith200Status))) |
| 435 | + }); |
| 436 | + } |
| 437 | + } |
| 438 | + |
| 439 | + return Task.FromResult(MockSetupsHelper.CreateStrongAccount(accountName, writeRegion, readRegions, shouldEnableThinClient: true, shouldEnablePPAF: false)); |
| 440 | + }); |
| 441 | + |
| 442 | + MockSetupsHelper.SetupContainerProperties( |
| 443 | + mockHttpHandler: mockHttpHandler, |
| 444 | + regionEndpoint: primaryRegionEndpoint, |
| 445 | + databaseName: databaseName, |
| 446 | + containerName: containerName, |
| 447 | + containerRid: containerRid); |
| 448 | + |
| 449 | + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions() |
| 450 | + { |
| 451 | + ConsistencyLevel = Cosmos.ConsistencyLevel.Strong, |
| 452 | + ApplicationPreferredRegions = new List<string>() |
| 453 | + { |
| 454 | + Regions.EastUS, |
| 455 | + Regions.WestUS |
| 456 | + }, |
| 457 | + ConnectionMode = ConnectionMode.Gateway, |
| 458 | + HttpClientFactory = () => new HttpClient(new HttpHandlerHelper(mockHttpHandler.Object)), |
| 459 | + }; |
| 460 | + |
| 461 | + using (CosmosClient customClient = new CosmosClient( |
| 462 | + globalEndpoint, |
| 463 | + Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())), |
| 464 | + cosmosClientOptions)) |
| 465 | + { |
| 466 | + Container container = customClient.GetContainer(databaseName, containerName); |
| 467 | + ToDoActivity toDoActivity = new ToDoActivity() |
| 468 | + { |
| 469 | + Id = "TestItem", |
| 470 | + Pk = "TestPk" |
| 471 | + }; |
| 472 | + |
| 473 | + ItemResponse<ToDoActivity> readResponse = await container.ReadItemAsync<ToDoActivity>(toDoActivity.Id, new Cosmos.PartitionKey(toDoActivity.Pk)); |
| 474 | + Console.WriteLine($"{readResponse.Diagnostics}"); |
| 475 | + Assert.AreEqual(HttpStatusCode.OK, readResponse.StatusCode); |
| 476 | + Assert.IsTrue(regionsVisited.Count == 1); |
| 477 | + Assert.AreEqual(Regions.WestUS, regionsVisited[0]); |
| 478 | + |
| 479 | + GlobalEndpointManager endpointManager = customClient.DocumentClient.GlobalEndpointManager; |
| 480 | + |
| 481 | + FieldInfo fieldInfo = endpointManager |
| 482 | + .GetType() |
| 483 | + .GetField( |
| 484 | + name: "locationCache", |
| 485 | + bindingAttr: BindingFlags.Instance | BindingFlags.NonPublic); |
| 486 | + |
| 487 | + LocationCache locationCache = (LocationCache)fieldInfo |
| 488 | + .GetValue( |
| 489 | + obj: endpointManager); |
| 490 | + |
| 491 | + MethodInfo method = locationCache.GetType().GetMethod("IsEndpointUnavailable", BindingFlags.NonPublic | BindingFlags.Instance); |
| 492 | + |
| 493 | + if (method != null) |
| 494 | + { |
| 495 | + bool isEastUsAvailable = (bool)method.Invoke(locationCache, new object[] { endpointManager.ThinClientReadEndpoints[0], OperationType.Read }); |
| 496 | + bool isWestUsAvailable = (bool)method.Invoke(locationCache, new object[] { endpointManager.ThinClientReadEndpoints[1], OperationType.Read }); |
| 497 | + |
| 498 | + Assert.IsTrue(isWestUsAvailable, "Since West US was never marked unavailable, this endpoint is expected to be available."); |
| 499 | + Assert.IsFalse(isEastUsAvailable, "Since East US was marked unavailable, this endpoint is expected to be unavailable."); |
| 500 | + } |
| 501 | + |
| 502 | + mockHttpHandler.VerifyAll(); |
| 503 | + } |
| 504 | + } |
| 505 | + finally |
| 506 | + { |
| 507 | + // Reset the environment variable to avoid impacting other tests. |
| 508 | + Environment.SetEnvironmentVariable(ConfigurationManager.ThinClientModeEnabled, null); |
| 509 | + } |
| 510 | + } |
| 511 | + |
| 512 | + [TestMethod] |
| 513 | + [Owner("dkunda")] |
361 | 514 | [DataRow(false, DisplayName = "When PPAF is disabled, Create Item Scenario should not retry on other regions on a single master write account.")] |
362 | 515 | public async Task CreateItemAsync_WithThinClientEnabledAndServiceUnavailableReceived_ShouldNotRetryOnOtherRegions( |
363 | 516 | bool enablePartitionLevelFailover) |
|
0 commit comments