예제 #1
0
        /// <summary>
        /// Creates a container as an asynchronous operation in the Azure Cosmos service.
        /// </summary>
        /// <param name="id">The cosmos container id</param>
        /// <param name="partitionKeyPath">The path to the partition key. Example: /location</param>
        /// <param name="throughput">(Optional) The throughput provisioned for a collection in measurement of Requests-per-Unit in the Azure Cosmos DB service.</param>
        /// <param name="requestOptions">(Optional) The options for the container request <see cref="CosmosRequestOptions"/></param>
        /// <param name="cancellationToken">(Optional) <see cref="CancellationToken"/> representing request cancellation.</param>
        /// <returns>A <see cref="Task"/> containing a <see cref="CosmosContainerResponse"/> which wraps a <see cref="CosmosContainerSettings"/> containing the read resource record.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="id"/> 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="CosmosContainerSettings"/> with an id matching the id you supplied already existed.</description>
        ///     </item>
        /// </list>
        /// </exception>
        /// <example>
        ///
        /// <code language="c#">
        /// <![CDATA[
        /// CosmosContainerResponse response = this.cosmosDatabase.Containers.CreateContainerAsync(Guid.NewGuid().ToString());
        /// ]]>
        /// </code>
        /// </example>
        public virtual Task <CosmosContainerResponse> CreateContainerAsync(
            string id,
            string partitionKeyPath,
            int?throughput = null,
            CosmosRequestOptions requestOptions = null,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            CosmosContainerSettings settings = new CosmosContainerSettings(id, partitionKeyPath);

            return(this.CreateContainerAsync(
                       settings,
                       throughput,
                       requestOptions,
                       cancellationToken));
        }
        private async Task <ShouldRetryResult> ShouldRetryInternalAsync(
            HttpStatusCode?statusCode,
            SubStatusCodes?subStatusCode,
            CancellationToken cancellationToken)
        {
            if (!statusCode.HasValue &&
                (!subStatusCode.HasValue ||
                 subStatusCode.Value == SubStatusCodes.Unknown))
            {
                return(null);
            }

            if (statusCode == HttpStatusCode.Gone &&
                subStatusCode == SubStatusCodes.PartitionKeyRangeGone)
            {
                if (this.retried)
                {
                    return(ShouldRetryResult.NoRetry());
                }

                using (DocumentServiceRequest request = DocumentServiceRequest.Create(
                           OperationType.Read,
                           ResourceType.Collection,
                           this.collectionLink,
                           null,
                           AuthorizationTokenType.PrimaryMasterKey))
                {
                    CosmosContainerSettings collection = await this.collectionCache.ResolveCollectionAsync(request, cancellationToken);

                    CollectionRoutingMap routingMap = await this.partitionKeyRangeCache.TryLookupAsync(collection.ResourceId, null, request, cancellationToken);

                    if (routingMap != null)
                    {
                        // Force refresh.
                        await this.partitionKeyRangeCache.TryLookupAsync(
                            collection.ResourceId,
                            routingMap,
                            request,
                            cancellationToken);
                    }
                }

                this.retried = true;
                return(ShouldRetryResult.RetryAfter(TimeSpan.FromSeconds(0)));
            }

            return(null);
        }
        /// <summary>
        /// Used by typed API only. Exceptions are allowed.
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns>Returns the partition key path</returns>
        internal virtual async Task <string[]> GetPartitionKeyPathTokensAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            CosmosContainerSettings containerSettings = await this.GetCachedContainerSettingsAsync(cancellationToken);

            if (containerSettings == null)
            {
                throw new ArgumentOutOfRangeException($"Container {this.LinkUri.ToString()} not found");
            }

            if (containerSettings.PartitionKey?.Paths == null)
            {
                throw new ArgumentOutOfRangeException($"Partition key not defined for container {this.LinkUri.ToString()}");
            }

            return(containerSettings.PartitionKeyPathTokens);
        }
        public override Task <CosmosResponseMessage> ReplaceStreamAsync(
            CosmosContainerSettings containerSettings,
            ContainerRequestOptions requestOptions = null,
            CancellationToken cancellationToken    = default(CancellationToken))
        {
            if (containerSettings == null)
            {
                throw new ArgumentNullException(nameof(containerSettings));
            }

            this.ClientContext.ValidateResource(containerSettings.Id);
            return(this.ReplaceStreamInternalAsync(
                       streamPayload: this.ClientContext.SettingsSerializer.ToStream(containerSettings),
                       requestOptions: requestOptions,
                       cancellationToken: cancellationToken));
        }
 /// <summary>
 /// Creates a container as an asynchronous operation in the Azure Cosmos service.
 /// </summary>
 /// <param name="containerSettings">The <see cref="CosmosContainerSettings"/> object.</param>
 /// <param name="throughput">(Optional) The throughput provisioned for a collection in measurement of Requests-per-Unit in the Azure Cosmos DB service.</param>
 /// <param name="requestOptions">(Optional) The options for the container request <see cref="CosmosRequestOptions"/></param>
 /// <param name="cancellationToken">(Optional) <see cref="CancellationToken"/> representing request cancellation.</param>
 /// <returns>A <see cref="Task"/> containing a <see cref="CosmosContainerResponse"/> which wraps a <see cref="CosmosContainerSettings"/> containing the read resource record.</returns>
 /// <exception cref="ArgumentNullException">If either <paramref name="containerSettings"/> 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="CosmosContainerSettings"/> with an id matching the id you supplied already existed.</description>
 ///     </item>
 /// </list>
 /// </exception>
 /// <example>
 ///
 /// <code language="c#">
 /// <![CDATA[
 /// CosmosContainerSettings settings = new CosmosContainerSettings()
 /// {
 ///     Id = Guid.NewGuid().ToString(),
 ///     IndexingPolicy = new IndexingPolicy()
 ///    {
 ///         Automatic = false,
 ///         IndexingMode = IndexingMode.Lazy,
 ///    };
 /// };
 ///
 /// CosmosContainerResponse response = this.cosmosDatabase.Containers.CreateContainerAsync(settings);
 /// ]]>
 /// </code>
 /// </example>
 public virtual Task <CosmosContainerResponse> CreateContainerAsync(
     CosmosContainerSettings containerSettings,
     int?throughput = null,
     CosmosRequestOptions requestOptions = null,
     CancellationToken cancellationToken = default(CancellationToken))
 {
     return(CosmosContainers.ProcessCollectionCreateAsync(
                containerSettings: containerSettings,
                database: this.database,
                throughput: throughput,
                responseCreator: response => this.database.Client.ResponseFactory.CreateContainerResponse(
                    response,
                    new CosmosContainer(this.database, containerSettings.Id)),
                requestOptions: requestOptions,
                cancellationToken: cancellationToken));
 }
 private Task <CosmosContainerResponse> ProcessAsync(
     CosmosContainerSettings containerSettings,
     OperationType operationType,
     CosmosContainerRequestOptions requestOptions = null,
     CancellationToken cancellationToken          = default(CancellationToken))
 {
     return(ExecUtils.ProcessResourceOperationAsync <CosmosContainerResponse>(
                this.Client,
                this.LinkUri,
                ResourceType.Collection,
                operationType,
                requestOptions,
                containerSettings?.GetResourceStream(),
                response =>
                this.Client.ResponseFactory.CreateContainerResponse(response, this),
                cancellationToken));
 }
        private static void AssertSerializedPayloads(CosmosContainerSettings settings, DocumentCollection documentCollection)
        {
            var jsonSettings = new JsonSerializerSettings
            {
                NullValueHandling = NullValueHandling.Ignore
            };

            // HAKC: Work-around till BE fixes defautls
            settings.ValidateRequiredProperties();

            string containerSerialized  = JsonConvert.SerializeObject(settings, jsonSettings);
            string collectionSerialized = CosmosContainerSettingsTests.SerializeDocumentCollection(documentCollection);

            JObject containerJObject  = JObject.Parse(containerSerialized);
            JObject collectionJObject = JObject.Parse(collectionSerialized);

            Assert.AreEqual(JsonConvert.SerializeObject(OrderProeprties(collectionJObject)), JsonConvert.SerializeObject(OrderProeprties(containerJObject)));
        }
        public void DefaultIncludesPopulated()
        {
            CosmosContainerSettings containerSettings = new CosmosContainerSettings("TestContainer", "/partitionKey");

            Assert.IsNotNull(containerSettings.IndexingPolicy);

            containerSettings.IndexingPolicy = new IndexingPolicy();
            Assert.AreEqual(0, containerSettings.IndexingPolicy.IncludedPaths.Count);

            // HAKC: Work-around till BE fixes defautls
            containerSettings.ValidateRequiredProperties();
            Assert.AreEqual(1, containerSettings.IndexingPolicy.IncludedPaths.Count);

            IncludedPath defaultEntry = containerSettings.IndexingPolicy.IncludedPaths[0];

            Assert.AreEqual(IndexingPolicy.DefaultPath, defaultEntry.Path);
            Assert.AreEqual(0, defaultEntry.Indexes.Count);
        }
        public void DefaultSameAsDocumentCollection()
        {
            CosmosContainerSettings containerSettings = new CosmosContainerSettings("TestContainer", "/partitionKey");

            DocumentCollection dc = new DocumentCollection()
            {
                Id           = "TestContainer",
                PartitionKey = new PartitionKeyDefinition()
                {
                    Paths = new Collection <string>()
                    {
                        "/partitionKey"
                    }
                }
            };

            CosmosContainerSettingsTests.AssertSerializedPayloads(containerSettings, dc);
        }
        public override Task <CosmosResponseMessage> CreateContainerAsStreamAsync(
            CosmosContainerSettings containerSettings,
            int?throughput = null,
            RequestOptions requestOptions       = null,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (containerSettings == null)
            {
                throw new ArgumentNullException(nameof(containerSettings));
            }

            this.ValidateContainerSettings(containerSettings);

            Stream streamPayload = this.ClientContext.SettingsSerializer.ToStream(containerSettings);

            return(this.CreateContainerAsStreamInternalAsync(streamPayload,
                                                             throughput,
                                                             requestOptions,
                                                             cancellationToken));
        }
        public override Task <ContainerResponse> CreateContainerIfNotExistsAsync(
            string id,
            string partitionKeyPath,
            int?throughput = null,
            RequestOptions requestOptions       = null,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (string.IsNullOrEmpty(id))
            {
                throw new ArgumentNullException(nameof(id));
            }

            if (string.IsNullOrEmpty(partitionKeyPath))
            {
                throw new ArgumentNullException(nameof(partitionKeyPath));
            }

            CosmosContainerSettings settings = new CosmosContainerSettings(id, partitionKeyPath);

            return(this.CreateContainerIfNotExistsAsync(settings, throughput, requestOptions, cancellationToken));
        }
예제 #12
0
        public override Task <ContainerResponse> CreateContainerAsync(
            CosmosContainerSettings containerSettings,
            int?requestUnitsPerSecond           = null,
            RequestOptions requestOptions       = null,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (containerSettings == null)
            {
                throw new ArgumentNullException(nameof(containerSettings));
            }

            this.ValidateContainerSettings(containerSettings);

            Task <CosmosResponseMessage> response = this.CreateContainerStreamInternalAsync(
                streamPayload: this.ClientContext.SettingsSerializer.ToStream(containerSettings),
                requestUnitsPerSecond: requestUnitsPerSecond,
                requestOptions: requestOptions,
                cancellationToken: cancellationToken);

            return(this.ClientContext.ResponseFactory.CreateContainerResponseAsync(this.GetContainer(containerSettings.Id), response));
        }
        public override Task <CosmosContainerResponse> CreateContainerAsync(
            CosmosContainerSettings containerSettings,
            int?throughput = null,
            CosmosRequestOptions requestOptions = null,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (containerSettings == null)
            {
                throw new ArgumentNullException(nameof(containerSettings));
            }

            this.ValidateContainerSettings(containerSettings);

            Task <CosmosResponseMessage> response = this.CreateContainerStreamAsync(
                streamPayload: CosmosResource.ToStream(containerSettings),
                throughput: throughput,
                requestOptions: requestOptions,
                cancellationToken: cancellationToken);

            return(this.client.ResponseFactory.CreateContainerResponse(this[containerSettings.Id], response));
        }
        private async Task <ResolutionResult> TryResolveServerPartitionByPartitionKeyRangeIdAsync(
            DocumentServiceRequest request,
            CosmosContainerSettings collection,
            CollectionRoutingMap routingMap,
            bool collectionCacheIsUpToDate,
            bool routingMapCacheIsUpToDate,
            bool forceRefreshPartitionAddresses,
            CancellationToken cancellationToken)
        {
            PartitionKeyRange partitionKeyRange = routingMap.TryGetRangeByPartitionKeyRangeId(request.PartitionKeyRangeIdentity.PartitionKeyRangeId);

            if (partitionKeyRange == null)
            {
                DefaultTrace.TraceInformation("Cannot resolve range '{0}'", request.PartitionKeyRangeIdentity.ToHeader());

                return(this.HandleRangeAddressResolutionFailure(request, collectionCacheIsUpToDate, routingMapCacheIsUpToDate, routingMap));
            }

            ServiceIdentity identity = routingMap.TryGetInfoByPartitionKeyRangeId(request.PartitionKeyRangeIdentity.PartitionKeyRangeId);

            PartitionAddressInformation addresses = await this.addressCache.TryGetAddressesAsync(
                request,
                new PartitionKeyRangeIdentity(collection.ResourceId, request.PartitionKeyRangeIdentity.PartitionKeyRangeId),
                identity,
                forceRefreshPartitionAddresses,
                cancellationToken);

            if (addresses == null)
            {
                DefaultTrace.TraceInformation("Cannot resolve addresses for range '{0}'", request.PartitionKeyRangeIdentity.ToHeader());

                return(this.HandleRangeAddressResolutionFailure(request, collectionCacheIsUpToDate, routingMapCacheIsUpToDate, routingMap));
            }

            return(new ResolutionResult(partitionKeyRange, addresses, identity));
        }
        private async Task <ShouldRetryResult> ShouldRetryInternalAsync(
            HttpStatusCode?statusCode,
            SubStatusCodes?subStatusCode,
            Task <ShouldRetryResult> chainedRetryTask,
            CancellationToken cancellationToken)
        {
            ShouldRetryResult shouldRetry = await chainedRetryTask;

            if (this.request == null)
            {
                DefaultTrace.TraceCritical("Cannot apply RenameCollectionAwareClientRetryPolicy as OnBeforeSendRequest has not been called and there is no DocumentServiceRequest context. Status Code {0} Sub Status Code {1}",
                                           statusCode.HasValue? statusCode.Value : 0,
                                           subStatusCode.HasValue ? subStatusCode.Value : SubStatusCodes.Unknown);
                return(shouldRetry);
            }

            if (!shouldRetry.ShouldRetry && !this.hasTriggered && statusCode.HasValue && subStatusCode.HasValue)
            {
                if (this.request.IsNameBased &&
                    statusCode.Value == HttpStatusCode.NotFound &&
                    subStatusCode.Value == SubStatusCodes.ReadSessionNotAvailable)
                {
                    // Clear the session token, because the collection name might be reused.
                    DefaultTrace.TraceWarning("Clear the the token for named base request {0}", request.ResourceAddress);

                    this.sessionContainer.ClearTokenByCollectionFullname(request.ResourceAddress);

                    this.hasTriggered = true;

                    string oldCollectionRid = request.RequestContext.ResolvedCollectionRid;

                    request.ForceNameCacheRefresh = true;
                    request.RequestContext.ResolvedCollectionRid = null;

                    try
                    {
                        CosmosContainerSettings collectionInfo = await this.collectionCache.ResolveCollectionAsync(request, cancellationToken);

                        if (collectionInfo == null)
                        {
                            DefaultTrace.TraceCritical("Can't recover from session unavailable exception because resolving collection name {0} returned null", request.ResourceAddress);
                        }
                        else if (!string.IsNullOrEmpty(oldCollectionRid) && !string.IsNullOrEmpty(collectionInfo.ResourceId))
                        {
                            if (!oldCollectionRid.Equals(collectionInfo.ResourceId))
                            {
                                return(ShouldRetryResult.RetryAfter(TimeSpan.Zero));
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        // When ResolveCollectionAsync throws an exception ignore it because it's an attempt to recover an existing
                        // error. When the recovery fails we return ShouldRetryResult.NoRetry and propaganate the original exception to the client

                        DefaultTrace.TraceCritical("Can't recover from session unavailable exception because resolving collection name {0} failed with {1}", request.ResourceAddress, e.ToString());
                    }
                }
            }

            return(shouldRetry);
        }
 /// <summary>
 /// Creates a container as an asynchronous operation in the Azure Cosmos service.
 /// </summary>
 /// <param name="containerSettings">The <see cref="CosmosContainerSettings"/> object.</param>
 /// <param name="requestUnitsPerSecond">(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="CosmosContainerSettings"/> containing the read resource record.</returns>
 /// <exception cref="ArgumentNullException">If either <paramref name="containerSettings"/> 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="CosmosContainerSettings"/> with an id matching the id you supplied already existed.</description>
 ///     </item>
 /// </list>
 /// </exception>
 /// <example>
 ///
 /// <code language="c#">
 /// <![CDATA[
 /// CosmosContainerSettings settings = new CosmosContainerSettings()
 /// {
 ///     Id = Guid.NewGuid().ToString(),
 ///     IndexingPolicy = new IndexingPolicy()
 ///    {
 ///         Automatic = false,
 ///         IndexingMode = IndexingMode.Lazy,
 ///    };
 /// };
 ///
 /// ContainerResponse response = this.cosmosDatabase.CreateContainerAsync(settings);
 /// ]]>
 /// </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 <ContainerResponse> CreateContainerAsync(
     CosmosContainerSettings containerSettings,
     int?requestUnitsPerSecond           = null,
     RequestOptions requestOptions       = null,
     CancellationToken cancellationToken = default(CancellationToken));
 /// <summary>
 /// Replace a <see cref="CosmosContainerSettings"/> from the Azure Cosmos service as an asynchronous operation.
 /// </summary>
 /// <param name="containerSettings">The <see cref="CosmosContainerSettings"/> object.</param>
 /// <param name="requestOptions">(Optional) The options for the container request <see cref="CosmosRequestOptions"/></param>
 /// <param name="cancellationToken">(Optional) <see cref="CancellationToken"/> representing request cancellation.</param>
 /// <returns>
 /// A <see cref="Task"/> containing a <see cref="CosmosContainerResponse"/> which wraps a <see cref="CosmosContainerSettings"/> containing the replace resource record.
 /// </returns>
 /// <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 Document are:
 /// <list type="table">
 ///     <listheader>
 ///         <term>StatusCode</term><description>Reason for exception</description>
 ///     </listheader>
 ///     <item>
 ///         <term>404</term><description>NotFound - This means the resource you tried to read did not exist.</description>
 ///     </item>
 ///     <item>
 ///         <term>429</term><description>TooManyRequests - This means you have exceeded the number of request units per second. Consult the DocumentClientException.RetryAfter value to see how long you should wait before retrying this operation.</description>
 ///     </item>
 /// </list>
 /// </exception>
 /// <example>
 /// Update the cosmosContainer to disable automatic indexing
 /// <code language="c#">
 /// <![CDATA[
 /// ContainerSettings setting = containerReadResponse;
 /// setting.IndexingPolicy.Automatic = false;
 /// CosmosContainerResponse response = cosmosContainer.ReplaceAsync(setting);
 /// ContainerSettings settings = response;
 /// ]]>
 /// </code>
 /// </example>
 public abstract Task <CosmosContainerResponse> ReplaceAsync(
     CosmosContainerSettings containerSettings,
     CosmosContainerRequestOptions requestOptions = null,
     CancellationToken cancellationToken          = default(CancellationToken));
        /// <summary>
        /// Resolves the endpoint of the partition for the given request
        /// </summary>
        /// <param name="request">Request for which the partition endpoint resolution is to be performed</param>
        /// <param name="forceRefreshPartitionAddresses">Force refresh the partition's endpoint</param>
        /// <param name="cancellationToken">Cancellation token</param>
        /// <returns>An instance of <see cref="ResolutionResult"/>.</returns>
        private async Task <ResolutionResult> ResolveAddressesAndIdentityAsync(
            DocumentServiceRequest request,
            bool forceRefreshPartitionAddresses,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (request.ServiceIdentity != null)
            {
                // 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)
                {
                    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;

            CosmosContainerSettings 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);
        }
        private async Task <ResolutionResult> TryResolveServerPartitionAsync(
            DocumentServiceRequest request,
            CosmosContainerSettings 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)
                {
                    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));
        }
        private PartitionKeyRange TryResolveServerPartitionByPartitionKey(
            DocumentServiceRequest request,
            string partitionKeyString,
            bool collectionCacheUptoDate,
            CosmosContainerSettings 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);
        }
 internal void ValidateContainerSettings(CosmosContainerSettings containerSettings)
 {
     containerSettings.ValidateRequiredProperties();
     this.client.DocumentClient.ValidateResource(containerSettings.Id);
 }
        /// <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
        /// </remarks>
        internal async Task <PartitionKeyInternal> GetNonePartitionKeyValue(CancellationToken cancellationToken = default(CancellationToken))
        {
            CosmosContainerSettings containerSettings = await this.GetCachedContainerSettingsAsync(cancellationToken);

            return(containerSettings.GetNoneValue());
        }
예제 #23
0
 /// <summary>
 /// Creates a container as an asynchronous operation in the Azure Cosmos service.
 /// </summary>
 /// <param name="containerSettings">The <see cref="CosmosContainerSettings"/> object.</param>
 /// <param name="throughput">(Optional) The throughput provisioned for a collection in measurement of Requests-per-Unit 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="CosmosResponseMessage"/> containing the created resource record.</returns>
 /// <seealso cref="DefineContainer(string, string)"/>
 public abstract Task <CosmosResponseMessage> CreateContainerAsStreamAsync(
     CosmosContainerSettings containerSettings,
     int?throughput = null,
     RequestOptions requestOptions       = null,
     CancellationToken cancellationToken = default(CancellationToken));
예제 #24
0
 /// <summary>
 /// Check if a container exists, and if it doesn't, create it.
 /// This will make a read operation, and if the container is not found it will do a create operation.
 /// </summary>
 /// <param name="containerSettings">The <see cref="CosmosContainerSettings"/> object.</param>
 /// <param name="throughput">(Optional) The throughput provisioned for a collection in measurement of Requests-per-Unit 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="CosmosContainerSettings"/> containing the read resource record.</returns>
 /// <exception cref="ArgumentNullException">If either <paramref name="containerSettings"/> 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="CosmosContainerSettings"/> with an id matching the id you supplied already existed.</description>
 ///     </item>
 /// </list>
 /// </exception>
 /// <example>
 ///
 /// <code language="c#">
 /// <![CDATA[
 /// CosmosContainerSettings settings = new CosmosContainerSettings()
 /// {
 ///     Id = Guid.NewGuid().ToString(),
 ///     IndexingPolicy = new IndexingPolicy()
 ///    {
 ///         Automatic = false,
 ///         IndexingMode = IndexingMode.Lazy,
 ///    };
 /// };
 ///
 /// ContainerResponse response = this.cosmosDatabase.Containers.CreateContainerIfNotExistsAsync(settings);
 /// ]]>
 /// </code>
 /// </example>
 public abstract Task <ContainerResponse> CreateContainerIfNotExistsAsync(
     CosmosContainerSettings containerSettings,
     int?throughput = null,
     RequestOptions requestOptions       = null,
     CancellationToken cancellationToken = default(CancellationToken));