private void ApplySessionToken(DocumentServiceRequest request) { if (request.Headers != null && !string.IsNullOrEmpty(request.Headers[HttpConstants.HttpHeaders.SessionToken])) { if (ReplicatedResourceClient.IsMasterResource(request.ResourceType)) { request.Headers.Remove(HttpConstants.HttpHeaders.SessionToken); } return; //User is explicitly controlling the session. } string requestConsistencyLevel = request.Headers[HttpConstants.HttpHeaders.ConsistencyLevel]; bool sessionConsistency = this.defaultConsistencyLevel == ConsistencyLevel.Session || (!string.IsNullOrEmpty(requestConsistencyLevel) && string.Equals(requestConsistencyLevel, ConsistencyLevel.Session.ToString(), StringComparison.OrdinalIgnoreCase)); if (!sessionConsistency || ReplicatedResourceClient.IsMasterResource(request.ResourceType)) { return; // Only apply the session token in case of session consistency and when resource is not a master resource } //Apply the ambient session. string sessionToken = this.sessionContainer.ResolveGlobalSessionToken(request); if (!string.IsNullOrEmpty(sessionToken)) { request.Headers[HttpConstants.HttpHeaders.SessionToken] = sessionToken; } }
public virtual async Task <DocumentServiceResponse> ProcessMessageAsync(DocumentServiceRequest request, CancellationToken cancellationToken = default(CancellationToken)) { GatewayStoreModel.ApplySessionToken( request, this.defaultConsistencyLevel, this.sessionContainer); DocumentServiceResponse response; try { Uri physicalAddress = GatewayStoreClient.IsFeedRequest(request.OperationType) ? this.GetFeedUri(request) : this.GetEntityUri(request); response = await this.gatewayStoreClient.InvokeAsync(request, request.ResourceType, physicalAddress, cancellationToken); } catch (DocumentClientException exception) { if ((!ReplicatedResourceClient.IsMasterResource(request.ResourceType)) && (exception.StatusCode == HttpStatusCode.PreconditionFailed || exception.StatusCode == HttpStatusCode.Conflict || (exception.StatusCode == HttpStatusCode.NotFound && exception.GetSubStatus() != SubStatusCodes.ReadSessionNotAvailable))) { this.CaptureSessionToken(exception.StatusCode, exception.GetSubStatus(), request, exception.Headers); } throw; } this.CaptureSessionToken(response.StatusCode, response.SubStatusCode, request, response.Headers); return(response); }
public virtual async Task <DocumentServiceResponse> ProcessMessageAsync(DocumentServiceRequest request, CancellationToken cancellationToken = default(CancellationToken)) { this.ApplySessionToken(request); DocumentServiceResponse response; try { response = await this.InvokeAsync(request, request.ResourceType, cancellationToken); } catch (DocumentClientException exception) { if ((!ReplicatedResourceClient.IsMasterResource(request.ResourceType)) && (exception.StatusCode == HttpStatusCode.PreconditionFailed || exception.StatusCode == HttpStatusCode.Conflict || (exception.StatusCode == HttpStatusCode.NotFound && exception.GetSubStatus() != SubStatusCodes.ReadSessionNotAvailable))) { this.CaptureSessionToken(request, exception.Headers); } throw; } this.CaptureSessionToken(request, response.Headers); return(response); }
private async Task CaptureSessionTokenAndHandleSplitAsync( HttpStatusCode?statusCode, SubStatusCodes subStatusCode, DocumentServiceRequest request, INameValueCollection responseHeaders) { // Exceptionless can try to capture session token from CompleteResponse if (request.IsValidStatusCodeForExceptionlessRetry((int)statusCode, subStatusCode)) { // Not capturing on master resources if (ReplicatedResourceClient.IsMasterResource(request.ResourceType)) { return; } // Only capturing on 409, 412, 404 && !1002 if (statusCode != HttpStatusCode.PreconditionFailed && statusCode != HttpStatusCode.Conflict && (statusCode != HttpStatusCode.NotFound || subStatusCode == SubStatusCodes.ReadSessionNotAvailable)) { return; } } if (request.ResourceType == ResourceType.Collection && request.OperationType == OperationType.Delete) { string resourceId; if (request.IsNameBased) { resourceId = responseHeaders[HttpConstants.HttpHeaders.OwnerId]; } else { resourceId = request.ResourceId; } this.sessionContainer.ClearTokenByResourceId(resourceId); } else { this.sessionContainer.SetSessionToken(request, responseHeaders); PartitionKeyRange detectedPartitionKeyRange = request.RequestContext.ResolvedPartitionKeyRange; string partitionKeyRangeInResponse = responseHeaders[HttpConstants.HttpHeaders.PartitionKeyRangeId]; if (detectedPartitionKeyRange != null && !string.IsNullOrEmpty(partitionKeyRangeInResponse) && !string.IsNullOrEmpty(request.RequestContext.ResolvedCollectionRid) && !partitionKeyRangeInResponse.Equals(detectedPartitionKeyRange.Id, StringComparison.OrdinalIgnoreCase)) { // The request ended up being on a different partition unknown to the client, so we better refresh the caches await this.partitionKeyRangeCache.TryGetPartitionKeyRangeByIdAsync( request.RequestContext.ResolvedCollectionRid, partitionKeyRangeInResponse, NoOpTrace.Singleton, forceRefresh : true); } } }
// DEVNOTE: This can be replace with ReplicatedResourceClient.IsMasterOperation on next Direct sync internal static bool IsMasterOperation( ResourceType resourceType, OperationType operationType) { // Stored procedures, trigger, and user defined functions CRUD operations are done on // master so they do not require the session token. Stored procedures execute is not a master operation return(ReplicatedResourceClient.IsMasterResource(resourceType) || GatewayStoreModel.IsStoredProcedureCrudOperation(resourceType, operationType) || resourceType == ResourceType.Trigger || resourceType == ResourceType.UserDefinedFunction || operationType == OperationType.QueryPlan); }
private void CaptureSessionToken( HttpStatusCode?statusCode, SubStatusCodes subStatusCode, DocumentServiceRequest request, INameValueCollection responseHeaders) { // Exceptionless can try to capture session token from CompleteResponse if (request.IsValidStatusCodeForExceptionlessRetry((int)statusCode, subStatusCode)) { // Not capturing on master resources if (ReplicatedResourceClient.IsMasterResource(request.ResourceType)) { return; } // Only capturing on 409, 412, 404 && !1002 if (statusCode != HttpStatusCode.PreconditionFailed && statusCode != HttpStatusCode.Conflict && (statusCode != HttpStatusCode.NotFound || subStatusCode == SubStatusCodes.ReadSessionNotAvailable)) { return; } } if (request.ResourceType == ResourceType.Collection && request.OperationType == OperationType.Delete) { string resourceId; if (request.IsNameBased) { resourceId = responseHeaders[HttpConstants.HttpHeaders.OwnerId]; } else { resourceId = request.ResourceId; } this.sessionContainer.ClearTokenByResourceId(resourceId); } else { this.sessionContainer.SetSessionToken(request, responseHeaders); } }
private static bool ShouldUpdateSessionToken( DocumentServiceRequest request, INameValueCollection responseHeaders, out ResourceId resourceId, out string collectionName) { resourceId = null; string ownerFullName = responseHeaders[HttpConstants.HttpHeaders.OwnerFullName]; if (string.IsNullOrEmpty(ownerFullName)) { ownerFullName = request.ResourceAddress; } collectionName = PathsHelper.GetCollectionPath(ownerFullName); string resourceIdString; if (request.IsNameBased) { resourceIdString = responseHeaders[HttpConstants.HttpHeaders.OwnerId]; if (string.IsNullOrEmpty(resourceIdString)) { resourceIdString = request.ResourceId; } } else { resourceIdString = request.ResourceId; } if (!string.IsNullOrEmpty(resourceIdString)) { resourceId = ResourceId.Parse(resourceIdString); if (resourceId.DocumentCollection != 0 && collectionName != null && !ReplicatedResourceClient.IsReadingFromMaster(request.ResourceType, request.OperationType)) { return(true); } } return(false); }
private void HandleUnsuccessfulStoreResponse(DocumentServiceRequest request, HttpStatusCode?statusCode, SubStatusCodes subStatusCode, INameValueCollection responseHeaders) { if (request.IsNameBased && statusCode == HttpStatusCode.NotFound && subStatusCode == SubStatusCodes.ReadSessionNotAvailable && request.ClearSessionTokenOnSessionReadFailure) { // 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.ClearToken(null, request.ResourceAddress, responseHeaders); } else { if ((!ReplicatedResourceClient.IsMasterResource(request.ResourceType)) && (statusCode == HttpStatusCode.PreconditionFailed || statusCode == HttpStatusCode.Conflict || (statusCode == HttpStatusCode.NotFound && subStatusCode != SubStatusCodes.ReadSessionNotAvailable))) { this.CaptureSessionToken(request, responseHeaders); } } }
public virtual async Task <DocumentServiceResponse> ProcessMessageAsync(DocumentServiceRequest request, CancellationToken cancellationToken = default) { await GatewayStoreModel.ApplySessionTokenAsync( request, this.defaultConsistencyLevel, this.sessionContainer, this.partitionKeyRangeCache, this.clientCollectionCache, this.endpointManager); DocumentServiceResponse response; try { Uri physicalAddress = GatewayStoreClient.IsFeedRequest(request.OperationType) ? this.GetFeedUri(request) : this.GetEntityUri(request); // Collect region name only for document resources if (request.ResourceType.Equals(ResourceType.Document) && this.endpointManager.TryGetLocationForGatewayDiagnostics(request.RequestContext.LocationEndpointToRoute, out string regionName)) { request.RequestContext.RegionName = regionName; } response = await this.gatewayStoreClient.InvokeAsync(request, request.ResourceType, physicalAddress, cancellationToken); } catch (DocumentClientException exception) { if ((!ReplicatedResourceClient.IsMasterResource(request.ResourceType)) && (exception.StatusCode == HttpStatusCode.PreconditionFailed || exception.StatusCode == HttpStatusCode.Conflict || (exception.StatusCode == HttpStatusCode.NotFound && exception.GetSubStatus() != SubStatusCodes.ReadSessionNotAvailable))) { await this.CaptureSessionTokenAndHandleSplitAsync(exception.StatusCode, exception.GetSubStatus(), request, exception.Headers); } throw; } await this.CaptureSessionTokenAndHandleSplitAsync(response.StatusCode, response.SubStatusCode, request, response.Headers); return(response); }
/// <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; 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); }
public async Task <INameValueCollection> CreateCommonHeadersAsync(FeedOptions feedOptions) { INameValueCollection requestHeaders = new DictionaryNameValueCollection(); Cosmos.ConsistencyLevel defaultConsistencyLevel = (Cosmos.ConsistencyLevel) await this.Client.GetDefaultConsistencyLevelAsync(); Cosmos.ConsistencyLevel?desiredConsistencyLevel = (Cosmos.ConsistencyLevel?) await this.Client.GetDesiredConsistencyLevelAsync(); if (!string.IsNullOrEmpty(feedOptions.SessionToken) && !ReplicatedResourceClient.IsReadingFromMaster(this.ResourceTypeEnum, OperationType.ReadFeed)) { if (defaultConsistencyLevel == Cosmos.ConsistencyLevel.Session || (desiredConsistencyLevel.HasValue && desiredConsistencyLevel.Value == Cosmos.ConsistencyLevel.Session)) { // Query across partitions is not supported today. Master resources (for e.g., database) // can span across partitions, whereas server resources (viz: collection, document and attachment) // don't span across partitions. Hence, session token returned by one partition should not be used // when quering resources from another partition. // Since master resources can span across partitions, don't send session token to the backend. // As master resources are sync replicated, we should always get consistent query result for master resources, // irrespective of the chosen replica. // For server resources, which don't span partitions, specify the session token // for correct replica to be chosen for servicing the query result. requestHeaders[HttpConstants.HttpHeaders.SessionToken] = feedOptions.SessionToken; } } requestHeaders[HttpConstants.HttpHeaders.Continuation] = feedOptions.RequestContinuationToken; requestHeaders[HttpConstants.HttpHeaders.IsQuery] = bool.TrueString; // Flow the pageSize only when we are not doing client eval if (feedOptions.MaxItemCount.HasValue) { requestHeaders[HttpConstants.HttpHeaders.PageSize] = feedOptions.MaxItemCount.ToString(); } requestHeaders[HttpConstants.HttpHeaders.EnableCrossPartitionQuery] = feedOptions.EnableCrossPartitionQuery.ToString(); if (feedOptions.MaxDegreeOfParallelism != 0) { requestHeaders[HttpConstants.HttpHeaders.ParallelizeCrossPartitionQuery] = bool.TrueString; } if (this.feedOptions.EnableScanInQuery != null) { requestHeaders[HttpConstants.HttpHeaders.EnableScanInQuery] = this.feedOptions.EnableScanInQuery.ToString(); } if (this.feedOptions.EmitVerboseTracesInQuery != null) { requestHeaders[HttpConstants.HttpHeaders.EmitVerboseTracesInQuery] = this.feedOptions.EmitVerboseTracesInQuery.ToString(); } if (this.feedOptions.EnableLowPrecisionOrderBy != null) { requestHeaders[HttpConstants.HttpHeaders.EnableLowPrecisionOrderBy] = this.feedOptions.EnableLowPrecisionOrderBy.ToString(); } if (!string.IsNullOrEmpty(this.feedOptions.FilterBySchemaResourceId)) { requestHeaders[HttpConstants.HttpHeaders.FilterBySchemaResourceId] = this.feedOptions.FilterBySchemaResourceId; } if (this.feedOptions.ResponseContinuationTokenLimitInKb != null) { requestHeaders[HttpConstants.HttpHeaders.ResponseContinuationTokenLimitInKB] = this.feedOptions.ResponseContinuationTokenLimitInKb.ToString(); } if (this.feedOptions.ConsistencyLevel.HasValue) { await this.Client.EnsureValidOverwriteAsync((Documents.ConsistencyLevel) feedOptions.ConsistencyLevel.Value); requestHeaders.Set(HttpConstants.HttpHeaders.ConsistencyLevel, this.feedOptions.ConsistencyLevel.Value.ToString()); } else if (desiredConsistencyLevel.HasValue) { requestHeaders.Set(HttpConstants.HttpHeaders.ConsistencyLevel, desiredConsistencyLevel.Value.ToString()); } if (this.feedOptions.EnumerationDirection.HasValue) { requestHeaders.Set(HttpConstants.HttpHeaders.EnumerationDirection, this.feedOptions.EnumerationDirection.Value.ToString()); } if (this.feedOptions.ReadFeedKeyType.HasValue) { requestHeaders.Set(HttpConstants.HttpHeaders.ReadFeedKeyType, this.feedOptions.ReadFeedKeyType.Value.ToString()); } if (this.feedOptions.StartId != null) { requestHeaders.Set(HttpConstants.HttpHeaders.StartId, this.feedOptions.StartId); } if (this.feedOptions.EndId != null) { requestHeaders.Set(HttpConstants.HttpHeaders.EndId, this.feedOptions.EndId); } if (this.feedOptions.StartEpk != null) { requestHeaders.Set(HttpConstants.HttpHeaders.StartEpk, this.feedOptions.StartEpk); } if (this.feedOptions.EndEpk != null) { requestHeaders.Set(HttpConstants.HttpHeaders.EndEpk, this.feedOptions.EndEpk); } if (this.feedOptions.PopulateQueryMetrics) { requestHeaders[HttpConstants.HttpHeaders.PopulateQueryMetrics] = bool.TrueString; } if (this.feedOptions.ForceQueryScan) { requestHeaders[HttpConstants.HttpHeaders.ForceQueryScan] = bool.TrueString; } if (this.feedOptions.MergeStaticId != null) { requestHeaders.Set(HttpConstants.HttpHeaders.MergeStaticId, this.feedOptions.MergeStaticId); } if (this.feedOptions.CosmosSerializationFormatOptions != null) { requestHeaders[HttpConstants.HttpHeaders.ContentSerializationFormat] = this.feedOptions.CosmosSerializationFormatOptions.ContentSerializationFormat; } else if (this.feedOptions.ContentSerializationFormat.HasValue) { requestHeaders[HttpConstants.HttpHeaders.ContentSerializationFormat] = this.feedOptions.ContentSerializationFormat.Value.ToString(); } return(requestHeaders); }