private static async Task <string> GetPKRangeIdForPartitionKey(
            Container container,
            ContainerProperties containerProperties,
            PartitionKey pkValue)
        {
            CollectionRoutingMap collectionRoutingMap =
                await((ContainerInternal)container).GetRoutingMapAsync(CancellationToken.None);
            string effectivePK =
                pkValue.InternalKey.GetEffectivePartitionKeyString(containerProperties.PartitionKey);

            return(collectionRoutingMap.GetRangeByEffectivePartitionKey(effectivePK).Id);
        }
        private async Task <string> ResolvePartitionKeyRangeIdAsync(
            ItemBatchOperation operation,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            PartitionKeyDefinition partitionKeyDefinition = await this.cosmosContainer.GetPartitionKeyDefinitionAsync(cancellationToken);

            CollectionRoutingMap collectionRoutingMap = await this.cosmosContainer.GetRoutingMapAsync(cancellationToken);

            Debug.Assert(operation.RequestOptions?.Properties?.TryGetValue(WFConstants.BackendHeaders.EffectivePartitionKeyString, out object epkObj) == null, "EPK is not supported");
            Documents.Routing.PartitionKeyInternal partitionKeyInternal = await this.GetPartitionKeyInternalAsync(operation, cancellationToken);

            operation.PartitionKeyJson = partitionKeyInternal.ToJsonString();
            string effectivePartitionKeyString = partitionKeyInternal.GetEffectivePartitionKeyString(partitionKeyDefinition);

            return(collectionRoutingMap.GetRangeByEffectivePartitionKey(effectivePartitionKeyString).Id);
        }
Ejemplo n.º 3
0
        async Task <IEnumerable <string> > GetPartitionKeyRangesAsync(
            FeedToken feedToken,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (feedToken is FeedTokenEPKRange feedTokenEPKRange)
            {
                PartitionKeyRangeCache partitionKeyRangeCache = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync();

                string containerRId = await this.GetRIDAsync(cancellationToken);

                IReadOnlyList <Documents.PartitionKeyRange> partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync(containerRId, feedTokenEPKRange.CompleteRange, forceRefresh : false);

                return(partitionKeyRanges.Select(partitionKeyRange => partitionKeyRange.Id));
            }

            if (feedToken is FeedTokenPartitionKeyRange feedTokenPartitionKeyRange)
            {
                if (feedTokenPartitionKeyRange.FeedTokenEPKRange != null)
                {
                    return(await this.GetPartitionKeyRangesAsync(feedTokenPartitionKeyRange.FeedTokenEPKRange, cancellationToken));
                }

                return(new List <string>()
                {
                    feedTokenPartitionKeyRange.PartitionKeyRangeId
                });
            }

            if (feedToken is FeedTokenPartitionKey feedTokenPartitionKey)
            {
                CollectionRoutingMap collectionRoutingMap = await this.GetRoutingMapAsync(cancellationToken);

                PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken);

                PartitionKeyInternal partitionKeyInternal = feedTokenPartitionKey.PartitionKey.InternalKey;
                string effectivePartitionKeyString        = partitionKeyInternal.GetEffectivePartitionKeyString(partitionKeyDefinition);
                string partitionKeyRangeId = collectionRoutingMap.GetRangeByEffectivePartitionKey(effectivePartitionKeyString).Id;
                return(new List <string>()
                {
                    partitionKeyRangeId
                });
            }

            throw new ArgumentException(nameof(feedToken), ClientResources.FeedToken_UnrecognizedFeedToken);
        }
        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);
        }
        private async Task <ResolutionResult> TryResolveServerPartitionAsync(
            DocumentServiceRequest request,
            ContainerProperties collection,
            CollectionRoutingMap routingMap,
            bool collectionCacheIsUptodate,
            bool collectionRoutingMapCacheIsUptodate,
            bool forceRefreshPartitionAddresses,
            CancellationToken cancellationToken)
        {
            // Check if this request partitionkeyrange-aware routing logic. We cannot retry here in this case
            // and need to bubble up errors.
            if (request.PartitionKeyRangeIdentity != null)
            {
                return(await this.TryResolveServerPartitionByPartitionKeyRangeIdAsync(
                           request,
                           collection,
                           routingMap,
                           collectionCacheIsUptodate,
                           collectionRoutingMapCacheIsUptodate,
                           forceRefreshPartitionAddresses,
                           cancellationToken));
            }

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

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

            object effectivePartitionKeyStringObject = null;

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

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

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

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

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

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

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

            return(new ResolutionResult(range, addresses, serviceIdentity));
        }
        public void TestCollectionRoutingMap()
        {
            ServiceIdentity      serviceIdentity0 = new ServiceIdentity("1", new Uri("http://1"), false);
            ServiceIdentity      serviceIdentity1 = new ServiceIdentity("2", new Uri("http://2"), false);
            ServiceIdentity      serviceIdentity2 = new ServiceIdentity("3", new Uri("http://3"), false);
            ServiceIdentity      serviceIdentity3 = new ServiceIdentity("4", new Uri("http://4"), false);
            CollectionRoutingMap routingMap       = CollectionRoutingMap.TryCreateCompleteRoutingMap(
                new[]
            {
                Tuple.Create(
                    new PartitionKeyRange {
                    Id           = "2",
                    MinInclusive = "0000000050",
                    MaxExclusive = "0000000070"
                },
                    serviceIdentity2),

                Tuple.Create(
                    new PartitionKeyRange {
                    Id           = "0",
                    MinInclusive = "",
                    MaxExclusive = "0000000030"
                },
                    serviceIdentity0),

                Tuple.Create(
                    new PartitionKeyRange {
                    Id           = "1",
                    MinInclusive = "0000000030",
                    MaxExclusive = "0000000050"
                },
                    serviceIdentity1),

                Tuple.Create(
                    new PartitionKeyRange {
                    Id           = "3",
                    MinInclusive = "0000000070",
                    MaxExclusive = "FF"
                },
                    serviceIdentity3),
            }, string.Empty);

            Assert.AreEqual("0", routingMap.OrderedPartitionKeyRanges[0].Id);
            Assert.AreEqual("1", routingMap.OrderedPartitionKeyRanges[1].Id);
            Assert.AreEqual("2", routingMap.OrderedPartitionKeyRanges[2].Id);
            Assert.AreEqual("3", routingMap.OrderedPartitionKeyRanges[3].Id);

            Assert.AreEqual(serviceIdentity0, routingMap.TryGetInfoByPartitionKeyRangeId("0"));
            Assert.AreEqual(serviceIdentity1, routingMap.TryGetInfoByPartitionKeyRangeId("1"));
            Assert.AreEqual(serviceIdentity2, routingMap.TryGetInfoByPartitionKeyRangeId("2"));
            Assert.AreEqual(serviceIdentity3, routingMap.TryGetInfoByPartitionKeyRangeId("3"));

            Assert.AreEqual("0", routingMap.GetRangeByEffectivePartitionKey("").Id);
            Assert.AreEqual("0", routingMap.GetRangeByEffectivePartitionKey("0000000000").Id);
            Assert.AreEqual("1", routingMap.GetRangeByEffectivePartitionKey("0000000030").Id);
            Assert.AreEqual("1", routingMap.GetRangeByEffectivePartitionKey("0000000031").Id);
            Assert.AreEqual("3", routingMap.GetRangeByEffectivePartitionKey("0000000071").Id);

            Assert.AreEqual("0", routingMap.TryGetRangeByPartitionKeyRangeId("0").Id);
            Assert.AreEqual("1", routingMap.TryGetRangeByPartitionKeyRangeId("1").Id);

            Assert.AreEqual(4, routingMap.GetOverlappingRanges(new[] { new Range <string>(PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey, PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey, true, false) }).Count);
            Assert.AreEqual(0, routingMap.GetOverlappingRanges(new[] { new Range <string>(PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey, PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey, false, false) }).Count);
            IReadOnlyList <PartitionKeyRange> partitionKeyRanges =
                routingMap.GetOverlappingRanges(new[]
            {
                new Range <string>(
                    "0000000040",
                    "0000000040",
                    true,
                    true)
            });

            Assert.AreEqual(1, partitionKeyRanges.Count);
            Assert.AreEqual("1", partitionKeyRanges.ElementAt(0).Id);

            IReadOnlyList <PartitionKeyRange> partitionKeyRanges1 =
                routingMap.GetOverlappingRanges(new[]
            {
                new Range <string>(
                    "0000000040",
                    "0000000045",
                    true,
                    true),
                new Range <string>(
                    "0000000045",
                    "0000000046",
                    true,
                    true),
                new Range <string>(
                    "0000000046",
                    "0000000050",
                    true,
                    true)
            });

            Assert.AreEqual(2, partitionKeyRanges1.Count);
            Assert.AreEqual("1", partitionKeyRanges1.ElementAt(0).Id);
            Assert.AreEqual("2", partitionKeyRanges1.ElementAt(1).Id);
        }
        public static string GetPartitionKeyRangeId(PartitionKeyInternal partitionKeyInternal, PartitionKeyDefinition partitionKeyDefinition, CollectionRoutingMap collectionRoutingMap)
        {
            string effectivePartitionKey = partitionKeyInternal.GetEffectivePartitionKeyString(partitionKeyDefinition);

            return(collectionRoutingMap.GetRangeByEffectivePartitionKey(effectivePartitionKey).Id);
        }