Ejemplo n.º 1
0
        private async Task ValidateRetryOnDatabaseAccountNotFoundAsync(bool enableMultipleWriteLocations, bool isReadRequest)
        {
            this.Initialize(
                useMultipleWriteLocations: enableMultipleWriteLocations,
                enableEndpointDiscovery: true,
                isPreferredLocationsListEmpty: false);

            await this.endpointManager.RefreshLocationAsync(this.databaseAccount);

            ClientRetryPolicy retryPolicy = new ClientRetryPolicy(this.endpointManager, true, new RetryOptions());

            int expectedRetryCount = isReadRequest || enableMultipleWriteLocations ? 2 : 1;

            using (DocumentServiceRequest request = this.CreateRequest(isReadRequest: isReadRequest, isMasterResourceType: false))
            {
                int retryCount = 0;

                try
                {
                    await BackoffRetryUtility <bool> .ExecuteAsync(
                        () =>
                    {
                        retryCount++;
                        retryPolicy.OnBeforeSendRequest(request);

                        if (retryCount == 1)
                        {
                            Uri expectedEndpoint = LocationCacheTests.EndpointByLocation[this.preferredLocations[0]];

                            Assert.AreEqual(expectedEndpoint, request.RequestContext.LocationEndpointToRoute);

                            StringKeyValueCollection headers = new StringKeyValueCollection();
                            headers[WFConstants.BackendHeaders.SubStatus] = ((int)SubStatusCodes.DatabaseAccountNotFound).ToString();
                            DocumentClientException forbiddenException    = new ForbiddenException(RMResources.NotFound, headers);

                            throw forbiddenException;
                        }
                        else if (retryCount == 2)
                        {
                            // Next request must go to next preferred endpoint
                            Uri expectedEndpoint = LocationCacheTests.EndpointByLocation[this.preferredLocations[1]];
                            Assert.AreEqual(expectedEndpoint, request.RequestContext.LocationEndpointToRoute);

                            return(Task.FromResult(true));
                        }
                        else
                        {
                            Assert.Fail();
                        }

                        return(Task.FromResult(true));
                    },
                        retryPolicy);
                }
                catch (ForbiddenException)
                {
                    if (expectedRetryCount == 1)
                    {
                        DefaultTrace.TraceInformation("Received expected ForbiddenException");
                    }
                    else
                    {
                        Assert.Fail();
                    }
                }

                Assert.AreEqual(expectedRetryCount, retryCount);
            }
        }
Ejemplo n.º 2
0
        private async Task <Tuple <DocumentFeedResponse <CosmosElement>, string> > ExecuteOnceAsync(
            IDocumentClientRetryPolicy retryPolicyInstance,
            CancellationToken cancellationToken)
        {
            // Don't reuse request, as the rest of client SDK doesn't reuse requests between retries.
            // The code leaves some temporary garbage in request (in RequestContext etc.),
            // which shold be erased during retries.
            using (DocumentServiceRequest request = await this.CreateRequestAsync())
            {
                DocumentFeedResponse <CosmosElement> feedRespose;
                string partitionIdentifier;
                // We need to determine how to execute the request:
                if (LogicalPartitionKeyProvided(request))
                {
                    feedRespose = await this.ExecuteRequestAsync(request, retryPolicyInstance, cancellationToken);

                    partitionIdentifier = $"PKId({request.Headers[HttpConstants.HttpHeaders.PartitionKey]})";
                }
                else if (PhysicalPartitionKeyRangeIdProvided(this))
                {
                    CollectionCache collectionCache = await this.Client.GetCollectionCacheAsync();

                    ContainerProperties collection = await collectionCache.ResolveCollectionAsync(request, CancellationToken.None, NoOpTrace.Singleton);

                    request.RouteTo(new PartitionKeyRangeIdentity(collection.ResourceId, base.PartitionKeyRangeId));
                    feedRespose = await this.ExecuteRequestAsync(request, retryPolicyInstance, cancellationToken);

                    partitionIdentifier = base.PartitionKeyRangeId;
                }
                else
                {
                    // The query is going to become a full fan out, but we go one partition at a time.
                    if (ServiceInteropAvailable())
                    {
                        // Get the routing map provider
                        CollectionCache collectionCache = await this.Client.GetCollectionCacheAsync();

                        ContainerProperties collection = await collectionCache.ResolveCollectionAsync(request, CancellationToken.None, NoOpTrace.Singleton);

                        QueryPartitionProvider queryPartitionProvider = await this.Client.GetQueryPartitionProviderAsync();

                        IRoutingMapProvider routingMapProvider = await this.Client.GetRoutingMapProviderAsync();

                        // Figure out what partition you are going to based on the range from the continuation token
                        // If token is null then just start at partitionKeyRangeId "0"
                        List <CompositeContinuationToken> suppliedTokens;
                        Range <string> rangeFromContinuationToken =
                            this.partitionRoutingHelper.ExtractPartitionKeyRangeFromContinuationToken(
                                request.Headers,
                                out suppliedTokens);
                        Tuple <PartitionRoutingHelper.ResolvedRangeInfo, IReadOnlyList <Range <string> > > queryRoutingInfo =
                            await this.TryGetTargetPartitionKeyRangeAsync(
                                request,
                                collection,
                                queryPartitionProvider,
                                routingMapProvider,
                                rangeFromContinuationToken,
                                suppliedTokens);

                        if (request.IsNameBased && queryRoutingInfo == null)
                        {
                            request.ForceNameCacheRefresh = true;
                            collection = await collectionCache.ResolveCollectionAsync(request, CancellationToken.None, NoOpTrace.Singleton);

                            queryRoutingInfo = await this.TryGetTargetPartitionKeyRangeAsync(
                                request,
                                collection,
                                queryPartitionProvider,
                                routingMapProvider,
                                rangeFromContinuationToken,
                                suppliedTokens);
                        }

                        if (queryRoutingInfo == null)
                        {
                            throw new NotFoundException($"{DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)}: Was not able to get queryRoutingInfo even after resolve collection async with force name cache refresh to the following collectionRid: {collection.ResourceId} with the supplied tokens: {JsonConvert.SerializeObject(suppliedTokens)}");
                        }

                        request.RouteTo(new PartitionKeyRangeIdentity(collection.ResourceId, queryRoutingInfo.Item1.ResolvedRange.Id));
                        DocumentFeedResponse <CosmosElement> response = await this.ExecuteRequestAsync(request, retryPolicyInstance, cancellationToken);

                        // Form a composite continuation token (range + backend continuation token).
                        // If the backend continuation token was null for the range,
                        // then use the next adjacent range.
                        // This is how the default execution context serially visits every partition.
                        if (!await this.partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync(
                                response.Headers,
                                providedPartitionKeyRanges: queryRoutingInfo.Item2,
                                routingMapProvider: routingMapProvider,
                                collectionRid: collection.ResourceId,
                                resolvedRangeInfo: queryRoutingInfo.Item1,
                                trace: NoOpTrace.Singleton))
                        {
                            // Collection to which this request was resolved doesn't exist.
                            // Retry policy will refresh the cache and return NotFound.
                            throw new NotFoundException($"{DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)}: Call to TryAddPartitionKeyRangeToContinuationTokenAsync failed to the following collectionRid: {collection.ResourceId} with the supplied tokens: {JsonConvert.SerializeObject(suppliedTokens)}");
                        }

                        feedRespose         = response;
                        partitionIdentifier = queryRoutingInfo.Item1.ResolvedRange.Id;
                    }
                    else
                    {
                        // For non-Windows platforms(like Linux and OSX) in .NET Core SDK, we cannot use ServiceInterop for parsing the query,
                        // so forcing the request through Gateway. We are also now by-passing this for 32-bit host process in NETFX on Windows
                        // as the ServiceInterop dll is only available in 64-bit.

                        request.UseGatewayMode = true;
                        feedRespose            = await this.ExecuteRequestAsync(request, retryPolicyInstance, cancellationToken);

                        partitionIdentifier = "Gateway";
                    }
                }

                return(new Tuple <DocumentFeedResponse <CosmosElement>, string>(feedRespose, partitionIdentifier));
            }
        }
Ejemplo n.º 3
0
        private async Task <Tuple <PartitionRoutingHelper.ResolvedRangeInfo, IReadOnlyList <Range <string> > > > TryGetTargetPartitionKeyRangeAsync(
            DocumentServiceRequest request,
            ContainerProperties collection,
            QueryPartitionProvider queryPartitionProvider,
            IRoutingMapProvider routingMapProvider,
            Range <string> rangeFromContinuationToken,
            List <CompositeContinuationToken> suppliedTokens)
        {
            string version = request.Headers[HttpConstants.HttpHeaders.Version];

            version = string.IsNullOrEmpty(version) ? HttpConstants.Versions.CurrentVersion : version;

            bool enableCrossPartitionQuery = false;

            string enableCrossPartitionQueryHeader = request.Headers[HttpConstants.HttpHeaders.EnableCrossPartitionQuery];

            if (enableCrossPartitionQueryHeader != null)
            {
                if (!bool.TryParse(enableCrossPartitionQueryHeader, out enableCrossPartitionQuery))
                {
                    throw new BadRequestException(
                              string.Format(
                                  CultureInfo.InvariantCulture,
                                  RMResources.InvalidHeaderValue,
                                  enableCrossPartitionQueryHeader,
                                  HttpConstants.HttpHeaders.EnableCrossPartitionQuery));
                }
            }

            IReadOnlyList <Range <string> > providedRanges;

            if (!this.providedRangesCache.TryGetValue(collection.ResourceId, out providedRanges))
            {
                if (this.ShouldExecuteQueryRequest)
                {
                    FeedOptions            feedOptions = this.GetFeedOptions(null);
                    PartitionKeyDefinition partitionKeyDefinition;
                    if ((feedOptions.Properties != null) && feedOptions.Properties.TryGetValue(
                            DefaultDocumentQueryExecutionContext.InternalPartitionKeyDefinitionProperty,
                            out object partitionKeyDefinitionObject))
                    {
                        if (partitionKeyDefinitionObject is PartitionKeyDefinition definition)
                        {
                            partitionKeyDefinition = definition;
                        }
                        else
                        {
                            throw new ArgumentException(
                                      "partitionkeydefinition has invalid type",
                                      nameof(partitionKeyDefinitionObject));
                        }
                    }
                    else
                    {
                        partitionKeyDefinition = collection.PartitionKey;
                    }

                    providedRanges = PartitionRoutingHelper.GetProvidedPartitionKeyRanges(
                        querySpecJsonString: JsonConvert.SerializeObject(this.QuerySpec),
                        enableCrossPartitionQuery: enableCrossPartitionQuery,
                        parallelizeCrossPartitionQuery: false,
                        isContinuationExpected: this.isContinuationExpected,
                        hasLogicalPartitionKey: false,
                        allowDCount: false,
                        allowNonValueAggregates: false,
                        useSystemPrefix: false,
                        partitionKeyDefinition: partitionKeyDefinition,
                        queryPartitionProvider: queryPartitionProvider,
                        clientApiVersion: version,
                        out QueryInfo _);
                }
                else if (request.Properties != null && request.Properties.TryGetValue(
                             WFConstants.BackendHeaders.EffectivePartitionKeyString,
                             out object effectivePartitionKey))
                {
                    if (effectivePartitionKey is string effectivePartitionKeyString)
                    {
                        providedRanges = new List <Range <string> >()
                        {
                            Range <string> .GetPointRange(effectivePartitionKeyString),
                        };
                    }
                    else
                    {
                        throw new ArgumentException(
                                  "EffectivePartitionKey must be a string",
                                  WFConstants.BackendHeaders.EffectivePartitionKeyString);
                    }
                }
                else
                {
                    providedRanges = new List <Range <string> >
                    {
                        new Range <string>(
                            PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey,
                            PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey,
                            true,
                            false)
                    };
                }

                this.providedRangesCache[collection.ResourceId] = providedRanges;
            }

            PartitionRoutingHelper.ResolvedRangeInfo resolvedRangeInfo = await this.partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync(
                providedRanges,
                routingMapProvider,
                collection.ResourceId,
                rangeFromContinuationToken,
                suppliedTokens,
                NoOpTrace.Singleton);

            if (resolvedRangeInfo.ResolvedRange == null)
            {
                return(null);
            }
            else
            {
                return(Tuple.Create(resolvedRangeInfo, providedRanges));
            }
        }
        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, collection, 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));
        }
        private PartitionKeyRange TryResolveServerPartitionByPartitionKey(
            DocumentServiceRequest request,
            string partitionKeyString,
            bool collectionCacheUptoDate,
            ContainerProperties collection,
            CollectionRoutingMap routingMap)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

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

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

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

            PartitionKeyInternal partitionKey;

            try
            {
                partitionKey = PartitionKeyInternal.FromJsonString(partitionKeyString);
            }
            catch (JsonException ex)
            {
                throw new BadRequestException(
                          string.Format(CultureInfo.InvariantCulture, RMResources.InvalidPartitionKey, partitionKeyString),
                          ex)
                      {
                          ResourceAddress = request.ResourceAddress
                      };
            }

            if (partitionKey == null)
            {
                throw new InternalServerErrorException(string.Format(CultureInfo.InvariantCulture, "partition key is null '{0}'", partitionKeyString));
            }

            if (partitionKey.Equals(PartitionKeyInternal.Empty) || partitionKey.Components.Count == collection.PartitionKey.Paths.Count)
            {
                // Although we can compute effective partition key here, in general case this Gateway can have outdated
                // partition key definition cached - like if collection with same name but with Range partitioning is created.
                // In this case server will not pass x-ms-documentdb-collection-rid check and will return back InvalidPartitionException.
                // Gateway will refresh its cache and retry.

                string effectivePartitionKey = partitionKey.GetEffectivePartitionKeyString(collection.PartitionKey);

                // There should be exactly one range which contains a partition key. Always.
                return(routingMap.GetRangeByEffectivePartitionKey(effectivePartitionKey));
            }

            if (collectionCacheUptoDate)
            {
                BadRequestException badRequestException = new BadRequestException(RMResources.PartitionKeyMismatch)
                {
                    ResourceAddress = request.ResourceAddress
                };
                badRequestException.Headers[WFConstants.BackendHeaders.SubStatus] =
                    ((uint)SubStatusCodes.PartitionKeyMismatch).ToString(CultureInfo.InvariantCulture);

                throw badRequestException;
            }

            // Partition key supplied has different number paths than locally cached partition key definition.
            // Three things can happen:
            //    1. User supplied wrong partition key.
            //    2. Client SDK has outdated partition key definition cache and extracted wrong value from the document.
            //    3. Gateway's cache is outdated.
            //
            // What we will do is append x-ms-documentdb-collection-rid header and forward it to random collection partition.
            // * If collection rid matches, server will send back 400.1001, because it also will not be able to compute
            // effective partition key. Gateway will forward this status code to client - client will handle it.
            // * If collection rid doesn't match, server will send back InvalidPartiitonException and Gateway will
            //   refresh name routing cache - this will refresh partition key definition as well, and retry.

            DefaultTrace.TraceInformation(
                "Cannot compute effective partition key. Definition has '{0}' paths, values supplied has '{1}' paths. Will refresh cache and retry.",
                collection.PartitionKey.Paths.Count,
                partitionKey.Components.Count);

            return(null);
        }
Ejemplo n.º 6
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);
            Uri             primaryAddress  = addressSelector.ResolvePrimaryTransportAddressUriAsync(entity, false /*forceAddressRefresh*/).Result.Uri;

            // 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
            TransportClient mockTransportClient = this.GetMockTransportClientDuringUpgrade(addressInformation);

            // get response from mock object
            StoreResponse response = mockTransportClient.InvokeResourceOperationAsync(new TransportAddressUri(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);

            // 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,
                                new AddressEnumerator(),
                                sessionContainer);

            // 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);
        }
Ejemplo n.º 7
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);
            Uri             primaryAddress  = addressSelector.ResolvePrimaryTransportAddressUriAsync(entity, false /*forceAddressRefresh*/).Result.Uri;

            // check if the address return from Address Selector matches the original address info
            Assert.IsTrue(primaryAddress.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);

                // 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,
                                    new AddressEnumerator(),
                                    sessionContainer);

                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);

                // 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,
                                    new AddressEnumerator(),
                                    sessionContainer);

                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);

                // 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,
                                    new AddressEnumerator(),
                                    sessionContainer);

                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);
            }
        }
        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>();
            mockAuthorizationTokenProvider.Setup(provider => provider.GetSystemAuthorizationTokenAsync(
                It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<INameValueCollection>(), It.IsAny<AuthorizationTokenType>()))
                .ReturnsAsync("dummyauthtoken");

            // 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);
            }
        }
Ejemplo n.º 9
0
        private async Task TestGatewayStoreModelProcessMessageAsync(GatewayStoreModel storeModel, DocumentServiceRequest request)
        {
            request.Headers["x-ms-session-token"] = "0:1#100#1=20#2=5#3=30";
            await storeModel.ProcessMessageAsync(request);

            request.Headers.Remove("x-ms-session-token");
            request.Headers["x-ms-consistency-level"] = "Session";
            await storeModel.ProcessMessageAsync(request);
        }
Ejemplo n.º 10
0
        private async Task ValidateRetryOnWriteSessionNotAvailabeWithEnableMultipleWriteLocationsAsync()
        {
            const bool useMultipleWriteLocations = true;
            bool       enableEndpointDiscovery   = true;

            this.Initialize(
                useMultipleWriteLocations: useMultipleWriteLocations,
                enableEndpointDiscovery: enableEndpointDiscovery,
                isPreferredLocationsListEmpty: false);

            await this.endpointManager.RefreshLocationAsync(this.databaseAccount);

            ClientRetryPolicy retryPolicy = new ClientRetryPolicy(this.endpointManager, enableEndpointDiscovery, new RetryOptions());

            using (DocumentServiceRequest request = this.CreateRequest(isReadRequest: false, isMasterResourceType: false))
            {
                int retryCount = 0;

                try
                {
                    await BackoffRetryUtility <bool> .ExecuteAsync(
                        () =>
                    {
                        retryPolicy.OnBeforeSendRequest(request);

                        if (retryCount == 0)
                        {
                            Assert.IsFalse(request.ClearSessionTokenOnSessionReadFailure);

                            Uri expectedEndpoint = LocationCacheTests.EndpointByLocation[this.preferredLocations[0]];

                            Assert.AreEqual(expectedEndpoint, request.RequestContext.LocationEndpointToRoute);
                        }
                        else if (retryCount == 1)
                        {
                            Assert.IsFalse(request.ClearSessionTokenOnSessionReadFailure);

                            // Second request must go to first write endpoint
                            Uri expectedEndpoint = new Uri(this.databaseAccount.WriteLocationsInternal[0].DatabaseAccountEndpoint);

                            Assert.AreEqual(expectedEndpoint, request.RequestContext.LocationEndpointToRoute);
                        }
                        else if (retryCount == 2)
                        {
                            Assert.IsFalse(request.ClearSessionTokenOnSessionReadFailure);

                            // Second request must go to first write endpoint
                            Uri expectedEndpoint = LocationCacheTests.EndpointByLocation[this.preferredLocations[1]];
                            Assert.AreEqual(expectedEndpoint, request.RequestContext.LocationEndpointToRoute);
                        }
                        else if (retryCount == 3)
                        {
                            Assert.IsTrue(request.ClearSessionTokenOnSessionReadFailure);

                            // Second request must go to first write endpoint
                            Uri expectedEndpoint = LocationCacheTests.EndpointByLocation[this.preferredLocations[2]];
                            Assert.AreEqual(expectedEndpoint, request.RequestContext.LocationEndpointToRoute);
                        }
                        else
                        {
                            Assert.Fail();
                        }

                        retryCount++;

                        StringKeyValueCollection headers = new StringKeyValueCollection();
                        headers[WFConstants.BackendHeaders.SubStatus] = ((int)SubStatusCodes.ReadSessionNotAvailable).ToString();
                        DocumentClientException notFoundException     = new NotFoundException(RMResources.NotFound, headers);


                        throw notFoundException;
                    },
                        retryPolicy);

                    Assert.Fail();
                }
                catch (NotFoundException)
                {
                    DefaultTrace.TraceInformation("Received expected notFoundException");
                    Assert.AreEqual(4, retryCount);
                }
            }
        }
Ejemplo n.º 11
0
        public override async Task <CosmosResponseMessage> SendAsync(
            CosmosRequestMessage request,
            CancellationToken cancellationToken)
        {
            CosmosResponseMessage response = null;
            string originalContinuation    = request.Headers.Continuation;

            try
            {
                RntdbEnumerationDirection rntdbEnumerationDirection = RntdbEnumerationDirection.Forward;
                object direction;
                if (request.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out direction))
                {
                    rntdbEnumerationDirection = (byte)RntdbEnumerationDirection.Reverse == (byte)direction ? RntdbEnumerationDirection.Reverse : RntdbEnumerationDirection.Forward;
                }

                request.Headers.Remove(HttpConstants.HttpHeaders.IsContinuationExpected);
                request.Headers.Add(HttpConstants.HttpHeaders.IsContinuationExpected, bool.TrueString);

                object startEpk;
                object endEpk;
                if (!request.Properties.TryGetValue(HandlerConstants.StartEpkString, out startEpk))
                {
                    startEpk = PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey;
                }
                if (!request.Properties.TryGetValue(HandlerConstants.EndEpkString, out endEpk))
                {
                    endEpk = PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey;
                }
                startEpk = startEpk ?? PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey;
                endEpk   = endEpk ?? PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey;

                List <Range <string> > providedRanges = new List <Range <string> > {
                    new Range <string>(
                        (string)startEpk,
                        (string)endEpk,
                        isMinInclusive: true,
                        isMaxInclusive: false)
                };

                DocumentServiceRequest serviceRequest = request.ToDocumentServiceRequest();

                PartitionKeyRangeCache routingMapProvider = await this.client.DocumentClient.GetPartitionKeyRangeCacheAsync();

                CollectionCache collectionCache = await this.client.DocumentClient.GetCollectionCacheAsync();

                CosmosContainerSettings collectionFromCache =
                    await collectionCache.ResolveCollectionAsync(serviceRequest, CancellationToken.None);

                List <CompositeContinuationToken> suppliedTokens;
                //direction is not expected to change  between continuations.
                Range <string> rangeFromContinuationToken =
                    this.partitionRoutingHelper.ExtractPartitionKeyRangeFromContinuationToken(serviceRequest.Headers, out suppliedTokens);

                ResolvedRangeInfo resolvedRangeInfo =
                    await this.partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRange(
                        providedPartitionKeyRanges : providedRanges,
                        routingMapProvider : routingMapProvider,
                        collectionRid : collectionFromCache.ResourceId,
                        rangeFromContinuationToken : rangeFromContinuationToken,
                        suppliedTokens : suppliedTokens,
                        direction : rntdbEnumerationDirection);

                if (serviceRequest.IsNameBased && resolvedRangeInfo.ResolvedRange == null && resolvedRangeInfo.ContinuationTokens == null)
                {
                    serviceRequest.ForceNameCacheRefresh = true;
                    collectionFromCache = await collectionCache.ResolveCollectionAsync(serviceRequest, CancellationToken.None);

                    resolvedRangeInfo = await this.partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRange(
                        providedPartitionKeyRanges : providedRanges,
                        routingMapProvider : routingMapProvider,
                        collectionRid : collectionFromCache.ResourceId,
                        rangeFromContinuationToken : rangeFromContinuationToken,
                        suppliedTokens : suppliedTokens,
                        direction : rntdbEnumerationDirection);
                }

                if (resolvedRangeInfo.ResolvedRange == null && resolvedRangeInfo.ContinuationTokens == null)
                {
                    return(((DocumentClientException)
                            new NotFoundException(
                                $"{DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)}: Was not able to get queryRoutingInfo even after resolve collection async with force name cache refresh to the following collectionRid: {collectionFromCache.ResourceId} with the supplied tokens: {JsonConvert.SerializeObject(suppliedTokens)}")
                            ).ToCosmosResponseMessage(request));
                }

                serviceRequest.RouteTo(new PartitionKeyRangeIdentity(collectionFromCache.ResourceId, resolvedRangeInfo.ResolvedRange.Id));

                response = await base.SendAsync(request, cancellationToken);

                if (!response.IsSuccessStatusCode)
                {
                    this.SetOriginalContinuationToken(request, response, originalContinuation);
                }
                else
                {
                    if (!await this.partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync(
                            response.Headers.CosmosMessageHeaders,
                            providedPartitionKeyRanges: providedRanges,
                            routingMapProvider: routingMapProvider,
                            collectionRid: collectionFromCache.ResourceId,
                            resolvedRangeInfo: resolvedRangeInfo,
                            direction: rntdbEnumerationDirection))
                    {
                        return(((DocumentClientException)
                                new NotFoundException(
                                    $"{DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)}: Call to TryAddPartitionKeyRangeToContinuationTokenAsync failed to the following collectionRid: {collectionFromCache.ResourceId} with the supplied tokens: {JsonConvert.SerializeObject(suppliedTokens)}")
                                ).ToCosmosResponseMessage(request));
                    }
                }

                return(response);
            }
            catch (DocumentClientException ex)
            {
                CosmosResponseMessage errorResponse = ex.ToCosmosResponseMessage(request);
                this.SetOriginalContinuationToken(request, errorResponse, originalContinuation);
                return(errorResponse);
            }
            catch (AggregateException ex)
            {
                this.SetOriginalContinuationToken(request, response, originalContinuation);

                // TODO: because the SDK underneath this path uses ContinueWith or task.Result we need to catch AggregateExceptions here
                // in order to ensure that underlying DocumentClientExceptions get propagated up correctly. Once all ContinueWith and .Result
                // is removed this catch can be safely removed.
                AggregateException innerExceptions    = ex.Flatten();
                Exception          docClientException = innerExceptions.InnerExceptions.FirstOrDefault(innerEx => innerEx is DocumentClientException);
                if (docClientException != null)
                {
                    return(((DocumentClientException)docClientException).ToCosmosResponseMessage(request));
                }

                throw;
            }
        }
Ejemplo n.º 12
0
        internal static async Task <DocumentServiceResponse> ParseResponseAsync(HttpResponseMessage responseMessage, JsonSerializerSettings serializerSettings = null, DocumentServiceRequest request = null)
        {
            using (responseMessage)
            {
                IClientSideRequestStatistics requestStatistics = request?.RequestContext?.ClientRequestStatistics;
                if ((int)responseMessage.StatusCode < 400)
                {
                    INameValueCollection headers = GatewayStoreClient.ExtractResponseHeaders(responseMessage);
                    Stream contentStream         = await GatewayStoreClient.BufferContentIfAvailableAsync(responseMessage);

                    return(new DocumentServiceResponse(
                               body: contentStream,
                               headers: headers,
                               statusCode: responseMessage.StatusCode,
                               clientSideRequestStatistics: requestStatistics,
                               serializerSettings: serializerSettings));
                }
                else if (request != null &&
                         request.IsValidStatusCodeForExceptionlessRetry((int)responseMessage.StatusCode))
                {
                    INameValueCollection headers = GatewayStoreClient.ExtractResponseHeaders(responseMessage);
                    Stream contentStream         = await GatewayStoreClient.BufferContentIfAvailableAsync(responseMessage);

                    return(new DocumentServiceResponse(
                               body: contentStream,
                               headers: headers,
                               statusCode: responseMessage.StatusCode,
                               clientSideRequestStatistics: requestStatistics,
                               serializerSettings: serializerSettings));
                }
                else
                {
                    throw await GatewayStoreClient.CreateDocumentClientExceptionAsync(responseMessage, requestStatistics);
                }
            }
        }
Ejemplo n.º 13
0
        internal override async Task <StoreResponse> InvokeStoreAsync(Uri baseAddress, ResourceOperation resourceOperation, DocumentServiceRequest request)
        {
            Uri physicalAddress = GatewayStoreClient.IsFeedRequest(request.OperationType) ?
                                  HttpTransportClient.GetResourceFeedUri(resourceOperation.resourceType, baseAddress, request) :
                                  HttpTransportClient.GetResourceEntryUri(resourceOperation.resourceType, baseAddress, request);

            using (HttpResponseMessage responseMessage = await this.InvokeClientAsync(request, resourceOperation.resourceType, physicalAddress, default))
            {
                return(await HttpTransportClient.ProcessHttpResponse(request.ResourceAddress, string.Empty, responseMessage, physicalAddress, request));
            }
        }
Ejemplo n.º 14
0
        private async ValueTask <HttpRequestMessage> PrepareRequestMessageAsync(
            DocumentServiceRequest request,
            Uri physicalAddress)
        {
            HttpMethod httpMethod = HttpMethod.Head;

            if (request.OperationType == OperationType.Create ||
                request.OperationType == OperationType.Upsert ||
                request.OperationType == OperationType.Query ||
                request.OperationType == OperationType.SqlQuery ||
                request.OperationType == OperationType.Batch ||
                request.OperationType == OperationType.ExecuteJavaScript ||
                request.OperationType == OperationType.QueryPlan ||
                (request.ResourceType == ResourceType.PartitionKey && request.OperationType == OperationType.Delete))
            {
                httpMethod = HttpMethod.Post;
            }
            else if (request.OperationType == OperationType.Read ||
                     request.OperationType == OperationType.ReadFeed)
            {
                httpMethod = HttpMethod.Get;
            }
            else if ((request.OperationType == OperationType.Replace) ||
                     (request.OperationType == OperationType.CollectionTruncate))
            {
                httpMethod = HttpMethod.Put;
            }
            else if (request.OperationType == OperationType.Delete)
            {
                httpMethod = HttpMethod.Delete;
            }
            else if (request.OperationType == OperationType.Patch)
            {
                // There isn't support for PATCH method in .NetStandard 2.0
                httpMethod = httpPatchMethod;
            }
            else
            {
                throw new NotImplementedException();
            }

            HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, physicalAddress);

            // The StreamContent created below will own and dispose its underlying stream, but we may need to reuse the stream on the
            // DocumentServiceRequest for future requests. Hence we need to clone without incurring copy cost, so that when
            // HttpRequestMessage -> StreamContent -> MemoryStream all get disposed, the original stream will be left open.
            if (request.Body != null)
            {
                await request.EnsureBufferedBodyAsync();

                MemoryStream clonedStream = new MemoryStream();
                // WriteTo doesn't use and update Position of source stream. No point in setting/restoring it.
                request.CloneableBody.WriteTo(clonedStream);
                clonedStream.Position = 0;

                requestMessage.Content = new StreamContent(clonedStream);
            }

            if (request.Headers != null)
            {
                foreach (string key in request.Headers)
                {
                    if (GatewayStoreClient.IsAllowedRequestHeader(key))
                    {
                        if (key.Equals(HttpConstants.HttpHeaders.ContentType, StringComparison.OrdinalIgnoreCase))
                        {
                            requestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue(request.Headers[key]);
                        }
                        else
                        {
                            requestMessage.Headers.TryAddWithoutValidation(key, request.Headers[key]);
                        }
                    }
                }
            }

            // add activityId
            Guid activityId = System.Diagnostics.Trace.CorrelationManager.ActivityId;

            Debug.Assert(activityId != Guid.Empty);
            requestMessage.Headers.Add(HttpConstants.HttpHeaders.ActivityId, activityId.ToString());

            return(requestMessage);
        }
Ejemplo n.º 15
0
        private async Task <Tuple <PartitionKeyRangeIdentity, PartitionAddressInformation> > ResolveMasterAsync(DocumentServiceRequest request, bool forceRefresh)
        {
            Tuple <PartitionKeyRangeIdentity, PartitionAddressInformation> masterAddressAndRange = this.masterPartitionAddressCache;

            int targetReplicaSetSize = this.serviceConfigReader.SystemReplicationPolicy.MaxReplicaSetSize;

            forceRefresh = forceRefresh ||
                           (masterAddressAndRange != null &&
                            masterAddressAndRange.Item2.AllAddresses.Count() < targetReplicaSetSize &&
                            DateTime.UtcNow.Subtract(this.suboptimalMasterPartitionTimestamp) > TimeSpan.FromSeconds(this.suboptimalPartitionForceRefreshIntervalInSeconds));

            if (forceRefresh || this.masterPartitionAddressCache == null)
            {
                string entryUrl = PathsHelper.GeneratePath(
                    ResourceType.Database,
                    string.Empty,
                    true);

                try
                {
                    FeedResource <Address> masterAddresses = await this.GetMasterAddressesViaGatewayAsync(
                        request,
                        ResourceType.Database,
                        null,
                        entryUrl,
                        forceRefresh,
                        false);

                    masterAddressAndRange                   = this.ToPartitionAddressAndRange(string.Empty, masterAddresses.ToList());
                    this.masterPartitionAddressCache        = masterAddressAndRange;
                    this.suboptimalMasterPartitionTimestamp = DateTime.MaxValue;
                }
                catch (Exception)
                {
                    this.suboptimalMasterPartitionTimestamp = DateTime.MaxValue;
                    throw;
                }
            }

            if (masterAddressAndRange.Item2.AllAddresses.Count() < targetReplicaSetSize && this.suboptimalMasterPartitionTimestamp.Equals(DateTime.MaxValue))
            {
                this.suboptimalMasterPartitionTimestamp = DateTime.UtcNow;
            }

            return(masterAddressAndRange);
        }
Ejemplo n.º 16
0
        private DocumentFeedResponse <CosmosElement> GetFeedResponse(
            DocumentServiceRequest documentServiceRequest,
            DocumentServiceResponse documentServiceResponse)
        {
            // Execute the callback an each element of the page
            // For example just could get a response like this
            // {
            //    "_rid": "qHVdAImeKAQ=",
            //    "Documents": [{
            //        "id": "03230",
            //        "_rid": "qHVdAImeKAQBAAAAAAAAAA==",
            //        "_self": "dbs\/qHVdAA==\/colls\/qHVdAImeKAQ=\/docs\/qHVdAImeKAQBAAAAAAAAAA==\/",
            //        "_etag": "\"410000b0-0000-0000-0000-597916b00000\"",
            //        "_attachments": "attachments\/",
            //        "_ts": 1501107886
            //    }],
            //    "_count": 1
            // }
            // And you should execute the callback on each document in "Documents".
            MemoryStream memoryStream = new MemoryStream();

            documentServiceResponse.ResponseBody.CopyTo(memoryStream);
            long responseLengthBytes = memoryStream.Length;

            byte[]         content       = memoryStream.ToArray();
            IJsonNavigator jsonNavigator = null;

            // Use the users custom navigator first. If it returns null back try the
            // internal navigator.
            if (this.feedOptions.CosmosSerializationOptions != null)
            {
                jsonNavigator = this.feedOptions.CosmosSerializationOptions.CreateCustomNavigatorCallback(content);
                if (jsonNavigator == null)
                {
                    throw new InvalidOperationException("The CosmosSerializationOptions did not return a JSON navigator.");
                }
            }
            else
            {
                jsonNavigator = JsonNavigator.Create(content);
            }

            string resourceName = this.GetRootNodeName(documentServiceRequest.ResourceType);

            if (!jsonNavigator.TryGetObjectProperty(
                    jsonNavigator.GetRootNode(),
                    resourceName,
                    out ObjectProperty objectProperty))
            {
                throw new InvalidOperationException($"Response Body Contract was violated. QueryResponse did not have property: {resourceName}");
            }

            IJsonNavigatorNode cosmosElements = objectProperty.ValueNode;

            if (!(CosmosElement.Dispatch(
                      jsonNavigator,
                      cosmosElements) is CosmosArray cosmosArray))
            {
                throw new InvalidOperationException($"QueryResponse did not have an array of : {resourceName}");
            }

            int itemCount = cosmosArray.Count;

            return(new DocumentFeedResponse <CosmosElement>(
                       cosmosArray,
                       itemCount,
                       documentServiceResponse.Headers,
                       documentServiceResponse.RequestStats,
                       responseLengthBytes));
        }
Ejemplo n.º 17
0
        private async Task <FeedResource <Address> > GetServerAddressesViaGatewayAsync(
            DocumentServiceRequest request,
            string collectionRid,
            IEnumerable <string> partitionKeyRangeIds,
            bool forceRefresh)
        {
            string entryUrl = PathsHelper.GeneratePath(ResourceType.Document, collectionRid, true);

            INameValueCollection addressQuery = new StringKeyValueCollection();

            addressQuery.Add(HttpConstants.QueryStrings.Url, HttpUtility.UrlEncode(entryUrl));

            INameValueCollection headers = new StringKeyValueCollection();

            if (forceRefresh)
            {
                headers.Set(HttpConstants.HttpHeaders.ForceRefresh, bool.TrueString);
            }

            if (request.ForceCollectionRoutingMapRefresh)
            {
                headers.Set(HttpConstants.HttpHeaders.ForceCollectionRoutingMapRefresh, bool.TrueString);
            }

            addressQuery.Add(HttpConstants.QueryStrings.Filter, this.protocolFilter);
            addressQuery.Add(HttpConstants.QueryStrings.PartitionKeyRangeIds, string.Join(",", partitionKeyRangeIds));

            string resourceTypeToSign = PathsHelper.GetResourcePath(ResourceType.Document);

            headers.Set(HttpConstants.HttpHeaders.XDate, DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture));
            string token = null;

            try
            {
                token = this.tokenProvider.GetUserAuthorizationToken(
                    collectionRid,
                    resourceTypeToSign,
                    HttpConstants.HttpMethods.Get,
                    headers,
                    AuthorizationTokenType.PrimaryMasterKey);
            }
            catch (UnauthorizedException)
            {
            }

            if (token == null && request.IsNameBased)
            {
                // User doesn't have rid based resource token. Maybe he has name based.
                string collectionAltLink = PathsHelper.GetCollectionPath(request.ResourceAddress);
                token = this.tokenProvider.GetUserAuthorizationToken(
                    collectionAltLink,
                    resourceTypeToSign,
                    HttpConstants.HttpMethods.Get,
                    headers,
                    AuthorizationTokenType.PrimaryMasterKey);
            }

            headers.Set(HttpConstants.HttpHeaders.Authorization, token);

            Uri targetEndpoint = UrlUtility.SetQuery(this.addressEndpoint, UrlUtility.CreateQuery(addressQuery));

            string identifier = GatewayAddressCache.LogAddressResolutionStart(request, targetEndpoint);

            using (HttpResponseMessage httpResponseMessage = await this.httpClient.GetAsync(targetEndpoint, headers))
            {
                using (DocumentServiceResponse documentServiceResponse =
                           await ClientExtensions.ParseResponseAsync(httpResponseMessage))
                {
                    GatewayAddressCache.LogAddressResolutionEnd(request, identifier);

                    return(documentServiceResponse.GetResource <FeedResource <Address> >());
                }
            }
        }
Ejemplo n.º 18
0
        /// <summary>
        /// Buffers more documents in the producer.
        /// </summary>
        /// <param name="token">The cancellation token.</param>
        /// <returns>A task to await on.</returns>
        public async Task BufferMoreDocuments(CancellationToken token)
        {
            token.ThrowIfCancellationRequested();

            try
            {
                await this.fetchSemaphore.WaitAsync();

                if (!this.HasMoreBackendResults)
                {
                    // Just NOP
                    return;
                }

                this.fetchSchedulingMetrics.Start();
                this.fetchExecutionRangeAccumulator.BeginFetchRange();
                int pageSize = (int)Math.Min(this.pageSize, (long)int.MaxValue);
                using (DocumentServiceRequest request = this.createRequestFunc(this.PartitionKeyRange, this.backendContinuationToken, pageSize))
                {
                    // BUG: retryPolicyInstance shound not be shared betweet different requests
                    IDocumentClientRetryPolicy retryPolicy = this.createRetryPolicyFunc();
                    retryPolicy.OnBeforeSendRequest(request);

                    // Custom backoff and retry
                    while (true)
                    {
                        int retries = 0;
                        try
                        {
                            FeedResponse <CosmosElement> feedResponse = await this.executeRequestFunc(request, token);

                            this.fetchExecutionRangeAccumulator.EndFetchRange(
                                feedResponse.ActivityId,
                                feedResponse.Count,
                                retries);
                            this.fetchSchedulingMetrics.Stop();
                            this.hasStartedFetching       = true;
                            this.backendContinuationToken = feedResponse.ResponseContinuation;
                            this.activityId = Guid.Parse(feedResponse.ActivityId);
                            await this.bufferedPages.AddAsync(feedResponse);

                            Interlocked.Add(ref this.bufferedItemCount, feedResponse.Count);

                            QueryMetrics queryMetrics = QueryMetrics.Zero;
                            if (feedResponse.ResponseHeaders[HttpConstants.HttpHeaders.QueryMetrics] != null)
                            {
                                queryMetrics = QueryMetrics.CreateFromDelimitedStringAndClientSideMetrics(
                                    feedResponse.ResponseHeaders[HttpConstants.HttpHeaders.QueryMetrics],
                                    new ClientSideMetrics(
                                        retries,
                                        feedResponse.RequestCharge,
                                        this.fetchExecutionRangeAccumulator.GetExecutionRanges(),
                                        new List <Tuple <string, SchedulingTimeSpan> >()));
                            }

                            if (!this.HasMoreBackendResults)
                            {
                                queryMetrics = QueryMetrics.CreateWithSchedulingMetrics(
                                    queryMetrics,
                                    new List <Tuple <string, SchedulingTimeSpan> >
                                {
                                    new Tuple <string, SchedulingTimeSpan>(
                                        this.PartitionKeyRange.Id,
                                        this.fetchSchedulingMetrics.Elapsed)
                                });
                            }

                            this.produceAsyncCompleteCallback(
                                this,
                                feedResponse.Count,
                                feedResponse.RequestCharge,
                                queryMetrics,
                                feedResponse.ResponseLengthBytes,
                                token);

                            break;
                        }
                        catch (Exception exception)
                        {
                            // See if we need to retry or just throw
                            ShouldRetryResult shouldRetryResult = await retryPolicy.ShouldRetryAsync(exception, token);

                            if (!shouldRetryResult.ShouldRetry)
                            {
                                if (shouldRetryResult.ExceptionToThrow != null)
                                {
                                    throw shouldRetryResult.ExceptionToThrow;
                                }
                                else
                                {
                                    // Propagate original exception.
                                    throw;
                                }
                            }
                            else
                            {
                                await Task.Delay(shouldRetryResult.BackoffTime);

                                retries++;
                            }
                        }
                    }
                }
            }
            finally
            {
                this.fetchSchedulingMetrics.Stop();
                this.fetchSemaphore.Release();
            }
        }
Ejemplo n.º 19
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);
            Uri             primaryAddress  = addressSelector.ResolvePrimaryTransportAddressUriAsync(entity, false /*forceAddressRefresh*/).Result.Uri;

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

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

            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, new AddressEnumerator(), sessionContainer);
                ConsistencyWriter consistencyWriter   = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, 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, 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, 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, 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, false);
                response            = consistencyWriter.WriteAsync(entity, new TimeoutHelper(TimeSpan.FromSeconds(30)), false).Result;
                Assert.AreEqual(100, response.LSN);
            }
        }
 public Task <DocumentServiceResponse> ReadFeedAsync(DocumentServiceRequest request, IDocumentClientRetryPolicy retryPolicyInstance, CancellationToken cancellationToken)
 {
     return(this.innerClient.ReadFeedAsync(request, retryPolicyInstance, cancellationToken));
 }
        /// <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)
            {
                if (request.ServiceIdentity.IsMasterService &&
                    request.ForceMasterRefresh &&
                    this.masterServiceIdentityProvider != null)
                {
                    await this.masterServiceIdentityProvider.RefreshAsync(request.ServiceIdentity, cancellationToken);

                    ServiceIdentity newMasterServiceIdentity = this.masterServiceIdentityProvider.MasterServiceIdentity;

                    bool masterServiceIdentityChanged = newMasterServiceIdentity != null &&
                                                        !newMasterServiceIdentity.Equals(request.ServiceIdentity);

                    DefaultTrace.TraceInformation(
                        "Refreshed master service identity. masterServiceIdentityChanged = {0}, " +
                        "previousRequestServiceIdentity = {1}, newMasterServiceIdentity = {2}",
                        masterServiceIdentityChanged,
                        request.ServiceIdentity,
                        newMasterServiceIdentity);

                    if (masterServiceIdentityChanged)
                    {
                        request.RouteTo(newMasterServiceIdentity);
                    }
                }

                // 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 && identity.IsMasterService && this.masterServiceIdentityProvider != null)
                {
                    DefaultTrace.TraceWarning("Could not get addresses for MasterServiceIdentity {0}. will refresh masterServiceIdentity and retry", identity);
                    await this.masterServiceIdentityProvider.RefreshAsync(identity, cancellationToken);

                    identity  = this.masterServiceIdentityProvider.MasterServiceIdentity;
                    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 void OnBeforeSendRequest(DocumentServiceRequest request)
 {
     this.nextRetryPolicy?.OnBeforeSendRequest(request);
 }
        private PartitionKeyRange TryResolveSinglePartitionCollection(
            DocumentServiceRequest request,
            ContainerProperties collection,
            CollectionRoutingMap routingMap,
            bool collectionCacheIsUptoDate)
        {
            // Neither partitionkey nor partitionkeyrangeid is specified.
            // Three options here:
            //    * This is non-partitioned collection and old client SDK which doesn't send partition key. In
            //      this case there's single entry in routing map. But can be multiple entries if before that
            //      existed partitioned collection with same name.
            //    * This is partitioned collection and old client SDK which doesn't send partition key.
            //      In this case there can be multiple ranges in routing map.
            //    * This is partitioned collection and this is custom written REST sdk, which has a bug and doesn't send
            //      partition key.
            // We cannot know for sure whether this is partitioned collection or not, because
            // partition key definition cache can be outdated.
            // So we route request to the first partition. If this is non-partitioned collection - request will succeed.
            // If it is partitioned collection - backend will return bad request as partition key header is required in this case.
            if (routingMap.OrderedPartitionKeyRanges.Count == 1)
            {
                return(routingMap.OrderedPartitionKeyRanges.Single());
            }

            if (collectionCacheIsUptoDate)
            {
                // If the current collection is user-partitioned collection
                if (collection.PartitionKey.Paths.Count >= 1 &&
                    !collection.PartitionKey.IsSystemKey.GetValueOrDefault(false))
                {
                    throw new BadRequestException(RMResources.MissingPartitionKeyValue)
                          {
                              ResourceAddress = request.ResourceAddress
                          };
                }
                else if (routingMap.OrderedPartitionKeyRanges.Count > 1)
                {
                    // With migrated-fixed-collection, it is possible to have multiple partition key ranges
                    // due to parallel usage of V3 SDK and a possible storage or throughput split
                    // The current client might be legacy and not aware of this.
                    // In such case route the request to the first partition
                    return(this.TryResolveServerPartitionByPartitionKey(
                               request,
                               "[]",          // This corresponds to first partition
                               collectionCacheIsUptoDate,
                               collection,
                               routingMap));
                }
                else
                {
                    // routingMap.OrderedPartitionKeyRanges.Count == 0
                    // Should never come here.
                    DefaultTrace.TraceCritical(
                        "No Partition Key ranges present for the collection {0}", collection.ResourceId);
                    throw new InternalServerErrorException(RMResources.InternalServerError)
                          {
                              ResourceAddress = request.ResourceAddress
                          };
                }
            }
            else
            {
                return(null);
            }
        }
Ejemplo n.º 24
0
 public void OnBeforeSendRequest(DocumentServiceRequest request)
 {
     this.documentServiceRequest = request;
     this.nextPolicy?.OnBeforeSendRequest(request);
 }
        /// <summary>
        /// ReplicatedResourceClient will use this API to get the direct connectivity AddressCache for given request.
        /// </summary>
        /// <param name="request"></param>
        private IAddressResolver GetAddressResolver(DocumentServiceRequest request)
        {
            Uri endpoint = this.endpointManager.ResolveServiceEndpoint(request);

            return(this.GetOrAddEndpoint(endpoint).AddressResolver);
        }
Ejemplo n.º 26
0
 public bool CanUseMultipleWriteLocations(DocumentServiceRequest request)
 {
     return(this.CanUseMultipleWriteLocations() &&
            (request.ResourceType == ResourceType.Document ||
             (request.ResourceType == ResourceType.StoredProcedure && request.OperationType == Documents.OperationType.ExecuteJavaScript)));
 }
Ejemplo n.º 27
0
 private static bool LogicalPartitionKeyProvided(DocumentServiceRequest request)
 {
     return(!string.IsNullOrEmpty(request.Headers[HttpConstants.HttpHeaders.PartitionKey]) ||
            !request.ResourceType.IsPartitioned());
 }
Ejemplo n.º 28
0
        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)
                {
                    this.serverPartitionAddressCache.Refresh(
                        partitionKeyRangeIdentity,
                        async() => await this.GetAddressesForRangeId(
                            request,
                            partitionKeyRangeIdentity.CollectionRid,
                            partitionKeyRangeIdentity.PartitionKeyRangeId,
                            forceRefresh: true),
                        cancellationToken);

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

                PartitionAddressInformation addresses = await this.serverPartitionAddressCache.GetAsync(
                    partitionKeyRangeIdentity,
                    null,
                    () => 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;
            }
        }
Ejemplo n.º 29
0
        private async Task <DocumentServiceResponse> ProcessUpsertAsync(IStoreModel storeProxy, DocumentServiceRequest serviceRequest, CancellationToken cancellationToken)
        {
            DocumentServiceResponse response = await storeProxy.ProcessMessageAsync(serviceRequest, cancellationToken);

            this.client.DocumentClient.CaptureSessionToken(serviceRequest, response);
            return(response);
        }
Ejemplo n.º 30
0
 public Task ReauthorizeSystemKeySignedRequestAsync(DocumentServiceRequest request, CancellationToken cancellationToken)
 {
     return(Task.FromResult(0));
 }