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