Esempio n. 1
0
        public void AddressCacheMockTest()
        {
            // create a real document service request
            DocumentServiceRequest entity = DocumentServiceRequest.Create(OperationType.Read, ResourceType.Document, AuthorizationTokenType.PrimaryMasterKey);

            // setup mocks for address information
            AddressInformation[] addressInformation = new AddressInformation[3];
            for (int i = 0; i < 3; i++)
            {
                addressInformation[i] = new AddressInformation
                {
                    PhysicalUri = "http://replica-" + i.ToString("G", CultureInfo.CurrentCulture)
                };
            }

            // Address Selector is an internal sealed class that can't be mocked, but its dependency
            // AddressCache can be mocked.
            Mock <IAddressResolver> mockAddressCache = new Mock <IAddressResolver>();

            mockAddressCache.Setup(
                cache => cache.ResolveAsync(
                    It.IsAny <DocumentServiceRequest>(),
                    false /*forceRefresh*/,
                    new CancellationToken()))
            .ReturnsAsync(new PartitionAddressInformation(addressInformation, null, null));

            // validate that the mock works
            PartitionAddressInformation addressInfo = mockAddressCache.Object.ResolveAsync(entity, false, new CancellationToken()).Result;

            Assert.IsTrue(addressInfo.AllAddresses[0] == addressInformation[0]);
        }
        private static void SetTransportAddressUrisToUnhealthy(
            PartitionAddressInformation stalePartitionAddressInformation,
            Lazy <HashSet <TransportAddressUri> > failedEndpoints)
        {
            if (stalePartitionAddressInformation == null ||
                failedEndpoints == null ||
                !failedEndpoints.IsValueCreated)
            {
                return;
            }

            IReadOnlyList <TransportAddressUri> perProtocolPartitionAddressInformation = stalePartitionAddressInformation.Get(Protocol.Tcp)?.ReplicaTransportAddressUris;

            if (perProtocolPartitionAddressInformation == null)
            {
                return;
            }

            foreach (TransportAddressUri failed in perProtocolPartitionAddressInformation)
            {
                if (failedEndpoints.Value.Contains(failed))
                {
                    failed.SetUnhealthy();
                }
            }
        }
 private static void LogPartitionCacheRefresh(
     IClientSideRequestStatistics clientSideRequestStatistics,
     PartitionAddressInformation old,
     PartitionAddressInformation updated)
 {
     if (clientSideRequestStatistics is ClientSideRequestStatisticsTraceDatum traceDatum)
     {
         traceDatum.RecordAddressCachRefreshContent(old, updated);
     }
 }
        public async Task GatewayAddressCacheInNetworkRequestTestAsync()
        {
            FakeMessageHandler messageHandler = new FakeMessageHandler();
            HttpClient         httpClient     = new HttpClient(messageHandler);

            httpClient.Timeout = TimeSpan.FromSeconds(120);
            GatewayAddressCache cache = new GatewayAddressCache(
                new Uri(GatewayAddressCacheTests.DatabaseAccountApiEndpoint),
                Documents.Client.Protocol.Https,
                this.mockTokenProvider.Object,
                this.mockServiceConfigReader.Object,
                MockCosmosUtil.CreateCosmosHttpClient(() => httpClient),
                suboptimalPartitionForceRefreshIntervalInSeconds: 2,
                enableTcpConnectionEndpointRediscovery: true);

            // No header should be present.
            PartitionAddressInformation legacyRequest = await cache.TryGetAddressesAsync(
                DocumentServiceRequest.Create(OperationType.Invalid, ResourceType.Address, AuthorizationTokenType.Invalid),
                this.testPartitionKeyRangeIdentity,
                this.serviceIdentity,
                false,
                CancellationToken.None);


            Assert.IsFalse(legacyRequest.IsLocalRegion);

            // Header indicates the request is from the same azure region.
            messageHandler.Headers[HttpConstants.HttpHeaders.LocalRegionRequest] = "true";
            PartitionAddressInformation inNetworkAddresses = await cache.TryGetAddressesAsync(
                DocumentServiceRequest.Create(OperationType.Invalid, ResourceType.Address, AuthorizationTokenType.Invalid),
                this.testPartitionKeyRangeIdentity,
                this.serviceIdentity,
                true,
                CancellationToken.None);

            Assert.IsTrue(inNetworkAddresses.IsLocalRegion);

            // Header indicates the request is not from the same azure region.
            messageHandler.Headers[HttpConstants.HttpHeaders.LocalRegionRequest] = "false";
            PartitionAddressInformation outOfNetworkAddresses = await cache.TryGetAddressesAsync(
                DocumentServiceRequest.Create(OperationType.Invalid, ResourceType.Address, AuthorizationTokenType.Invalid),
                this.testPartitionKeyRangeIdentity,
                this.serviceIdentity,
                true,
                CancellationToken.None);

            Assert.IsFalse(outOfNetworkAddresses.IsLocalRegion);
        }
        public async Task <PartitionAddressInformation> ResolveAsync(
            DocumentServiceRequest request,
            bool forceRefresh,
            CancellationToken cancellationToken)
        {
            IAddressResolver            resolver = this.GetAddressResolver(request);
            PartitionAddressInformation partitionAddressInformation = await resolver.ResolveAsync(request, forceRefresh, cancellationToken);

            if (!this.partitionKeyRangeLocationCache.TryAddPartitionLevelLocationOverride(request))
            {
                return(partitionAddressInformation);
            }

            resolver = this.GetAddressResolver(request);
            return(await resolver.ResolveAsync(request, forceRefresh, cancellationToken));
        }
            public ResolutionResult(
                PartitionAddressInformation addresses,
                ServiceIdentity serviceIdentity)
            {
                if (addresses == null)
                {
                    throw new ArgumentNullException("addresses");
                }

                if (serviceIdentity == null)
                {
                    throw new ArgumentNullException("serviceIdentity");
                }

                this.Addresses             = addresses;
                this.TargetServiceIdentity = serviceIdentity;
            }
Esempio n. 7
0
        private async Task <DocumentServiceResponse> ProcessMessageForRead(DocumentClient client, DocumentServiceRequest request)
        {
            Uri requestEndpointUri = client.GlobalEndpointManager.ResolveServiceEndpoint(request);

            Assert.IsNotNull(requestEndpointUri);

            PartitionAddressInformation partitionAddress = await client.AddressResolver.ResolveAsync(request, false, new CancellationToken());

            AddressInformation[] addressList = partitionAddress.AllAddresses;
            Assert.IsNotNull(addressList);
            Assert.IsTrue(addressList.Length > 0);

            if (!request.ClearSessionTokenOnSessionReadFailure)
            {
                Assert.AreEqual(8081, requestEndpointUri.Port);
                throw this.CreateReadSessionNotAvailableException();
            }

            Assert.AreEqual(8081, requestEndpointUri.Port);
            return(await this.CreateEmptyDocumentServiceResponse());
        }
        public async Task TestGatewayAddressCacheUpdateOnConnectionResetAsync()
        {
            FakeMessageHandler messageHandler = new FakeMessageHandler();
            HttpClient         httpClient     = new HttpClient(messageHandler);

            httpClient.Timeout = TimeSpan.FromSeconds(120);
            GatewayAddressCache cache = new GatewayAddressCache(
                new Uri(GatewayAddressCacheTests.DatabaseAccountApiEndpoint),
                Documents.Client.Protocol.Https,
                this.mockTokenProvider.Object,
                this.mockServiceConfigReader.Object,
                MockCosmosUtil.CreateCosmosHttpClient(() => httpClient),
                suboptimalPartitionForceRefreshIntervalInSeconds: 2,
                enableTcpConnectionEndpointRediscovery: true);

            PartitionAddressInformation addresses = cache.TryGetAddressesAsync(
                DocumentServiceRequest.Create(OperationType.Invalid, ResourceType.Address, AuthorizationTokenType.Invalid),
                this.testPartitionKeyRangeIdentity,
                this.serviceIdentity,
                false,
                CancellationToken.None).Result;

            Assert.IsNotNull(addresses.AllAddresses.Select(address => address.PhysicalUri == "https://blabla.com"));

            // call updateAddress
            await cache.TryRemoveAddressesAsync(new Documents.Rntbd.ServerKey(new Uri("https://blabla.com")), CancellationToken.None);

            // check if the addresss is updated
            addresses = cache.TryGetAddressesAsync(
                DocumentServiceRequest.Create(OperationType.Invalid, ResourceType.Address, AuthorizationTokenType.Invalid),
                this.testPartitionKeyRangeIdentity,
                this.serviceIdentity,
                false,
                CancellationToken.None).Result;

            Assert.IsNotNull(addresses.AllAddresses.Select(address => address.PhysicalUri == "https://blabla5.com"));
        }
        private async Task <ResolutionResult> TryResolveServerPartitionByPartitionKeyRangeIdAsync(
            DocumentServiceRequest request,
            ContainerProperties collection,
            CollectionRoutingMap routingMap,
            bool collectionCacheIsUpToDate,
            bool routingMapCacheIsUpToDate,
            bool forceRefreshPartitionAddresses,
            CancellationToken cancellationToken)
        {
            PartitionKeyRange partitionKeyRange = routingMap.TryGetRangeByPartitionKeyRangeId(request.PartitionKeyRangeIdentity.PartitionKeyRangeId);

            if (partitionKeyRange == null)
            {
                DefaultTrace.TraceInformation("Cannot resolve range '{0}'", request.PartitionKeyRangeIdentity.ToHeader());

                return(this.HandleRangeAddressResolutionFailure(request, collectionCacheIsUpToDate, routingMapCacheIsUpToDate, routingMap));
            }

            ServiceIdentity identity = routingMap.TryGetInfoByPartitionKeyRangeId(request.PartitionKeyRangeIdentity.PartitionKeyRangeId);

            PartitionAddressInformation addresses = await this.addressCache.TryGetAddressesAsync(
                request,
                new PartitionKeyRangeIdentity(collection.ResourceId, request.PartitionKeyRangeIdentity.PartitionKeyRangeId),
                identity,
                forceRefreshPartitionAddresses,
                cancellationToken);

            if (addresses == null)
            {
                DefaultTrace.TraceInformation("Cannot resolve addresses for range '{0}'", request.PartitionKeyRangeIdentity.ToHeader());

                return(this.HandleRangeAddressResolutionFailure(request, collectionCacheIsUpToDate, routingMapCacheIsUpToDate, routingMap));
            }

            return(new ResolutionResult(partitionKeyRange, addresses, identity));
        }
        private async Task <ResolutionResult> TryResolveServerPartitionAsync(
            DocumentServiceRequest request,
            ContainerProperties collection,
            CollectionRoutingMap routingMap,
            bool collectionCacheIsUptodate,
            bool collectionRoutingMapCacheIsUptodate,
            bool forceRefreshPartitionAddresses,
            CancellationToken cancellationToken)
        {
            // Check if this request partitionkeyrange-aware routing logic. We cannot retry here in this case
            // and need to bubble up errors.
            if (request.PartitionKeyRangeIdentity != null)
            {
                return(await this.TryResolveServerPartitionByPartitionKeyRangeIdAsync(
                           request,
                           collection,
                           routingMap,
                           collectionCacheIsUptodate,
                           collectionRoutingMapCacheIsUptodate,
                           forceRefreshPartitionAddresses,
                           cancellationToken));
            }

            if (!request.ResourceType.IsPartitioned() &&
                !(request.ResourceType == ResourceType.StoredProcedure && request.OperationType == OperationType.ExecuteJavaScript) &&
                // Collection head is sent internally for strong consistency given routing hints from original requst, which is for partitioned resource.
                !(request.ResourceType == ResourceType.Collection && request.OperationType == OperationType.Head))
            {
                DefaultTrace.TraceCritical(
                    "Shouldn't come here for non partitioned resources. resourceType : {0}, operationtype:{1}, resourceaddress:{2}",
                    request.ResourceType,
                    request.OperationType,
                    request.ResourceAddress);
                throw new InternalServerErrorException(RMResources.InternalServerError)
                      {
                          ResourceAddress = request.ResourceAddress
                      };
            }

            PartitionKeyRange range;
            string            partitionKeyString = request.Headers[HttpConstants.HttpHeaders.PartitionKey];

            object effectivePartitionKeyStringObject = null;

            if (partitionKeyString != null)
            {
                range = this.TryResolveServerPartitionByPartitionKey(
                    request,
                    partitionKeyString,
                    collectionCacheIsUptodate,
                    collection,
                    routingMap);
            }
            else if (request.Properties != null && request.Properties.TryGetValue(
                         WFConstants.BackendHeaders.EffectivePartitionKeyString,
                         out effectivePartitionKeyStringObject))
            {
                // Allow EPK only for partitioned collection (excluding migrated fixed collections)
                if (!collection.HasPartitionKey || collection.PartitionKey.IsSystemKey.GetValueOrDefault(false))
                {
                    throw new ArgumentOutOfRangeException(nameof(collection));
                }

                string effectivePartitionKeyString = effectivePartitionKeyStringObject as string;
                if (string.IsNullOrEmpty(effectivePartitionKeyString))
                {
                    throw new ArgumentOutOfRangeException(nameof(effectivePartitionKeyString));
                }

                range = routingMap.GetRangeByEffectivePartitionKey(effectivePartitionKeyString);
            }
            else
            {
                range = this.TryResolveSinglePartitionCollection(request, routingMap, collectionCacheIsUptodate);
            }

            if (range == null)
            {
                // Collection cache or routing map cache is potentially outdated. Return null -
                // upper logic will refresh cache and retry.
                return(null);
            }

            ServiceIdentity serviceIdentity = routingMap.TryGetInfoByPartitionKeyRangeId(range.Id);

            PartitionAddressInformation addresses = await this.addressCache.TryGetAddressesAsync(
                request,
                new PartitionKeyRangeIdentity(collection.ResourceId, range.Id),
                serviceIdentity,
                forceRefreshPartitionAddresses,
                cancellationToken);

            if (addresses == null)
            {
                DefaultTrace.TraceVerbose(
                    "Could not resolve addresses for identity {0}/{1}. Potentially collection cache or routing map cache is outdated. Return null - upper logic will refresh and retry. ",
                    new PartitionKeyRangeIdentity(collection.ResourceId, range.Id),
                    serviceIdentity);
                return(null);
            }

            return(new ResolutionResult(range, addresses, serviceIdentity));
        }
        /// <summary>
        /// Resolves the endpoint of the partition for the given request
        /// </summary>
        /// <param name="request">Request for which the partition endpoint resolution is to be performed</param>
        /// <param name="forceRefreshPartitionAddresses">Force refresh the partition's endpoint</param>
        /// <param name="cancellationToken">Cancellation token</param>
        /// <returns>An instance of <see cref="ResolutionResult"/>.</returns>
        private async Task <ResolutionResult> ResolveAddressesAndIdentityAsync(
            DocumentServiceRequest request,
            bool forceRefreshPartitionAddresses,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (request.ServiceIdentity != null)
            {
                // In this case we don't populate request.RequestContext.ResolvedPartitionKeyRangeId,
                // which is needed for session token.
                // The assumption is that:
                //     1. Master requests never use session consistency.
                //     2. Service requests (like collection create etc.) don't use session consistency.
                //     3. Requests which target specific partition of an existing collection will use x-ms-documentdb-partitionkeyrangeid header
                //        to send request to specific partition and will not set request.ServiceIdentity
                ServiceIdentity             identity  = request.ServiceIdentity;
                PartitionAddressInformation addresses = await this.addressCache.TryGetAddressesAsync(request, null, identity, forceRefreshPartitionAddresses, cancellationToken);

                if (addresses == null)
                {
                    DefaultTrace.TraceInformation("Could not get addresses for explicitly specified ServiceIdentity {0}", identity);
                    throw new NotFoundException()
                          {
                              ResourceAddress = request.ResourceAddress
                          };
                }

                return(new ResolutionResult(addresses, identity));
            }

            if (ReplicatedResourceClient.IsReadingFromMaster(request.ResourceType, request.OperationType) && request.PartitionKeyRangeIdentity == null)
            {
                DefaultTrace.TraceInformation("Resolving Master service address, forceMasterRefresh: {0}, currentMaster: {1}",
                                              request.ForceMasterRefresh,
                                              this.masterServiceIdentityProvider?.MasterServiceIdentity);

                // Client implementation, GlobalAddressResolver passes in a null IMasterServiceIdentityProvider, because it doesn't actually use the serviceIdentity
                // in the addressCache.TryGetAddresses method. In GatewayAddressCache.cs, the master address is resolved by making a call to Gateway AddressFeed,
                // not using the serviceIdentity that is passed in
                if (request.ForceMasterRefresh && this.masterServiceIdentityProvider != null)
                {
                    ServiceIdentity previousMasterService = this.masterServiceIdentityProvider.MasterServiceIdentity;
                    await this.masterServiceIdentityProvider.RefreshAsync(previousMasterService, cancellationToken);
                }
                ServiceIdentity             serviceIdentity           = this.masterServiceIdentityProvider?.MasterServiceIdentity;
                PartitionKeyRangeIdentity   partitionKeyRangeIdentity = this.masterPartitionKeyRangeIdentity;
                PartitionAddressInformation addresses = await this.addressCache.TryGetAddressesAsync(
                    request,
                    partitionKeyRangeIdentity,
                    serviceIdentity,
                    forceRefreshPartitionAddresses,
                    cancellationToken);

                if (addresses == null)
                {
                    // This shouldn't really happen.
                    DefaultTrace.TraceCritical("Could not get addresses for master partition {0}", serviceIdentity);
                    throw new NotFoundException()
                          {
                              ResourceAddress = request.ResourceAddress
                          };
                }

                PartitionKeyRange partitionKeyRange = new PartitionKeyRange {
                    Id = PartitionKeyRange.MasterPartitionKeyRangeId
                };
                return(new ResolutionResult(partitionKeyRange, addresses, serviceIdentity));
            }

            bool collectionCacheIsUptoDate = !request.IsNameBased ||
                                             (request.PartitionKeyRangeIdentity != null && request.PartitionKeyRangeIdentity.CollectionRid != null);

            bool collectionRoutingMapCacheIsUptoDate = false;

            ContainerProperties collection = await this.collectionCache.ResolveCollectionAsync(request, cancellationToken);

            CollectionRoutingMap routingMap = await this.collectionRoutingMapCache.TryLookupAsync(
                collection.ResourceId, null, request, cancellationToken);

            if (routingMap != null && request.ForceCollectionRoutingMapRefresh)
            {
                DefaultTrace.TraceInformation(
                    "AddressResolver.ResolveAddressesAndIdentityAsync ForceCollectionRoutingMapRefresh collection.ResourceId = {0}",
                    collection.ResourceId);

                routingMap = await this.collectionRoutingMapCache.TryLookupAsync(collection.ResourceId, routingMap, request, cancellationToken);
            }

            if (request.ForcePartitionKeyRangeRefresh)
            {
                collectionRoutingMapCacheIsUptoDate   = true;
                request.ForcePartitionKeyRangeRefresh = false;
                if (routingMap != null)
                {
                    routingMap = await this.collectionRoutingMapCache.TryLookupAsync(collection.ResourceId, routingMap, request, cancellationToken);
                }
            }

            if (routingMap == null && !collectionCacheIsUptoDate)
            {
                // Routing map was not found by resolved collection rid. Maybe collection rid is outdated.
                // Refresh collection cache and reresolve routing map.
                request.ForceNameCacheRefresh       = true;
                collectionCacheIsUptoDate           = true;
                collectionRoutingMapCacheIsUptoDate = false;
                collection = await this.collectionCache.ResolveCollectionAsync(request, cancellationToken);

                routingMap = await this.collectionRoutingMapCache.TryLookupAsync(
                    collection.ResourceId,
                    previousValue : null,
                    request : request,
                    cancellationToken : cancellationToken);
            }

            AddressResolver.EnsureRoutingMapPresent(request, routingMap, collection);

            // At this point we have both collection and routingMap.
            ResolutionResult result = await this.TryResolveServerPartitionAsync(
                request,
                collection,
                routingMap,
                collectionCacheIsUptoDate,
                collectionRoutingMapCacheIsUptodate : collectionRoutingMapCacheIsUptoDate,
                forceRefreshPartitionAddresses : forceRefreshPartitionAddresses,
                cancellationToken : cancellationToken);

            if (result == null)
            {
                // Couldn't resolve server partition or its addresses.
                // Either collection cache is outdated or routing map cache is outdated.
                if (!collectionCacheIsUptoDate)
                {
                    request.ForceNameCacheRefresh = true;
                    collectionCacheIsUptoDate     = true;
                    collection = await this.collectionCache.ResolveCollectionAsync(request, cancellationToken);

                    if (collection.ResourceId != routingMap.CollectionUniqueId)
                    {
                        // Collection cache was stale. We resolved to new Rid. routing map cache is potentially stale
                        // for this new collection rid. Mark it as such.
                        collectionRoutingMapCacheIsUptoDate = false;
                        routingMap = await this.collectionRoutingMapCache.TryLookupAsync(
                            collection.ResourceId,
                            previousValue : null,
                            request : request,
                            cancellationToken : cancellationToken);
                    }
                }

                if (!collectionRoutingMapCacheIsUptoDate)
                {
                    collectionRoutingMapCacheIsUptoDate = true;
                    routingMap = await this.collectionRoutingMapCache.TryLookupAsync(
                        collection.ResourceId,
                        previousValue : routingMap,
                        request : request,
                        cancellationToken : cancellationToken);
                }

                AddressResolver.EnsureRoutingMapPresent(request, routingMap, collection);

                result = await this.TryResolveServerPartitionAsync(
                    request,
                    collection,
                    routingMap,
                    collectionCacheIsUptodate : true,
                    collectionRoutingMapCacheIsUptodate : true,
                    forceRefreshPartitionAddresses : forceRefreshPartitionAddresses,
                    cancellationToken : cancellationToken);
            }

            if (result == null)
            {
                DefaultTrace.TraceInformation("Couldn't route partitionkeyrange-oblivious request after retry/cache refresh. Collection doesn't exist.");

                // At this point collection cache and routing map caches are refreshed.
                // The only reason we will get here is if collection doesn't exist.
                // Case when partitionkeyrange doesn't exist is handled in the corresponding method.
                throw new NotFoundException()
                      {
                          ResourceAddress = request.ResourceAddress
                      };
            }

            if (request.IsNameBased)
            {
                // Append collection rid.
                // If we resolved collection rid incorrectly because of outdated cache, this can lead
                // to incorrect routing decisions. But backend will validate collection rid and throw
                // InvalidPartitionException if we reach wrong collection.
                // Also this header will be used by backend to inject collection rid into metrics for
                // throttled requests.
                request.Headers[WFConstants.BackendHeaders.CollectionRid] = collection.ResourceId;
            }

            return(result);
        }
        public async Task <PartitionAddressInformation> TryGetAddresses(
            DocumentServiceRequest request,
            PartitionKeyRangeIdentity partitionKeyRangeIdentity,
            ServiceIdentity serviceIdentity,
            bool forceRefreshPartitionAddresses,
            CancellationToken cancellationToken)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            if (partitionKeyRangeIdentity == null)
            {
                throw new ArgumentNullException("partitionKeyRangeIdentity");
            }

            try
            {
                if (partitionKeyRangeIdentity.PartitionKeyRangeId == PartitionKeyRange.MasterPartitionKeyRangeId)
                {
                    return((await this.ResolveMasterAsync(request, forceRefreshPartitionAddresses)).Item2);
                }

                DateTime suboptimalServerPartitionTimestamp;
                if (this.suboptimalServerPartitionTimestamps.TryGetValue(partitionKeyRangeIdentity, out suboptimalServerPartitionTimestamp))
                {
                    bool forceRefreshDueToSuboptimalPartitionReplicaSet =
                        DateTime.UtcNow.Subtract(suboptimalServerPartitionTimestamp) > TimeSpan.FromSeconds(this.suboptimalPartitionForceRefreshIntervalInSeconds);

                    if (forceRefreshDueToSuboptimalPartitionReplicaSet && this.suboptimalServerPartitionTimestamps.TryUpdate(partitionKeyRangeIdentity, DateTime.MaxValue, suboptimalServerPartitionTimestamp))
                    {
                        forceRefreshPartitionAddresses = true;
                    }
                }

                if (forceRefreshPartitionAddresses || request.ForceCollectionRoutingMapRefresh)
                {
                    this.serverPartitionAddressCache.Refresh(
                        partitionKeyRangeIdentity,
                        async() => await this.GetAddressesForRangeId(
                            request,
                            partitionKeyRangeIdentity.CollectionRid,
                            partitionKeyRangeIdentity.PartitionKeyRangeId,
                            forceRefresh: forceRefreshPartitionAddresses),
                        cancellationToken);

                    DateTime ignoreDateTime;
                    this.suboptimalServerPartitionTimestamps.TryRemove(partitionKeyRangeIdentity, out ignoreDateTime);
                }

                PartitionAddressInformation addresses = await this.serverPartitionAddressCache.GetAsync(
                    partitionKeyRangeIdentity,
                    null,
                    async() => await this.GetAddressesForRangeId(
                        request,
                        partitionKeyRangeIdentity.CollectionRid,
                        partitionKeyRangeIdentity.PartitionKeyRangeId,
                        forceRefresh: false),
                    cancellationToken);

                int targetReplicaSetSize = this.serviceConfigReader.UserReplicationPolicy.MaxReplicaSetSize;
                if (addresses.AllAddresses.Count() < targetReplicaSetSize)
                {
                    this.suboptimalServerPartitionTimestamps.TryAdd(partitionKeyRangeIdentity, DateTime.UtcNow);
                }

                return(addresses);
            }
            catch (DocumentClientException ex)
            {
                if ((ex.StatusCode == HttpStatusCode.NotFound) ||
                    (ex.StatusCode == HttpStatusCode.Gone && ex.GetSubStatus() == SubStatusCodes.PartitionKeyRangeGone))
                {
                    //remove from suboptimal cache in case the the collection+pKeyRangeId combo is gone.
                    DateTime ignoreDateTime;
                    this.suboptimalServerPartitionTimestamps.TryRemove(partitionKeyRangeIdentity, out ignoreDateTime);

                    return(null);
                }

                throw;
            }
            catch (Exception)
            {
                if (forceRefreshPartitionAddresses)
                {
                    DateTime ignoreDateTime;
                    this.suboptimalServerPartitionTimestamps.TryRemove(partitionKeyRangeIdentity, out ignoreDateTime);
                }

                throw;
            }
        }
        public async Task TestGatewayAddressCacheAvoidCacheRefresWhenAlreadyUpdatedAsync()
        {
            Mock <IHttpHandler> mockHttpHandler = new Mock <IHttpHandler>(MockBehavior.Strict);
            string oldAddress = "rntbd://dummytenant.documents.azure.com:14003/apps/APPGUID/services/SERVICEGUID/partitions/PARTITIONGUID/replicas/4s";
            string newAddress = "rntbd://dummytenant.documents.azure.com:14003/apps/APPGUID/services/SERVICEGUID/partitions/PARTITIONGUID/replicas/5s";

            mockHttpHandler.SetupSequence(x => x.SendAsync(
                                              It.IsAny <HttpRequestMessage>(),
                                              It.IsAny <CancellationToken>()))
            .Returns(MockCosmosUtil.CreateHttpResponseOfAddresses(new List <string>()
            {
                "rntbd://dummytenant.documents.azure.com:14003/apps/APPGUID/services/SERVICEGUID/partitions/PARTITIONGUID/replicas/1p",
                "rntbd://dummytenant.documents.azure.com:14003/apps/APPGUID/services/SERVICEGUID/partitions/PARTITIONGUID/replicas/2s",
                "rntbd://dummytenant.documents.azure.com:14003/apps/APPGUID/services/SERVICEGUID/partitions/PARTITIONGUID/replicas/3s",
                oldAddress,
            }))
            .Returns(MockCosmosUtil.CreateHttpResponseOfAddresses(new List <string>()
            {
                "rntbd://dummytenant.documents.azure.com:14003/apps/APPGUID/services/SERVICEGUID/partitions/PARTITIONGUID/replicas/1p",
                "rntbd://dummytenant.documents.azure.com:14003/apps/APPGUID/services/SERVICEGUID/partitions/PARTITIONGUID/replicas/2s",
                "rntbd://dummytenant.documents.azure.com:14003/apps/APPGUID/services/SERVICEGUID/partitions/PARTITIONGUID/replicas/3s",
                newAddress,
            }));

            HttpClient          httpClient = new HttpClient(new HttpHandlerHelper(mockHttpHandler.Object));
            GatewayAddressCache cache      = new GatewayAddressCache(
                new Uri(GatewayAddressCacheTests.DatabaseAccountApiEndpoint),
                Documents.Client.Protocol.Tcp,
                this.mockTokenProvider.Object,
                this.mockServiceConfigReader.Object,
                MockCosmosUtil.CreateCosmosHttpClient(() => httpClient),
                suboptimalPartitionForceRefreshIntervalInSeconds: 2,
                enableTcpConnectionEndpointRediscovery: true);

            DocumentServiceRequest request1 = DocumentServiceRequest.Create(OperationType.Invalid, ResourceType.Address, AuthorizationTokenType.Invalid);
            DocumentServiceRequest request2 = DocumentServiceRequest.Create(OperationType.Invalid, ResourceType.Address, AuthorizationTokenType.Invalid);

            PartitionAddressInformation request1Addresses = await cache.TryGetAddressesAsync(
                request : request1,
                partitionKeyRangeIdentity : this.testPartitionKeyRangeIdentity,
                serviceIdentity : this.serviceIdentity,
                forceRefreshPartitionAddresses : false,
                cancellationToken : CancellationToken.None);

            PartitionAddressInformation request2Addresses = await cache.TryGetAddressesAsync(
                request : request2,
                partitionKeyRangeIdentity : this.testPartitionKeyRangeIdentity,
                serviceIdentity : this.serviceIdentity,
                forceRefreshPartitionAddresses : false,
                cancellationToken : CancellationToken.None);

            Assert.AreEqual(request1Addresses, request2Addresses);
            Assert.AreEqual(4, request1Addresses.AllAddresses.Count());
            Assert.AreEqual(1, request1Addresses.AllAddresses.Count(x => x.PhysicalUri == oldAddress));
            Assert.AreEqual(0, request1Addresses.AllAddresses.Count(x => x.PhysicalUri == newAddress));

            // check if the addresss is updated
            request1Addresses = await cache.TryGetAddressesAsync(
                request : request1,
                partitionKeyRangeIdentity : this.testPartitionKeyRangeIdentity,
                serviceIdentity : this.serviceIdentity,
                forceRefreshPartitionAddresses : true,
                cancellationToken : CancellationToken.None);

            // Even though force refresh is true it will just use the new cache
            // value rather than doing a gateway call to do another refresh since the value
            // already changed from the last cache access
            request2Addresses = await cache.TryGetAddressesAsync(
                request : request2,
                partitionKeyRangeIdentity : this.testPartitionKeyRangeIdentity,
                serviceIdentity : this.serviceIdentity,
                forceRefreshPartitionAddresses : true,
                cancellationToken : CancellationToken.None);

            Assert.AreEqual(request1Addresses, request2Addresses);
            Assert.AreEqual(4, request1Addresses.AllAddresses.Count());
            Assert.AreEqual(0, request1Addresses.AllAddresses.Count(x => x.PhysicalUri == oldAddress));
            Assert.AreEqual(1, request1Addresses.AllAddresses.Count(x => x.PhysicalUri == newAddress));

            mockHttpHandler.VerifyAll();
        }
Esempio n. 14
0
        public void GlobalStrongConsistencyMockTest()
        {
            // create a real document service request (with auth token level = god)
            DocumentServiceRequest entity = DocumentServiceRequest.Create(OperationType.Read, ResourceType.Document, AuthorizationTokenType.SystemAll);

            // set request charge tracker -  this is referenced in store reader (ReadMultipleReplicaAsync)
            DocumentServiceRequestContext requestContext = new DocumentServiceRequestContext
            {
                RequestChargeTracker = new RequestChargeTracker()
            };

            entity.RequestContext = requestContext;

            // set a dummy resource id on the request.
            entity.ResourceId = "1-MxAPlgMgA=";

            // set consistency level on the request to Bounded Staleness
            entity.Headers[HttpConstants.HttpHeaders.ConsistencyLevel] = ConsistencyLevel.BoundedStaleness.ToString();

            // also setup timeout helper, used in store reader
            entity.RequestContext.TimeoutHelper = new TimeoutHelper(new TimeSpan(2, 2, 2));

            // when the store reader throws Invalid Partition exception, the higher layer should
            // clear this target identity.
            entity.RequestContext.TargetIdentity            = new ServiceIdentity("dummyTargetIdentity1", new Uri("http://dummyTargetIdentity1"), false);
            entity.RequestContext.ResolvedPartitionKeyRange = new PartitionKeyRange();

            AddressInformation[]    addressInformation = this.GetMockAddressInformationDuringUpgrade();
            Mock <IAddressResolver> mockAddressCache   = this.GetMockAddressCache(addressInformation);

            // validate that the mock works
            PartitionAddressInformation        partitionAddressInformation = mockAddressCache.Object.ResolveAsync(entity, false, new CancellationToken()).Result;
            IReadOnlyList <AddressInformation> addressInfo = partitionAddressInformation.AllAddresses;

            Assert.IsTrue(addressInfo[0] == addressInformation[0]);

            AddressSelector addressSelector = new AddressSelector(mockAddressCache.Object, Protocol.Tcp);
            Tuple <Uri, AddressCacheToken> primaryAddress = addressSelector.ResolvePrimaryUriAsync(entity, false /*forceAddressRefresh*/).Result;

            // check if the address return from Address Selector matches the original address info
            Assert.IsTrue(primaryAddress.Item1.Equals(addressInformation[0].PhysicalUri));

            // Quorum Met scenario
            {
                // get mock transport client that returns a sequence of responses to simulate upgrade
                TransportClient mockTransportClient = this.GetMockTransportClientForGlobalStrongReads(addressInformation, ReadQuorumResultKind.QuorumMet);

                // create a real session container - we don't need session for this test anyway
                ISessionContainer sessionContainer = new SessionContainer(string.Empty);

                ConnectionStateListener connectionStateListener = new ConnectionStateListener(null);

                // create store reader with mock transport client, real address selector (that has mock address cache), and real session container
                StoreReader storeReader =
                    new StoreReader(mockTransportClient,
                                    addressSelector,
                                    sessionContainer,
                                    connectionStateListener);

                Mock <IAuthorizationTokenProvider> mockAuthorizationTokenProvider = new Mock <IAuthorizationTokenProvider>();
                mockAuthorizationTokenProvider.Setup(provider => provider.AddSystemAuthorizationHeaderAsync(
                                                         It.IsAny <DocumentServiceRequest>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>()))
                .Returns(Task.FromResult(0));

                // setup max replica set size on the config reader
                ReplicationPolicy replicationPolicy = new ReplicationPolicy
                {
                    MaxReplicaSetSize = 4
                };
                Mock <IServiceConfigurationReader> mockServiceConfigReader = new Mock <IServiceConfigurationReader>();
                mockServiceConfigReader.SetupGet(x => x.UserReplicationPolicy).Returns(replicationPolicy);

                QuorumReader reader =
                    new QuorumReader(mockTransportClient, addressSelector, storeReader, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object);

                entity.RequestContext.OriginalRequestConsistencyLevel = Documents.ConsistencyLevel.Strong;
                entity.RequestContext.ClientRequestStatistics         = new ClientSideRequestStatistics();

                StoreResponse result = reader.ReadStrongAsync(entity, 2, ReadMode.Strong).Result;
                Assert.IsTrue(result.LSN == 100);

                result.TryGetHeaderValue(WFConstants.BackendHeaders.GlobalCommittedLSN, out string globalCommitedLSN);

                long nGlobalCommitedLSN = long.Parse(globalCommitedLSN, CultureInfo.InvariantCulture);
                Assert.IsTrue(nGlobalCommitedLSN == 90);
            }

            // Quorum Selected scenario
            {
                // get mock transport client that returns a sequence of responses to simulate upgrade
                TransportClient mockTransportClient = this.GetMockTransportClientForGlobalStrongReads(addressInformation, ReadQuorumResultKind.QuorumSelected);

                // create a real session container - we don't need session for this test anyway
                ISessionContainer sessionContainer = new SessionContainer(string.Empty);

                ConnectionStateListener connectionStateListener = new ConnectionStateListener(null);

                // create store reader with mock transport client, real address selector (that has mock address cache), and real session container
                StoreReader storeReader =
                    new StoreReader(mockTransportClient,
                                    addressSelector,
                                    sessionContainer,
                                    connectionStateListener);

                Mock <IAuthorizationTokenProvider> mockAuthorizationTokenProvider = new Mock <IAuthorizationTokenProvider>();
                mockAuthorizationTokenProvider.Setup(provider => provider.AddSystemAuthorizationHeaderAsync(
                                                         It.IsAny <DocumentServiceRequest>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>()))
                .Returns(Task.FromResult(0));

                // setup max replica set size on the config reader
                ReplicationPolicy replicationPolicy = new ReplicationPolicy
                {
                    MaxReplicaSetSize = 4
                };
                Mock <IServiceConfigurationReader> mockServiceConfigReader = new Mock <IServiceConfigurationReader>();
                mockServiceConfigReader.SetupGet(x => x.UserReplicationPolicy).Returns(replicationPolicy);

                QuorumReader reader =
                    new QuorumReader(mockTransportClient, addressSelector, storeReader, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object);

                entity.RequestContext.OriginalRequestConsistencyLevel = Documents.ConsistencyLevel.Strong;
                entity.RequestContext.ClientRequestStatistics         = new ClientSideRequestStatistics();
                entity.RequestContext.QuorumSelectedLSN          = -1;
                entity.RequestContext.GlobalCommittedSelectedLSN = -1;
                try
                {
                    StoreResponse result = reader.ReadStrongAsync(entity, 2, ReadMode.Strong).Result;
                    Assert.IsTrue(false);
                }
                catch (AggregateException ex)
                {
                    if (ex.InnerException is GoneException)
                    {
                        DefaultTrace.TraceInformation("Gone exception expected!");
                    }
                }

                Assert.IsTrue(entity.RequestContext.QuorumSelectedLSN == 100);
                Assert.IsTrue(entity.RequestContext.GlobalCommittedSelectedLSN == 100);
            }

            // Quorum not met scenario
            {
                // get mock transport client that returns a sequence of responses to simulate upgrade
                TransportClient mockTransportClient = this.GetMockTransportClientForGlobalStrongReads(addressInformation, ReadQuorumResultKind.QuorumNotSelected);

                // create a real session container - we don't need session for this test anyway
                ISessionContainer sessionContainer = new SessionContainer(string.Empty);

                ConnectionStateListener connectionStateListener = new ConnectionStateListener(null);

                // create store reader with mock transport client, real address selector (that has mock address cache), and real session container
                StoreReader storeReader =
                    new StoreReader(mockTransportClient,
                                    addressSelector,
                                    sessionContainer,
                                    connectionStateListener);

                Mock <IAuthorizationTokenProvider> mockAuthorizationTokenProvider = new Mock <IAuthorizationTokenProvider>();
                mockAuthorizationTokenProvider.Setup(provider => provider.AddSystemAuthorizationHeaderAsync(
                                                         It.IsAny <DocumentServiceRequest>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>()))
                .Returns(Task.FromResult(0));

                // setup max replica set size on the config reader
                ReplicationPolicy replicationPolicy = new ReplicationPolicy
                {
                    MaxReplicaSetSize = 4
                };
                Mock <IServiceConfigurationReader> mockServiceConfigReader = new Mock <IServiceConfigurationReader>();
                mockServiceConfigReader.SetupGet(x => x.UserReplicationPolicy).Returns(replicationPolicy);

                QuorumReader reader =
                    new QuorumReader(mockTransportClient, addressSelector, storeReader, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object);

                entity.RequestContext.PerformLocalRefreshOnGoneException = true;
                entity.RequestContext.OriginalRequestConsistencyLevel    = Documents.ConsistencyLevel.Strong;
                entity.RequestContext.ClientRequestStatistics            = new ClientSideRequestStatistics();

                StoreResponse result = reader.ReadStrongAsync(entity, 2, ReadMode.Strong).Result;
                Assert.IsTrue(result.LSN == 100);

                result.TryGetHeaderValue(WFConstants.BackendHeaders.GlobalCommittedLSN, out string globalCommitedLSN);

                long nGlobalCommitedLSN = long.Parse(globalCommitedLSN, CultureInfo.InvariantCulture);
                Assert.IsTrue(nGlobalCommitedLSN == 90);
            }
        }
Esempio n. 15
0
        public void GlobalStrongConsistentWriteMockTest()
        {
            // create a real document service request (with auth token level = god)
            DocumentServiceRequest entity = DocumentServiceRequest.Create(OperationType.Create, ResourceType.Document, AuthorizationTokenType.SystemAll);

            // set request charge tracker -  this is referenced in store reader (ReadMultipleReplicaAsync)
            DocumentServiceRequestContext requestContext = new DocumentServiceRequestContext
            {
                RequestChargeTracker = new RequestChargeTracker()
            };

            entity.RequestContext = requestContext;

            // set a dummy resource id on the request.
            entity.ResourceId = "1-MxAPlgMgA=";

            // set consistency level on the request to Bounded Staleness
            entity.Headers[HttpConstants.HttpHeaders.ConsistencyLevel] = ConsistencyLevel.Strong.ToString();

            // also setup timeout helper, used in store reader
            entity.RequestContext.TimeoutHelper = new TimeoutHelper(new TimeSpan(2, 2, 2));

            // when the store reader throws Invalid Partition exception, the higher layer should
            // clear this target identity.
            entity.RequestContext.TargetIdentity            = new ServiceIdentity("dummyTargetIdentity1", new Uri("http://dummyTargetIdentity1"), false);
            entity.RequestContext.ResolvedPartitionKeyRange = new PartitionKeyRange();

            AddressInformation[]    addressInformation = this.GetMockAddressInformationDuringUpgrade();
            Mock <IAddressResolver> mockAddressCache   = this.GetMockAddressCache(addressInformation);

            // validate that the mock works
            PartitionAddressInformation        partitionAddressInformation = mockAddressCache.Object.ResolveAsync(entity, false, new CancellationToken()).Result;
            IReadOnlyList <AddressInformation> addressInfo = partitionAddressInformation.AllAddresses;

            Assert.IsTrue(addressInfo[0] == addressInformation[0]);

            AddressSelector addressSelector = new AddressSelector(mockAddressCache.Object, Protocol.Tcp);
            Tuple <Uri, AddressCacheToken> primaryAddress = addressSelector.ResolvePrimaryUriAsync(entity, false /*forceAddressRefresh*/).Result;

            // check if the address return from Address Selector matches the original address info
            Assert.IsTrue(primaryAddress.Item1.Equals(addressInformation[0].PhysicalUri));

            // create a real session container - we don't need session for this test anyway
            ISessionContainer sessionContainer = new SessionContainer(string.Empty);

            ConnectionStateListener connectionStateListener = new ConnectionStateListener(null);

            Mock <IServiceConfigurationReader> mockServiceConfigReader = new Mock <IServiceConfigurationReader>();

            Mock <IAuthorizationTokenProvider> mockAuthorizationTokenProvider = new Mock <IAuthorizationTokenProvider>();

            mockAuthorizationTokenProvider.Setup(provider => provider.AddSystemAuthorizationHeaderAsync(
                                                     It.IsAny <DocumentServiceRequest>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>()))
            .Returns(Task.FromResult(0));

            for (int i = 0; i < addressInformation.Length; i++)
            {
                TransportClient   mockTransportClient = this.GetMockTransportClientForGlobalStrongWrites(addressInformation, i, false, false, false);
                StoreReader       storeReader         = new StoreReader(mockTransportClient, addressSelector, sessionContainer, connectionStateListener);
                ConsistencyWriter consistencyWriter   = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, connectionStateListener, false);
                StoreResponse     response            = consistencyWriter.WriteAsync(entity, new TimeoutHelper(TimeSpan.FromSeconds(30)), false).Result;
                Assert.AreEqual(100, response.LSN);

                //globalCommittedLsn never catches up in this case
                mockTransportClient = this.GetMockTransportClientForGlobalStrongWrites(addressInformation, i, true, false, false);
                consistencyWriter   = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, connectionStateListener, false);
                try
                {
                    response = consistencyWriter.WriteAsync(entity, new TimeoutHelper(TimeSpan.FromSeconds(30)), false).Result;
                    Assert.Fail();
                }
                catch (Exception)
                {
                }

                mockTransportClient = this.GetMockTransportClientForGlobalStrongWrites(addressInformation, i, false, true, false);
                consistencyWriter   = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, connectionStateListener, false);
                response            = consistencyWriter.WriteAsync(entity, new TimeoutHelper(TimeSpan.FromSeconds(30)), false).Result;
                Assert.AreEqual(100, response.LSN);

                mockTransportClient = this.GetMockTransportClientForGlobalStrongWrites(addressInformation, i, false, true, true);
                consistencyWriter   = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, connectionStateListener, false);
                response            = consistencyWriter.WriteAsync(entity, new TimeoutHelper(TimeSpan.FromSeconds(30)), false).Result;
                Assert.AreEqual(100, response.LSN);

                mockTransportClient = this.GetMockTransportClientForGlobalStrongWrites(addressInformation, i, false, false, true);
                consistencyWriter   = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, connectionStateListener, false);
                response            = consistencyWriter.WriteAsync(entity, new TimeoutHelper(TimeSpan.FromSeconds(30)), false).Result;
                Assert.AreEqual(100, response.LSN);
            }
        }
Esempio n. 16
0
        public void StoreReaderBarrierTest()
        {
            // create a real document service request
            DocumentServiceRequest entity = DocumentServiceRequest.Create(OperationType.Read, ResourceType.Document, AuthorizationTokenType.PrimaryMasterKey);

            // set request charge tracker -  this is referenced in store reader (ReadMultipleReplicaAsync)
            DocumentServiceRequestContext requestContext = new DocumentServiceRequestContext
            {
                ClientRequestStatistics = new ClientSideRequestStatistics(),
                RequestChargeTracker    = new RequestChargeTracker()
            };

            entity.RequestContext = requestContext;

            // also setup timeout helper, used in store reader
            entity.RequestContext.TimeoutHelper = new TimeoutHelper(new TimeSpan(2, 2, 2));

            // when the store reader throws Invalid Partition exception, the higher layer should
            // clear this target identity.
            entity.RequestContext.TargetIdentity            = new ServiceIdentity("dummyTargetIdentity1", new Uri("http://dummyTargetIdentity1"), false);
            entity.RequestContext.ResolvedPartitionKeyRange = new PartitionKeyRange();

            AddressInformation[]    addressInformation = this.GetMockAddressInformationDuringUpgrade();
            Mock <IAddressResolver> mockAddressCache   = this.GetMockAddressCache(addressInformation);

            // validate that the mock works
            PartitionAddressInformation        partitionAddressInformation = mockAddressCache.Object.ResolveAsync(entity, false, new CancellationToken()).Result;
            IReadOnlyList <AddressInformation> addressInfo = partitionAddressInformation.AllAddresses;

            Assert.IsTrue(addressInfo[0] == addressInformation[0]);

            AddressSelector addressSelector = new AddressSelector(mockAddressCache.Object, Protocol.Tcp);
            Tuple <Uri, AddressCacheToken> primaryAddress = addressSelector.ResolvePrimaryUriAsync(entity, false /*forceAddressRefresh*/).Result;

            // check if the address return from Address Selector matches the original address info
            Assert.IsTrue(primaryAddress.Item1.Equals(addressInformation[0].PhysicalUri));

            // get mock transport client that returns a sequence of responses to simulate upgrade
            TransportClient mockTransportClient = this.GetMockTransportClientDuringUpgrade(addressInformation);

            // get response from mock object
            StoreResponse response = mockTransportClient.InvokeResourceOperationAsync(new Uri(addressInformation[0].PhysicalUri), entity).Result;

            // validate that the LSN matches
            Assert.IsTrue(response.LSN == 50);

            response.TryGetHeaderValue(WFConstants.BackendHeaders.ActivityId, out string activityId);

            // validate that the ActivityId Matches
            Assert.IsTrue(activityId == "ACTIVITYID1_1");

            // create a real session container - we don't need session for this test anyway
            ISessionContainer sessionContainer = new SessionContainer(string.Empty);

            ConnectionStateListener connectionStateListener = new ConnectionStateListener(null);

            // create store reader with mock transport client, real address selector (that has mock address cache), and real session container
            StoreReader storeReader =
                new StoreReader(mockTransportClient,
                                addressSelector,
                                sessionContainer,
                                connectionStateListener);

            // reads always go to read quorum (2) replicas
            int replicaCountToRead = 2;

            IList <StoreResult> result = storeReader.ReadMultipleReplicaAsync(
                entity,
                false /*includePrimary*/,
                replicaCountToRead,
                true /*requiresValidLSN*/,
                false /*useSessionToken*/,
                ReadMode.Strong).Result;

            // make sure we got 2 responses from the store reader
            Assert.IsTrue(result.Count == 2);
        }
        public async Task <PartitionAddressInformation> TryGetAddressesAsync(
            DocumentServiceRequest request,
            PartitionKeyRangeIdentity partitionKeyRangeIdentity,
            ServiceIdentity serviceIdentity,
            bool forceRefreshPartitionAddresses,
            CancellationToken cancellationToken)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            if (partitionKeyRangeIdentity == null)
            {
                throw new ArgumentNullException(nameof(partitionKeyRangeIdentity));
            }

            try
            {
                if (partitionKeyRangeIdentity.PartitionKeyRangeId == PartitionKeyRange.MasterPartitionKeyRangeId)
                {
                    return((await this.ResolveMasterAsync(request, forceRefreshPartitionAddresses)).Item2);
                }

                if (this.suboptimalServerPartitionTimestamps.TryGetValue(partitionKeyRangeIdentity, out DateTime suboptimalServerPartitionTimestamp))
                {
                    bool forceRefreshDueToSuboptimalPartitionReplicaSet =
                        DateTime.UtcNow.Subtract(suboptimalServerPartitionTimestamp) > TimeSpan.FromSeconds(this.suboptimalPartitionForceRefreshIntervalInSeconds);

                    if (forceRefreshDueToSuboptimalPartitionReplicaSet && this.suboptimalServerPartitionTimestamps.TryUpdate(partitionKeyRangeIdentity, DateTime.MaxValue, suboptimalServerPartitionTimestamp))
                    {
                        forceRefreshPartitionAddresses = true;
                    }
                }

                PartitionAddressInformation addresses;
                PartitionAddressInformation staleAddressInfo = null;
                if (forceRefreshPartitionAddresses || request.ForceCollectionRoutingMapRefresh)
                {
                    addresses = await this.serverPartitionAddressCache.GetAsync(
                        key : partitionKeyRangeIdentity,
                        singleValueInitFunc : (currentCachedValue) =>
                    {
                        staleAddressInfo = currentCachedValue;

                        GatewayAddressCache.SetTransportAddressUrisToUnhealthy(
                            currentCachedValue,
                            request?.RequestContext?.FailedEndpoints);

                        return(this.GetAddressesForRangeIdAsync(
                                   request,
                                   partitionKeyRangeIdentity.CollectionRid,
                                   partitionKeyRangeIdentity.PartitionKeyRangeId,
                                   forceRefresh: forceRefreshPartitionAddresses));
                    },
                        forceRefresh : (currentCachedValue) =>
                    {
                        int cachedHashCode = request?.RequestContext?.LastPartitionAddressInformationHashCode ?? 0;
                        if (cachedHashCode == 0)
                        {
                            return(true);
                        }

                        // The cached value is different then the previous access hash then assume
                        // another request already updated the cache since there is a new value in the cache
                        return(currentCachedValue.GetHashCode() == cachedHashCode);
                    });

                    if (staleAddressInfo != null)
                    {
                        GatewayAddressCache.LogPartitionCacheRefresh(request.RequestContext.ClientRequestStatistics, staleAddressInfo, addresses);
                    }

                    this.suboptimalServerPartitionTimestamps.TryRemove(partitionKeyRangeIdentity, out DateTime ignoreDateTime);
                }
                else
                {
                    addresses = await this.serverPartitionAddressCache.GetAsync(
                        key : partitionKeyRangeIdentity,
                        singleValueInitFunc : (_) => this.GetAddressesForRangeIdAsync(
                            request,
                            partitionKeyRangeIdentity.CollectionRid,
                            partitionKeyRangeIdentity.PartitionKeyRangeId,
                            forceRefresh: false),
                        forceRefresh : (_) => false);
                }

                // Always save the hash code. This is used to determine if another request already updated the cache.
                // This helps reduce latency by avoiding uncessary cache refreshes.
                if (request?.RequestContext != null)
                {
                    request.RequestContext.LastPartitionAddressInformationHashCode = addresses.GetHashCode();
                }

                int targetReplicaSetSize = this.serviceConfigReader.UserReplicationPolicy.MaxReplicaSetSize;
                if (addresses.AllAddresses.Count() < targetReplicaSetSize)
                {
                    this.suboptimalServerPartitionTimestamps.TryAdd(partitionKeyRangeIdentity, DateTime.UtcNow);
                }

                return(addresses);
            }
            catch (DocumentClientException ex)
            {
                if ((ex.StatusCode == HttpStatusCode.NotFound) ||
                    (ex.StatusCode == HttpStatusCode.Gone && ex.GetSubStatus() == SubStatusCodes.PartitionKeyRangeGone))
                {
                    //remove from suboptimal cache in case the the collection+pKeyRangeId combo is gone.
                    this.suboptimalServerPartitionTimestamps.TryRemove(partitionKeyRangeIdentity, out _);

                    return(null);
                }

                throw;
            }
            catch (Exception)
            {
                if (forceRefreshPartitionAddresses)
                {
                    this.suboptimalServerPartitionTimestamps.TryRemove(partitionKeyRangeIdentity, out _);
                }

                throw;
            }
        }
        public void MockStoreClientTest()
        {
            // create a real document service request (with auth token level = god)
            DocumentServiceRequest entity = DocumentServiceRequest.Create(OperationType.Read, ResourceType.Document, AuthorizationTokenType.PrimaryMasterKey);

            // set request charge tracker -  this is referenced in store reader (ReadMultipleReplicaAsync)
            var requestContext = new DocumentServiceRequestContext();

            requestContext.RequestChargeTracker = new RequestChargeTracker();
            entity.RequestContext = requestContext;

            // set a dummy resource id on the request.
            entity.ResourceId = "1-MxAPlgMgA=";

            // set consistency level on the request to Bounded Staleness
            entity.Headers[HttpConstants.HttpHeaders.ConsistencyLevel] = ConsistencyLevel.BoundedStaleness.ToString();

            // also setup timeout helper, used in store reader
            entity.RequestContext.TimeoutHelper = new TimeoutHelper(new TimeSpan(2, 2, 2));

            // when the store reader throws Invalid Partition exception, the higher layer should
            // clear this target identity.
            entity.RequestContext.TargetIdentity            = new ServiceIdentity("dummyTargetIdentity1", new Uri("http://dummyTargetIdentity1"), false);
            entity.RequestContext.ResolvedPartitionKeyRange = new PartitionKeyRange();

            AddressInformation[] addressInformation = GetMockAddressInformationDuringUpgrade();
            var mockAddressCache = GetMockAddressCache(addressInformation);

            // validate that the mock works
            PartitionAddressInformation partitionAddressInformation = mockAddressCache.Object.ResolveAsync(entity, false, new CancellationToken()).Result;
            var addressInfo = partitionAddressInformation.AllAddresses;

            Assert.IsTrue(addressInfo[0] == addressInformation[0]);

            AddressSelector addressSelector = new AddressSelector(mockAddressCache.Object, Protocol.Tcp);
            var             primaryAddress  = addressSelector.ResolvePrimaryUriAsync(entity, false /*forceAddressRefresh*/).Result;

            // check if the address return from Address Selector matches the original address info
            Assert.IsTrue(primaryAddress.Equals(addressInformation[0].PhysicalUri));

            // get mock transport client that returns a sequence of responses to simulate upgrade
            var mockTransportClient = GetMockTransportClientDuringUpgrade(addressInformation);

            // get response from mock object
            var response = mockTransportClient.InvokeResourceOperationAsync(new Uri(addressInformation[0].PhysicalUri), entity).Result;

            // validate that the LSN matches
            Assert.IsTrue(response.LSN == 50);

            string activityId;

            response.TryGetHeaderValue(WFConstants.BackendHeaders.ActivityId, out activityId);

            // validate that the ActivityId Matches
            Assert.IsTrue(activityId == "ACTIVITYID1_1");

            // create a real session container - we don't need session for this test anyway
            ISessionContainer sessionContainer = new SessionContainer(string.Empty);

            // create store reader with mock transport client, real address selector (that has mock address cache), and real session container
            StoreReader storeReader =
                new StoreReader(mockTransportClient,
                                addressSelector,
                                sessionContainer);

            Mock <IAuthorizationTokenProvider> mockAuthorizationTokenProvider = new Mock <IAuthorizationTokenProvider>();

            // setup max replica set size on the config reader
            ReplicationPolicy replicationPolicy = new ReplicationPolicy();

            replicationPolicy.MaxReplicaSetSize = 4;
            Mock <IServiceConfigurationReader> mockServiceConfigReader = new Mock <IServiceConfigurationReader>();

            mockServiceConfigReader.SetupGet(x => x.UserReplicationPolicy).Returns(replicationPolicy);

            try
            {
                StoreClient storeClient =
                    new StoreClient(
                        mockAddressCache.Object,
                        sessionContainer,
                        mockServiceConfigReader.Object,
                        mockAuthorizationTokenProvider.Object,
                        Protocol.Tcp,
                        mockTransportClient);

                ServerStoreModel storeModel = new ServerStoreModel(storeClient);

                var result = storeModel.ProcessMessageAsync(entity).Result;

                // if we have reached this point, there was a successful request.
                // validate if the target identity has been cleared out.
                // If the target identity is null and the request still succeeded, it means
                // that the very first read succeeded without a barrier request.
                Assert.IsNull(entity.RequestContext.TargetIdentity);
                Assert.IsNull(entity.RequestContext.ResolvedPartitionKeyRange);
            }
            catch (Exception e)
            {
                Assert.IsTrue(e.InnerException is ServiceUnavailableException ||
                              e.InnerException is ArgumentNullException ||
                              e.InnerException is ServiceUnavailableException ||
                              e.InnerException is NullReferenceException);
            }
        }