Ejemplo n.º 1
0
        // Name based look-up, needs re-computation and can't be cached
        public override async Task <string> GetRIDAsync(CancellationToken cancellationToken)
        {
            ContainerProperties containerProperties = await this.GetCachedContainerPropertiesAsync(cancellationToken);

            return(containerProperties?.ResourceId);
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Instantiates a new instance of the <see cref="PartitionKeyInternal"/> object.
        /// </summary>
        /// <remarks>
        /// The function selects the right partition key constant for inserting documents that don't have
        /// a value for partition key. The constant selection is based on whether the collection is migrated
        /// or user partitioned
        ///
        /// For non-existing container will throw <see cref="DocumentClientException"/> with 404 as status code
        /// </remarks>
        public override async Task <PartitionKeyInternal> GetNonePartitionKeyValueAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            ContainerProperties containerProperties = await this.GetCachedContainerPropertiesAsync(cancellationToken);

            return(containerProperties.GetNoneValue());
        }
        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.º 4
0
        /// <summary>
        /// Instantiates a new instance of the <see cref="PartitionKeyInternal"/> object.
        /// </summary>
        /// <remarks>
        /// The function selects the right partition key constant for inserting documents that don't have
        /// a value for partition key. The constant selection is based on whether the collection is migrated
        /// or user partitioned
        ///
        /// For non-existing container will throw <see cref="DocumentClientException"/> with 404 as status code
        /// </remarks>
        public override async Task <PartitionKeyInternal> GetNonePartitionKeyValueAsync(ITrace trace, CancellationToken cancellationToken = default)
        {
            ContainerProperties containerProperties = await this.GetCachedContainerPropertiesAsync(forceRefresh : false, trace, cancellationToken : cancellationToken);

            return(containerProperties.GetNoneValue());
        }
        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 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.º 7
0
 internal void ValidateContainerProperties(ContainerProperties containerProperties)
 {
     containerProperties.ValidateRequiredProperties();
     this.ClientContext.ValidateResource(containerProperties.Id);
 }
        /// <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);
        }
Ejemplo n.º 9
0
        public async Task <ContainerResponse> CreateContainerIfNotExistsAsync(
            ContainerProperties containerProperties,
            ThroughputProperties throughputProperties,
            RequestOptions requestOptions,
            ITrace trace,
            CancellationToken cancellationToken)
        {
            if (containerProperties == null)
            {
                throw new ArgumentNullException(nameof(containerProperties));
            }

            this.ValidateContainerProperties(containerProperties);

            double        totalRequestCharge = 0;
            ContainerCore container          = (ContainerCore)this.GetContainer(containerProperties.Id);

            using (ResponseMessage readResponse = await container.ReadContainerStreamAsync(
                       requestOptions: requestOptions,
                       trace: trace,
                       cancellationToken: cancellationToken))
            {
                totalRequestCharge = readResponse.Headers.RequestCharge;

                if (readResponse.StatusCode != HttpStatusCode.NotFound)
                {
                    ContainerResponse retrivedContainerResponse = this.ClientContext.ResponseFactory.CreateContainerResponse(
                        container,
                        readResponse);

                    if (containerProperties.PartitionKey.Kind != Documents.PartitionKind.MultiHash)
                    {
                        if (!retrivedContainerResponse.Resource.PartitionKeyPath.Equals(containerProperties.PartitionKeyPath))
                        {
                            throw new ArgumentException(
                                      string.Format(
                                          ClientResources.PartitionKeyPathConflict,
                                          containerProperties.PartitionKeyPath,
                                          containerProperties.Id,
                                          retrivedContainerResponse.Resource.PartitionKeyPath),
                                      nameof(containerProperties.PartitionKey));
                        }
                    }
#if PREVIEW
                    else
                    {
                        IReadOnlyList <string> retrivedPartitionKeyPaths = retrivedContainerResponse.Resource.PartitionKeyPaths;
                        IReadOnlyList <string> receivedPartitionKeyPaths = containerProperties.PartitionKeyPaths;

                        if (retrivedPartitionKeyPaths.Count != receivedPartitionKeyPaths.Count || !Enumerable.SequenceEqual(retrivedPartitionKeyPaths, receivedPartitionKeyPaths))
                        {
                            throw new ArgumentException(
                                      string.Format(
                                          ClientResources.PartitionKeyPathConflict,
                                          string.Join(",", containerProperties.PartitionKeyPaths),
                                          containerProperties.Id,
                                          string.Join(",", retrivedContainerResponse.Resource.PartitionKeyPaths)),
                                      nameof(containerProperties.PartitionKey));
                        }
                    }
#endif
                    return(retrivedContainerResponse);
                }
            }

            this.ValidateContainerProperties(containerProperties);
            using (ResponseMessage createResponse = await this.CreateContainerStreamAsync(
                       containerProperties,
                       throughputProperties,
                       requestOptions,
                       trace,
                       cancellationToken))
            {
                totalRequestCharge += createResponse.Headers.RequestCharge;
                createResponse.Headers.RequestCharge = totalRequestCharge;

                if (createResponse.StatusCode != HttpStatusCode.Conflict)
                {
                    return(this.ClientContext.ResponseFactory.CreateContainerResponse(container, createResponse));
                }
            }

            // This second Read is to handle the race condition when 2 or more threads have Read the database and only one succeeds with Create
            // so for the remaining ones we should do a Read instead of throwing Conflict exception
            using (ResponseMessage readResponseAfterCreate = await container.ReadContainerStreamAsync(
                       requestOptions: requestOptions,
                       trace: trace,
                       cancellationToken: cancellationToken))
            {
                totalRequestCharge += readResponseAfterCreate.Headers.RequestCharge;
                readResponseAfterCreate.Headers.RequestCharge = totalRequestCharge;

                return(this.ClientContext.ResponseFactory.CreateContainerResponse(container, readResponseAfterCreate));
            }
        }
 /// <summary>
 /// Creates a container as an asynchronous operation in the Azure Cosmos service.
 /// </summary>
 /// <param name="containerProperties">The <see cref="ContainerProperties"/> object.</param>
 /// <param name="throughput">(Optional) The throughput provisioned for a container in measurement of Request Units per second in the Azure Cosmos DB service.</param>
 /// <param name="requestOptions">(Optional) The options for the container request <see cref="RequestOptions"/></param>
 /// <param name="cancellationToken">(Optional) <see cref="CancellationToken"/> representing request cancellation.</param>
 /// <returns>A <see cref="Task"/> containing a <see cref="ResponseMessage"/> containing the created resource record.</returns>
 /// <example>
 /// Creates a container as an asynchronous operation in the Azure Cosmos service and return stream response.
 /// <code language="c#">
 /// <![CDATA[
 /// ContainerProperties containerProperties = new ContainerProperties()
 /// {
 ///     Id = Guid.NewGuid().ToString(),
 ///     PartitionKeyPath = "/pk",
 /// };
 ///
 /// using(ResponseMessage response = await this.cosmosDatabase.CreateContainerStreamAsync(containerProperties))
 /// {
 /// }
 /// ]]>
 /// </code>
 /// </example>
 /// <seealso cref="DefineContainer(string, string)"/>
 /// <remarks>
 /// <seealso href="https://docs.microsoft.com/azure/cosmos-db/request-units"/> for details on provision throughput.
 /// </remarks>
 public abstract Task <ResponseMessage> CreateContainerStreamAsync(
     ContainerProperties containerProperties,
     int?throughput = null,
     RequestOptions requestOptions       = null,
     CancellationToken cancellationToken = default(CancellationToken));
 /// <summary>
 /// <para>Check if a container exists, and if it doesn't, create it.
 /// Only the container id is used to verify if there is an existing container. Other container properties such as throughput are not validated and can be different then the passed properties.</para>
 /// </summary>
 /// <param name="containerProperties">The <see cref="ContainerProperties"/> object.</param>
 /// <param name="throughput">(Optional) The throughput provisioned for a container in measurement of Requests Units per second in the Azure Cosmos DB service.</param>
 /// <param name="requestOptions">(Optional) The options for the container request <see cref="RequestOptions"/></param>
 /// <param name="cancellationToken">(Optional) <see cref="CancellationToken"/> representing request cancellation.</param>
 /// <returns>A <see cref="Task"/> containing a <see cref="ContainerResponse"/> which wraps a <see cref="ContainerProperties"/> containing the read resource record.</returns>
 /// <exception cref="ArgumentNullException">If either <paramref name="containerProperties"/> is not set.</exception>
 /// <exception cref="System.AggregateException">Represents a consolidation of failures that occurred during async processing. Look within InnerExceptions to find the actual exception(s).</exception>
 /// <exception cref="CosmosException">This exception can encapsulate many different types of errors. To determine the specific error always look at the StatusCode property. Some common codes you may get when creating a container are:
 /// <list type="table">
 ///     <listheader>
 ///         <term>StatusCode</term><description>Reason for exception</description>
 ///     </listheader>
 ///     <item>
 ///         <term>400</term><description>BadRequest - This means something was wrong with the request supplied. It is likely that an id was not supplied for the new container.</description>
 ///     </item>
 ///     <item>
 ///         <term>403</term><description>Forbidden - This means you attempted to exceed your quota for containers. Contact support to have this quota increased.</description>
 ///     </item>
 ///     <item>
 ///         <term>409</term><description>Conflict - This means a <see cref="ContainerProperties"/> with an id matching the id you supplied already existed.</description>
 ///     </item>
 /// </list>
 /// </exception>
 /// <list>
 ///     <listheader>
 ///         <term>StatusCode</term><description>Common success StatusCodes for the CreateDatabaseIfNotExistsAsync operation</description>
 ///     </listheader>
 ///     <item>
 ///         <term>201</term><description>Created - New database is created.</description>
 ///     </item>
 ///     <item>
 ///         <term>200</term><description>Accepted - This means the database already exists.</description>
 ///     </item>
 /// </list>
 /// <example>
 ///
 /// <code language="c#">
 /// <![CDATA[
 /// ContainerProperties containerProperties = new ContainerProperties()
 /// {
 ///     Id = Guid.NewGuid().ToString(),
 ///     PartitionKeyPath = "/pk",
 ///     IndexingPolicy = new IndexingPolicy()
 ///    {
 ///         Automatic = false,
 ///         IndexingMode = IndexingMode.Lazy,
 ///    };
 /// };
 ///
 /// ContainerResponse response = await this.cosmosDatabase.CreateContainerIfNotExistsAsync(containerProperties);
 /// ]]>
 /// </code>
 /// </example>
 /// <remarks>
 /// <seealso href="https://docs.microsoft.com/azure/cosmos-db/request-units"/> for details on provision throughput.
 /// </remarks>
 public abstract Task <ContainerResponse> CreateContainerIfNotExistsAsync(
     ContainerProperties containerProperties,
     int?throughput = null,
     RequestOptions requestOptions       = null,
     CancellationToken cancellationToken = default(CancellationToken));
Ejemplo n.º 12
0
        public override async Task <ContainerResponse> CreateContainerIfNotExistsAsync(
            ContainerProperties containerProperties,
            int?throughput = null,
            RequestOptions requestOptions       = null,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (containerProperties == null)
            {
                throw new ArgumentNullException(nameof(containerProperties));
            }

            this.ValidateContainerProperties(containerProperties);

            Container       container    = this.GetContainer(containerProperties.Id);
            ResponseMessage readResponse = await container.ReadContainerStreamAsync(
                cancellationToken : cancellationToken);

            if (readResponse.StatusCode != HttpStatusCode.NotFound)
            {
                ContainerResponse retrivedContainerResponse = await this.ClientContext.ResponseFactory.CreateContainerResponseAsync(
                    container,
                    Task.FromResult(readResponse));

                if (!retrivedContainerResponse.Resource.PartitionKeyPath.Equals(containerProperties.PartitionKeyPath))
                {
                    throw new ArgumentException(
                              string.Format(
                                  ClientResources.PartitionKeyPathConflict,
                                  containerProperties.PartitionKeyPath,
                                  containerProperties.Id,
                                  retrivedContainerResponse.Resource.PartitionKeyPath),
                              nameof(containerProperties.PartitionKey));
                }

                return(retrivedContainerResponse);
            }

            this.ValidateContainerProperties(containerProperties);
            ResponseMessage createResponse = await this.CreateContainerStreamAsync(
                containerProperties,
                throughput,
                requestOptions,
                cancellationToken);

            // Merge the previous message diagnostics
            createResponse.DiagnosticsContext.AddDiagnosticsInternal(readResponse.DiagnosticsContext);

            if (readResponse.StatusCode != HttpStatusCode.Conflict)
            {
                return(await this.ClientContext.ResponseFactory.CreateContainerResponseAsync(container, Task.FromResult(createResponse)));
            }

            // This second Read is to handle the race condition when 2 or more threads have Read the database and only one succeeds with Create
            // so for the remaining ones we should do a Read instead of throwing Conflict exception
            ResponseMessage readResponseAfterCreate = await container.ReadContainerStreamAsync(
                cancellationToken : cancellationToken);

            // Merge the previous message diagnostics
            createResponse.DiagnosticsContext.AddDiagnosticsInternal(readResponse.DiagnosticsContext);
            return(await this.ClientContext.ResponseFactory.CreateContainerResponseAsync(container, Task.FromResult(readResponseAfterCreate)));
        }
 Task <ContainerResponse> CreateContainerAsync(ContainerProperties containerProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions = null, CancellationToken cancellationToken = default)
 {
     return(TaskHelper.RunInlineIfNeededAsync(() => this.database.CreateContainerAsync(containerProperties, throughputProperties, requestOptions, cancellationToken)));
 }