public static void SetupStrongAccountProperties(
            Mock <IHttpHandler> mockHttpClientHandler,
            string accountName,
            string endpoint,
            IList <AccountRegion> writeRegions,
            IList <AccountRegion> readRegions)
        {
            HttpResponseMessage httpResponseMessage = MockSetupsHelper.CreateStrongAccount(
                accountName,
                writeRegions,
                readRegions);

            Uri endpointUri = new Uri(endpoint);

            mockHttpClientHandler.Setup(x => x.SendAsync(
                                            It.Is <HttpRequestMessage>(x => x.RequestUri == endpointUri),
                                            It.IsAny <CancellationToken>()))
            .Returns <HttpRequestMessage, CancellationToken>((request, cancellationToken) => Task.FromResult(httpResponseMessage));
        }
        private static void SetupAccountAndCacheOperations(
            out string secondaryRegionNameForUri,
            out string globalEndpoint,
            out string secondaryRegionEndpiont,
            out string databaseName,
            out string containerName,
            out ResourceId containerResourceId,
            out Mock <IHttpHandler> mockHttpHandler,
            out IReadOnlyList <string> primaryRegionPartitionKeyRangeIds,
            out TransportAddressUri primaryRegionprimaryReplicaUri)
        {
            string accountName             = "testAccount";
            string primaryRegionNameForUri = "eastus";

            secondaryRegionNameForUri = "westus";
            globalEndpoint            = $"https://{accountName}.documents.azure.com:443/";
            Uri    globalEndpointUri     = new Uri(globalEndpoint);
            string primaryRegionEndpoint = $"https://{accountName}-{primaryRegionNameForUri}.documents.azure.com";

            secondaryRegionEndpiont = $"https://{accountName}-{secondaryRegionNameForUri}.documents.azure.com";
            databaseName            = "testDb";
            containerName           = "testContainer";
            string containerRid = "ccZ1ANCszwk=";

            containerResourceId = ResourceId.Parse(containerRid);

            List <AccountRegion> writeRegion = new List <AccountRegion>()
            {
                new AccountRegion()
                {
                    Name     = "East US",
                    Endpoint = $"{primaryRegionEndpoint}:443/"
                }
            };

            List <AccountRegion> readRegions = new List <AccountRegion>()
            {
                new AccountRegion()
                {
                    Name     = "East US",
                    Endpoint = $"{primaryRegionEndpoint}:443/"
                },
                new AccountRegion()
                {
                    Name     = "West US",
                    Endpoint = $"{secondaryRegionEndpiont}:443/"
                }
            };

            // Create a mock http handler to inject gateway responses.
            // MockBehavior.Strict ensures that only the mocked APIs get called
            mockHttpHandler = new Mock <IHttpHandler>(MockBehavior.Strict);
            MockSetupsHelper.SetupStrongAccountProperties(
                mockHttpClientHandler: mockHttpHandler,
                endpoint: globalEndpointUri.ToString(),
                accountName: accountName,
                writeRegions: writeRegion,
                readRegions: readRegions);

            MockSetupsHelper.SetupContainerProperties(
                mockHttpHandler: mockHttpHandler,
                regionEndpoint: primaryRegionEndpoint,
                databaseName: databaseName,
                containerName: containerName,
                containerRid: containerRid);

            MockSetupsHelper.SetupPartitionKeyRanges(
                mockHttpHandler: mockHttpHandler,
                regionEndpoint: primaryRegionEndpoint,
                containerResourceId: containerResourceId,
                partitionKeyRangeIds: out primaryRegionPartitionKeyRangeIds);

            MockSetupsHelper.SetupAddresses(
                mockHttpHandler: mockHttpHandler,
                partitionKeyRangeId: primaryRegionPartitionKeyRangeIds.First(),
                regionEndpoint: primaryRegionEndpoint,
                regionName: primaryRegionNameForUri,
                containerResourceId: containerResourceId,
                primaryReplicaUri: out primaryRegionprimaryReplicaUri);
        }
        public async Task TestWriteForbiddenScenarioAsync()
        {
            GlobalPartitionEndpointManagerTests.SetupAccountAndCacheOperations(
                out string secondaryRegionNameForUri,
                out string globalEndpoint,
                out string secondaryRegionEndpiont,
                out string databaseName,
                out string containerName,
                out ResourceId containerResourceId,
                out Mock <IHttpHandler> mockHttpHandler,
                out IReadOnlyList <string> primaryRegionPartitionKeyRangeIds,
                out TransportAddressUri primaryRegionprimaryReplicaUri);

            Mock <TransportClient> mockTransport = new Mock <TransportClient>(MockBehavior.Strict);

            MockSetupsHelper.SetupWriteForbiddenException(
                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);

            MockSetupsHelper.SetupCreateItemResponse(
                mockTransport,
                secondaryRegionPrimaryReplicaUri);

            CosmosClientOptions cosmosClientOptions = new CosmosClientOptions()
            {
                EnablePartitionLevelFailover = true,
                ConsistencyLevel             = Cosmos.ConsistencyLevel.Strong,
                ApplicationPreferredRegions  = new List <string>()
                {
                    Regions.EastUS,
                    Regions.WestUS
                },
                HttpClientFactory             = () => new HttpClient(new HttpHandlerHelper(mockHttpHandler.Object)),
                TransportClientHandlerFactory = (original) => mockTransport.Object,
            };

            using CosmosClient customClient = new CosmosClient(
                      globalEndpoint,
                      Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())),
                      cosmosClientOptions);

            Container container = customClient.GetContainer(databaseName, containerName);

            ToDoActivity toDoActivity = new ToDoActivity()
            {
                Id = "TestItem",
                Pk = "TestPk"
            };

            ItemResponse <ToDoActivity> response = await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.Pk));

            Assert.AreEqual(HttpStatusCode.Created, response.StatusCode);
            mockTransport.VerifyAll();
            mockHttpHandler.VerifyAll();

            // Clears all the setups. No network calls should be done on the next operation.
            mockHttpHandler.Reset();
            mockTransport.Reset();
            mockTransport.Setup(x => x.Dispose());

            MockSetupsHelper.SetupCreateItemResponse(
                mockTransport,
                secondaryRegionPrimaryReplicaUri);

            ToDoActivity toDoActivity2 = new ToDoActivity()
            {
                Id = "TestItem2",
                Pk = "TestPk"
            };

            response = await container.CreateItemAsync(toDoActivity2, new Cosmos.PartitionKey(toDoActivity2.Pk));

            Assert.AreEqual(HttpStatusCode.Created, response.StatusCode);
        }
        public async Task TestHttpRequestExceptionScenarioAsync()
        {
            // testhost.dll.config sets it to 2 seconds which causes it to always expire before retrying. Remove the override.
            System.Configuration.ConfigurationManager.AppSettings["UnavailableLocationsExpirationTimeInSeconds"] = "500";

            string     accountName               = nameof(TestHttpRequestExceptionScenarioAsync);
            string     primaryRegionNameForUri   = "eastus";
            string     secondaryRegionNameForUri = "westus";
            string     globalEndpoint            = $"https://{accountName}.documents.azure.com:443/";
            Uri        globalEndpointUri         = new Uri(globalEndpoint);
            string     primaryRegionEndpoint     = $"https://{accountName}-{primaryRegionNameForUri}.documents.azure.com";
            string     secondaryRegionEndpiont   = $"https://{accountName}-{secondaryRegionNameForUri}.documents.azure.com";
            string     databaseName              = "testDb";
            string     containerName             = "testContainer";
            string     containerRid              = "ccZ1ANCszwk=";
            ResourceId containerResourceId       = ResourceId.Parse(containerRid);

            List <AccountRegion> writeRegion = new List <AccountRegion>()
            {
                new AccountRegion()
                {
                    Name     = "East US",
                    Endpoint = $"{primaryRegionEndpoint}:443/"
                }
            };

            List <AccountRegion> readRegions = new List <AccountRegion>()
            {
                new AccountRegion()
                {
                    Name     = "East US",
                    Endpoint = $"{primaryRegionEndpoint}:443/"
                },
                new AccountRegion()
                {
                    Name     = "West US",
                    Endpoint = $"{secondaryRegionEndpiont}:443/"
                }
            };

            List <AccountRegion> writeRegionFailedOver = new List <AccountRegion>()
            {
                new AccountRegion()
                {
                    Name     = "West US",
                    Endpoint = $"{secondaryRegionEndpiont}:443/"
                }
            };

            List <AccountRegion> readRegionsFailedOver = new List <AccountRegion>()
            {
                new AccountRegion()
                {
                    Name     = "West US",
                    Endpoint = $"{secondaryRegionEndpiont}:443/"
                },
                new AccountRegion()
                {
                    Name     = "East US",
                    Endpoint = $"{primaryRegionEndpoint}:443/"
                },
            };

            // Create a mock http handler to inject gateway responses.
            // MockBehavior.Strict ensures that only the mocked APIs get called
            Mock <IHttpHandler> mockHttpHandler = new Mock <IHttpHandler>(MockBehavior.Strict);


            mockHttpHandler.Setup(x => x.SendAsync(
                                      It.Is <HttpRequestMessage>(m => m.RequestUri == globalEndpointUri || m.RequestUri.ToString().Contains(primaryRegionNameForUri)),
                                      It.IsAny <CancellationToken>())).Throws(new HttpRequestException("Mock HttpRequestException to simulate region being down"));

            int count = 0;

            mockHttpHandler.Setup(x => x.SendAsync(
                                      It.Is <HttpRequestMessage>(x => x.RequestUri == new Uri(secondaryRegionEndpiont)),
                                      It.IsAny <CancellationToken>()))
            .Returns <HttpRequestMessage, CancellationToken>((request, cancellationToken) =>
            {
                // Simulate the legacy gateway being down. After 40 requests simulate the write region pointing to new location.
                count++;
                if (count < 2)
                {
                    return(Task.FromResult(MockSetupsHelper.CreateStrongAccount(accountName, writeRegion, readRegions)));
                }
                else
                {
                    return(Task.FromResult(MockSetupsHelper.CreateStrongAccount(accountName, writeRegionFailedOver, readRegionsFailedOver)));
                }
            });


            MockSetupsHelper.SetupContainerProperties(
                mockHttpHandler: mockHttpHandler,
                regionEndpoint: secondaryRegionEndpiont,
                databaseName: databaseName,
                containerName: containerName,
                containerRid: containerRid);

            MockSetupsHelper.SetupPartitionKeyRanges(
                mockHttpHandler: mockHttpHandler,
                regionEndpoint: secondaryRegionEndpiont,
                containerResourceId: containerResourceId,
                partitionKeyRangeIds: out IReadOnlyList <string> secondaryRegionPartitionKeyRangeIds);

            MockSetupsHelper.SetupAddresses(
                mockHttpHandler: mockHttpHandler,
                partitionKeyRangeId: secondaryRegionPartitionKeyRangeIds.First(),
                regionEndpoint: secondaryRegionEndpiont,
                regionName: secondaryRegionNameForUri,
                containerResourceId: containerResourceId,
                primaryReplicaUri: out TransportAddressUri secondaryRegionprimaryReplicaUri);

            Mock <TransportClient> mockTransport = new Mock <TransportClient>(MockBehavior.Strict);

            MockSetupsHelper.SetupRequestTimeoutException(
                mockTransport,
                secondaryRegionprimaryReplicaUri);

            // 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: secondaryRegionPartitionKeyRangeIds.First(),
                regionEndpoint: secondaryRegionEndpiont,
                regionName: secondaryRegionNameForUri,
                containerResourceId: containerResourceId,
                primaryReplicaUri: out TransportAddressUri secondaryRegionPrimaryReplicaUri);

            MockSetupsHelper.SetupCreateItemResponse(
                mockTransport,
                secondaryRegionPrimaryReplicaUri);

            CosmosClientOptions cosmosClientOptions = new CosmosClientOptions()
            {
                EnablePartitionLevelFailover = true,
                ConsistencyLevel             = Cosmos.ConsistencyLevel.Strong,
                ApplicationPreferredRegions  = new List <string>()
                {
                    Regions.EastUS,
                    Regions.WestUS
                },
                HttpClientFactory             = () => new HttpClient(new HttpHandlerHelper(mockHttpHandler.Object)),
                TransportClientHandlerFactory = (original) => mockTransport.Object,
            };

            using (CosmosClient customClient = new CosmosClient(
                       globalEndpoint,
                       Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())),
                       cosmosClientOptions))
            {
                Container container = customClient.GetContainer(databaseName, containerName);

                ToDoActivity toDoActivity = new ToDoActivity()
                {
                    Id = "TestItem",
                    Pk = "TestPk"
                };

                ItemResponse <ToDoActivity> response = await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.Pk));

                Assert.AreEqual(HttpStatusCode.Created, response.StatusCode);
                mockTransport.VerifyAll();
                mockHttpHandler.VerifyAll();

                // Clears all the setups. No network calls should be done on the next operation.
                mockHttpHandler.Reset();
                mockTransport.Reset();

                MockSetupsHelper.SetupCreateItemResponse(
                    mockTransport,
                    secondaryRegionPrimaryReplicaUri);

                ToDoActivity toDoActivity2 = new ToDoActivity()
                {
                    Id = "TestItem2",
                    Pk = "TestPk"
                };

                response = await container.CreateItemAsync(toDoActivity2, new Cosmos.PartitionKey(toDoActivity2.Pk));

                Assert.AreEqual(HttpStatusCode.Created, response.StatusCode);
                mockTransport.Setup(x => x.Dispose());

                // Reset it back to the override to avoid impacting other tests.
                System.Configuration.ConfigurationManager.AppSettings["UnavailableLocationsExpirationTimeInSeconds"] = "2";
            }

            await Task.Delay(TimeSpan.FromMinutes(2));
        }
Example #5
0
        public async Task TestGoneFromServiceScenarioAsync()
        {
            Mock <IHttpHandler> mockHttpHandler = new Mock <IHttpHandler>(MockBehavior.Strict);
            Uri endpoint = MockSetupsHelper.SetupSingleRegionAccount(
                "mockAccountInfo",
                consistencyLevel: ConsistencyLevel.Session,
                mockHttpHandler,
                out string primaryRegionEndpoint);

            string databaseName  = "mockDbName";
            string containerName = "mockContainerName";
            string containerRid  = "ccZ1ANCszwk=";

            Documents.ResourceId cRid = Documents.ResourceId.Parse(containerRid);
            MockSetupsHelper.SetupContainerProperties(
                mockHttpHandler: mockHttpHandler,
                regionEndpoint: primaryRegionEndpoint,
                databaseName: databaseName,
                containerName: containerName,
                containerRid: containerRid);

            MockSetupsHelper.SetupSinglePartitionKeyRange(
                mockHttpHandler,
                primaryRegionEndpoint,
                cRid,
                out IReadOnlyList <string> partitionKeyRanges);

            List <string> replicaIds1 = new List <string>()
            {
                "11111111111111111",
                "22222222222222222",
                "33333333333333333",
                "44444444444444444",
            };

            HttpResponseMessage replicaSet1 = MockSetupsHelper.CreateAddresses(
                replicaIds1,
                partitionKeyRanges.First(),
                "eastus",
                cRid);

            // One replica changed on the refresh
            List <string> replicaIds2 = new List <string>()
            {
                "11111111111111111",
                "22222222222222222",
                "33333333333333333",
                "55555555555555555",
            };

            HttpResponseMessage replicaSet2 = MockSetupsHelper.CreateAddresses(
                replicaIds2,
                partitionKeyRanges.First(),
                "eastus",
                cRid);

            bool delayCacheRefresh     = true;
            bool delayRefreshUnblocked = false;

            mockHttpHandler.SetupSequence(x => x.SendAsync(
                                              It.Is <HttpRequestMessage>(r => r.RequestUri.ToString().Contains("addresses")), It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(replicaSet1))
            .Returns(async() =>
            {
                //block cache refresh to verify bad replica is not visited during refresh
                while (delayCacheRefresh)
                {
                    await Task.Delay(TimeSpan.FromMilliseconds(20));
                }

                delayRefreshUnblocked = true;
                return(replicaSet2);
            });

            int callBack = 0;
            List <Documents.TransportAddressUri> urisVisited         = new List <Documents.TransportAddressUri>();
            Mock <Documents.TransportClient>     mockTransportClient = new Mock <Documents.TransportClient>(MockBehavior.Strict);

            mockTransportClient.Setup(x => x.InvokeResourceOperationAsync(It.IsAny <Documents.TransportAddressUri>(), It.IsAny <Documents.DocumentServiceRequest>()))
            .Callback <Documents.TransportAddressUri, Documents.DocumentServiceRequest>((t, _) => urisVisited.Add(t))
            .Returns(() =>
            {
                callBack++;
                if (callBack == 1)
                {
                    throw Documents.Rntbd.TransportExceptions.GetGoneException(
                        new Uri("https://localhost:8081"),
                        Guid.NewGuid(),
                        new Documents.TransportException(Documents.TransportErrorCode.ConnectionBroken,
                                                         null,
                                                         Guid.NewGuid(),
                                                         new Uri("https://localhost:8081"),
                                                         "Mock",
                                                         userPayload: true,
                                                         payloadSent: false));
                }

                return(Task.FromResult(new Documents.StoreResponse()
                {
                    Status = 200,
                    Headers = new Documents.Collections.StoreResponseNameValueCollection()
                    {
                        ActivityId = Guid.NewGuid().ToString(),
                        LSN = "12345",
                        PartitionKeyRangeId = "0",
                        GlobalCommittedLSN = "12345",
                        SessionToken = "1#12345#1=12345"
                    },
                    ResponseBody = new MemoryStream()
                }));
            });

            CosmosClientOptions cosmosClientOptions = new CosmosClientOptions()
            {
                ConsistencyLevel              = Cosmos.ConsistencyLevel.Session,
                HttpClientFactory             = () => new HttpClient(new HttpHandlerHelper(mockHttpHandler.Object)),
                TransportClientHandlerFactory = (original) => mockTransportClient.Object,
            };

            using (CosmosClient customClient = new CosmosClient(
                       endpoint.ToString(),
                       Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())),
                       cosmosClientOptions))
            {
                try
                {
                    Container container = customClient.GetContainer(databaseName, containerName);

                    for (int i = 0; i < 20; i++)
                    {
                        ResponseMessage response = await container.ReadItemStreamAsync(Guid.NewGuid().ToString(), new Cosmos.PartitionKey(Guid.NewGuid().ToString()));

                        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
                    }

                    mockTransportClient.VerifyAll();
                    mockHttpHandler.VerifyAll();

                    Documents.TransportAddressUri failedReplica = urisVisited.First();
                    Assert.AreEqual(1, urisVisited.Count(x => x.Equals(failedReplica)));

                    urisVisited.Clear();
                    delayCacheRefresh = false;
                    do
                    {
                        await Task.Delay(TimeSpan.FromMilliseconds(100));
                    }while (!delayRefreshUnblocked);

                    for (int i = 0; i < 20; i++)
                    {
                        ResponseMessage response = await container.ReadItemStreamAsync(Guid.NewGuid().ToString(), new Cosmos.PartitionKey(Guid.NewGuid().ToString()));

                        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
                    }

                    Assert.AreEqual(4, urisVisited.ToHashSet().Count());

                    // Clears all the setups. No network calls should be done on the next operation.
                    mockHttpHandler.Reset();
                    mockTransportClient.Reset();
                }
                finally
                {
                    mockTransportClient.Setup(x => x.Dispose());
                }
            }
        }