Beispiel #1
0
        /// <summary>
        /// TryAddPartitionKeyRangeToContinuationTokenAsync
        /// </summary>
        /// <returns><c>false</c> if collectionRid is likely wrong because range was not found. Cache needs to be refreshed probably.</returns>
        public virtual async Task <bool> TryAddPartitionKeyRangeToContinuationTokenAsync(
            INameValueCollection backendResponseHeaders,
            IReadOnlyList <Range <string> > providedPartitionKeyRanges,
            IRoutingMapProvider routingMapProvider,
            string collectionRid,
            ResolvedRangeInfo resolvedRangeInfo,
            ITrace trace,
            RntdbEnumerationDirection direction = RntdbEnumerationDirection.Forward)
        {
            Debug.Assert(resolvedRangeInfo.ResolvedRange != null, "ResolvedRange can't be null");

            PartitionKeyRange currentRange = resolvedRangeInfo.ResolvedRange;

            // IF : Split happened, or already had multiple target ranges in the continuation
            if (resolvedRangeInfo.ContinuationTokens != null && resolvedRangeInfo.ContinuationTokens.Count > 1)
            {
                if (!string.IsNullOrEmpty(backendResponseHeaders[HttpConstants.HttpHeaders.Continuation]))
                {
                    resolvedRangeInfo.ContinuationTokens[0].Token = backendResponseHeaders[HttpConstants.HttpHeaders.Continuation];
                }
                else
                {
                    resolvedRangeInfo.ContinuationTokens.RemoveAt(0);
                }

                backendResponseHeaders[HttpConstants.HttpHeaders.Continuation] = JsonConvert.SerializeObject(resolvedRangeInfo.ContinuationTokens);
            }
            else
            {
                //// ELSE: Single target Range was provided, and no split happened

                PartitionKeyRange rangeToUse = currentRange;

                // We only need to get the next range if we have to
                if (string.IsNullOrEmpty(backendResponseHeaders[HttpConstants.HttpHeaders.Continuation]))
                {
                    if (direction == RntdbEnumerationDirection.Reverse)
                    {
                        rangeToUse = PartitionRoutingHelper.MinBefore(
                            (await routingMapProvider.TryGetOverlappingRangesAsync(
                                 collectionRid,
                                 providedPartitionKeyRanges.Single(),
                                 trace)).ToList(),
                            currentRange);
                    }
                    else
                    {
                        Range <string> nextProvidedRange = PartitionRoutingHelper.MinAfter(
                            providedPartitionKeyRanges,
                            currentRange.ToRange(),
                            Range <string> .MaxComparer.Instance);

                        if (nextProvidedRange == null)
                        {
                            return(true);
                        }

                        string max = string.CompareOrdinal(nextProvidedRange.Min, currentRange.MaxExclusive) > 0
                             ? nextProvidedRange.Min
                             : currentRange.MaxExclusive;

                        if (string.CompareOrdinal(max, PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey) == 0)
                        {
                            return(true);
                        }

                        PartitionKeyRange nextRange = await routingMapProvider.TryGetRangeByEffectivePartitionKeyAsync(collectionRid, max, trace);

                        if (nextRange == null)
                        {
                            return(false);
                        }

                        rangeToUse = nextRange;
                    }
                }

                if (rangeToUse != null)
                {
                    backendResponseHeaders[HttpConstants.HttpHeaders.Continuation] = PartitionRoutingHelper.AddPartitionKeyRangeToContinuationToken(
                        backendResponseHeaders[HttpConstants.HttpHeaders.Continuation],
                        rangeToUse);
                }
            }

            return(true);
        }
        public async Task AddPartitionKeyRangeToContinuationTokenOnBoundry()
        {
            List <Range <string> > providedRanges = new List <Range <string> > {
                new Range <string>(
                    "A",
                    "D",
                    isMinInclusive: true,
                    isMaxInclusive: false)
            };

            //Reverse
            ResolvedRangeInfo currentPartitionKeyRange = new ResolvedRangeInfo(new PartitionKeyRange {
                Id = "0", MinInclusive = "A", MaxExclusive = "B"
            }, null);
            IReadOnlyList <PartitionKeyRange> overlappingRanges = new List <PartitionKeyRange> {
                new PartitionKeyRange {
                    Id = "0", MinInclusive = "A", MaxExclusive = "B"
                },
            }.AsReadOnly();
            Mock <IRoutingMapProvider> routingMapProvider = new Mock <IRoutingMapProvider>();

            routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync(
                                         It.IsAny <string>(),
                                         It.Is <Range <string> >(x => x.Min == providedRanges.Single().Min&& x.Max == providedRanges.Single().Max),
                                         It.IsAny <ITrace>(),
                                         It.Is <bool>(x => x == false)
                                         )).Returns(Task.FromResult(overlappingRanges)).Verifiable();

            PartitionRoutingHelper          partitionRoutingHelper = new PartitionRoutingHelper();
            StoreRequestNameValueCollection headers = new StoreRequestNameValueCollection();
            bool result = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync(
                headers,
                providedRanges,
                routingMapProvider.Object,
                CollectionId,
                currentPartitionKeyRange,
                NoOpTrace.Singleton,
                RntdbEnumerationDirection.Reverse
                );

            Assert.IsTrue(result);
            routingMapProvider.Verify();
            string expectedContinuationToken = JsonConvert.SerializeObject(new CompositeContinuationToken
            {
                Token = null,
                Range = overlappingRanges.First().ToRange(),
            });

            Assert.IsNull(headers.Get(HttpConstants.HttpHeaders.Continuation));

            //Forward
            currentPartitionKeyRange = new ResolvedRangeInfo(new PartitionKeyRange {
                Id = "0", MinInclusive = "A", MaxExclusive = "D"
            }, null);
            overlappingRanges = new List <PartitionKeyRange> {
                new PartitionKeyRange {
                    Id = "0", MinInclusive = "A", MaxExclusive = "D"
                },
            }.AsReadOnly();
            routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync(
                                         It.IsAny <string>(),
                                         It.IsAny <Range <string> >(),
                                         It.IsAny <ITrace>(),
                                         It.IsAny <bool>()
                                         )).Returns(Task.FromResult(overlappingRanges));
            headers = new StoreRequestNameValueCollection();

            result = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync(
                headers,
                providedRanges,
                routingMapProvider.Object,
                CollectionId,
                currentPartitionKeyRange,
                NoOpTrace.Singleton,
                RntdbEnumerationDirection.Forward
                );

            Assert.IsTrue(result);
            routingMapProvider.Verify(m => m.TryGetOverlappingRangesAsync(
                                          It.IsAny <string>(),
                                          It.Is <Range <string> >(e => e.IsMaxInclusive),
                                          It.IsAny <ITrace>(),
                                          It.IsAny <bool>()
                                          ), Times.Never);
            expectedContinuationToken = JsonConvert.SerializeObject(new CompositeContinuationToken
            {
                Token = null,
                Range = overlappingRanges.Last().ToRange(),
            });
            Assert.IsNull(headers.Get(HttpConstants.HttpHeaders.Continuation));
        }
        public async Task GetTargetRangeFromContinuationTokenOnSplit()
        {
            const string Token = "token";

            List <Range <string> > providedRanges = new List <Range <string> > {
                new Range <string>(
                    PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey,
                    PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey,
                    isMinInclusive: true,
                    isMaxInclusive: false)
            };

            Range <string> rangeFromContinuationToken        = new Range <string>("A", "C", true, false);
            List <CompositeContinuationToken> suppliedTokens = new List <CompositeContinuationToken>
            {
                new CompositeContinuationToken {
                    Token = Token, Range = rangeFromContinuationToken
                }
            };

            IReadOnlyList <PartitionKeyRange> overlappingRanges = new List <PartitionKeyRange> {
                new PartitionKeyRange {
                    Id = "2", MinInclusive = "A", MaxExclusive = "B"
                },
                new PartitionKeyRange {
                    Id = "3", MinInclusive = "B", MaxExclusive = "C"
                },
                new PartitionKeyRange {
                    Id = "1", MinInclusive = "C", MaxExclusive = "D"
                }
            }.AsReadOnly();
            IReadOnlyList <PartitionKeyRange> replacedRanges     = overlappingRanges.Take(2).ToList().AsReadOnly();
            Mock <IRoutingMapProvider>        routingMapProvider = new Mock <IRoutingMapProvider>();

            routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync(
                                         It.IsAny <string>(),
                                         It.Is <Range <string> >(x => x.Min == rangeFromContinuationToken.Min),
                                         It.IsAny <ITrace>(),
                                         It.Is <bool>(x => x == false)
                                         )).Returns(Task.FromResult((IReadOnlyList <PartitionKeyRange>)overlappingRanges.Take(1).ToList())).Verifiable();
            routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync(
                                         It.IsAny <string>(),
                                         It.Is <Range <string> >(x => x.Min == rangeFromContinuationToken.Min && x.Max == rangeFromContinuationToken.Max),
                                         It.IsAny <ITrace>(),
                                         It.Is <bool>(x => x == true)
                                         )).Returns(Task.FromResult(replacedRanges)).Verifiable();

            //Reverse
            PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper();
            ResolvedRangeInfo      resolvedRangeInfo      = await partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync(
                providedRanges,
                routingMapProvider.Object,
                CollectionId,
                rangeFromContinuationToken,
                suppliedTokens,
                NoOpTrace.Singleton,
                RntdbEnumerationDirection.Reverse);

            routingMapProvider.Verify();
            Assert.IsTrue(replacedRanges.Last().Equals(resolvedRangeInfo.ResolvedRange));
            List <PartitionKeyRange> reversedReplacedRanges = new List <PartitionKeyRange>(replacedRanges);

            reversedReplacedRanges.Reverse();
            Assert.AreEqual(replacedRanges.Count, resolvedRangeInfo.ContinuationTokens.Count);
            Assert.AreEqual(resolvedRangeInfo.ContinuationTokens[0].Token, Token);

            for (int i = 0; i < resolvedRangeInfo.ContinuationTokens.Count; i++)
            {
                Assert.IsTrue(reversedReplacedRanges[i].ToRange().Equals(resolvedRangeInfo.ContinuationTokens[i].Range));
            }

            //Forward
            partitionRoutingHelper = new PartitionRoutingHelper();
            resolvedRangeInfo      = await partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync(
                providedRanges,
                routingMapProvider.Object,
                CollectionId,
                rangeFromContinuationToken,
                suppliedTokens,
                NoOpTrace.Singleton,
                RntdbEnumerationDirection.Forward);

            routingMapProvider.Verify();
            Assert.IsTrue(replacedRanges.First().Equals(resolvedRangeInfo.ResolvedRange));
            Assert.AreEqual(replacedRanges.Count, resolvedRangeInfo.ContinuationTokens.Count);
            Assert.AreEqual(resolvedRangeInfo.ContinuationTokens[0].Token, Token);

            for (int i = 0; i < resolvedRangeInfo.ContinuationTokens.Count; i++)
            {
                Assert.IsTrue(replacedRanges[i].ToRange().Equals(resolvedRangeInfo.ContinuationTokens[i].Range));
            }
        }
        public async Task GetTargetRangeFromContinuationTokenWhenEmpty()
        {
            List <Range <string> > providedRanges = new List <Range <string> > {
                new Range <string>(
                    PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey,
                    PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey,
                    isMinInclusive: true,
                    isMaxInclusive: false)
            };

            //Empty
            Range <string> range = new Range <string>("", "", true, false);
            List <CompositeContinuationToken> suppliedTokens = new List <CompositeContinuationToken>
            {
                new CompositeContinuationToken {
                    Range = range
                }
            };

            IReadOnlyList <PartitionKeyRange> overlappingRanges = new List <PartitionKeyRange> {
                new PartitionKeyRange {
                    Id = "0", MinInclusive = "A", MaxExclusive = "B"
                },
                new PartitionKeyRange {
                    Id = "1", MinInclusive = "B", MaxExclusive = "C"
                }
            }.AsReadOnly();
            Mock <IRoutingMapProvider> routingMapProvider = new Mock <IRoutingMapProvider>();

            routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync(
                                         It.IsAny <string>(),
                                         It.IsAny <Range <string> >(),
                                         It.IsAny <ITrace>(),
                                         It.Is <bool>(x => x == false)
                                         )).Returns(Task.FromResult(overlappingRanges)).Verifiable();


            //Reverse
            PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper();
            ResolvedRangeInfo      resolvedRangeInfo      = await partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync(
                providedRanges,
                routingMapProvider.Object,
                CollectionId,
                range,
                suppliedTokens,
                NoOpTrace.Singleton,
                RntdbEnumerationDirection.Reverse);

            Assert.AreEqual(overlappingRanges.Last().Id, resolvedRangeInfo.ResolvedRange.Id);
            CollectionAssert.AreEqual(suppliedTokens, resolvedRangeInfo.ContinuationTokens);
            routingMapProvider.Verify();

            //Forward
            routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync(
                                         It.IsAny <string>(),
                                         It.Is <Range <string> >(x => x.Min == range.Min),
                                         It.IsAny <ITrace>(),
                                         It.IsAny <bool>()
                                         )).Returns(Task.FromResult((IReadOnlyList <PartitionKeyRange>)overlappingRanges.Take(1).ToList())).Verifiable();
            resolvedRangeInfo = await partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync(
                providedRanges,
                routingMapProvider.Object,
                CollectionId,
                range,
                suppliedTokens,
                NoOpTrace.Singleton,
                RntdbEnumerationDirection.Forward);

            Assert.AreEqual(overlappingRanges.First().Id, resolvedRangeInfo.ResolvedRange.Id);
            CollectionAssert.AreEqual(suppliedTokens, resolvedRangeInfo.ContinuationTokens);
            routingMapProvider.Verify();
        }
        public override async Task <ResponseMessage> SendAsync(
            RequestMessage request,
            CancellationToken cancellationToken)
        {
            ResponseMessage 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)direction == (byte)RntdbEnumerationDirection.Reverse ? 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();

                ContainerProperties 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.TryGetTargetRangeFromContinuationTokenRangeAsync(
                        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.TryGetTargetRangeFromContinuationTokenRangeAsync(
                        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)
            {
                ResponseMessage 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;
            }
        }
        public async Task AddPartitionKeyRangeToContinuationTokenOnNullBackendContinuation()
        {
            List <Range <string> > providedRanges = new List <Range <string> > {
                new Range <string>(
                    "A",
                    "D",
                    isMinInclusive: true,
                    isMaxInclusive: false)
            };
            ResolvedRangeInfo currentPartitionKeyRange = new ResolvedRangeInfo(new PartitionKeyRange {
                Id = "1", MinInclusive = "B", MaxExclusive = "C"
            }, null);

            IReadOnlyList <PartitionKeyRange> overlappingRanges = new List <PartitionKeyRange> {
                new PartitionKeyRange {
                    Id = "0", MinInclusive = "A", MaxExclusive = "B"
                },
                currentPartitionKeyRange.ResolvedRange,
                new PartitionKeyRange {
                    Id = "3", MinInclusive = "C", MaxExclusive = "D"
                }
            }.AsReadOnly();
            Mock <IRoutingMapProvider> routingMapProvider = new Mock <IRoutingMapProvider>();

            routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync(
                                         It.IsAny <string>(),
                                         It.Is <Range <string> >(x => x.Min == providedRanges.Single().Min&& x.Max == providedRanges.Single().Max),
                                         It.Is <bool>(x => x == false)
                                         )).Returns(Task.FromResult(overlappingRanges)).Verifiable();

            //Reverse
            PartitionRoutingHelper        partitionRoutingHelper = new PartitionRoutingHelper();
            DictionaryNameValueCollection headers = new DictionaryNameValueCollection();
            bool result = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync(
                headers,
                providedRanges,
                routingMapProvider.Object,
                CollectionId,
                currentPartitionKeyRange,
                RntdbEnumerationDirection.Reverse
                );

            Assert.IsTrue(result);
            routingMapProvider.Verify();
            string expectedContinuationToken = JsonConvert.SerializeObject(new CompositeContinuationToken
            {
                Token = null,
                Range = overlappingRanges.First().ToRange(),
            });

            Assert.AreEqual(expectedContinuationToken, headers.Get(HttpConstants.HttpHeaders.Continuation));

            //Forward
            routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync(
                                         It.IsAny <string>(),
                                         It.IsAny <Range <string> >(),
                                         It.IsAny <bool>()
                                         )).Returns(Task.FromResult((IReadOnlyList <PartitionKeyRange>)overlappingRanges.Skip(2).ToList())).Verifiable();
            headers = new DictionaryNameValueCollection();
            result  = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync(
                headers,
                providedRanges,
                routingMapProvider.Object,
                CollectionId,
                currentPartitionKeyRange,
                RntdbEnumerationDirection.Forward
                );

            Assert.IsTrue(result);
            routingMapProvider.Verify();
            expectedContinuationToken = JsonConvert.SerializeObject(new CompositeContinuationToken
            {
                Token = null,
                Range = overlappingRanges.Last().ToRange(),
            });
            Assert.AreEqual(expectedContinuationToken, headers.Get(HttpConstants.HttpHeaders.Continuation));
        }