public async Task EnqueueAsync(BlobTriggerMessage message, CancellationToken cancellationToken) { string contents = JsonConvert.SerializeObject(message, JsonSerialization.Settings); await _queue.AddMessageAndCreateIfNotExistsAsync(_queue.CreateMessage(contents), cancellationToken); _watcher.Notify(_queue.Name); }
public async Task <(string QueueName, string MessageId)> EnqueueAsync(BlobTriggerMessage message, CancellationToken cancellationToken) { string contents = JsonConvert.SerializeObject(message, JsonSerialization.Settings); var receipt = await _queue.AddMessageAndCreateIfNotExistsAsync(contents, cancellationToken).ConfigureAwait(false); _watcher.Notify(_queue.Name); return(QueueName : _queue.Name, MessageId : receipt.MessageId); }
public async Task <FunctionResult> ExecuteAsync(IStorageQueueMessage value, CancellationToken cancellationToken) { BlobTriggerMessage message = JsonConvert.DeserializeObject <BlobTriggerMessage>(value.AsString, JsonSerialization.Settings); if (message == null) { throw new InvalidOperationException("Invalid blob trigger message."); } string functionId = message.FunctionId; if (functionId == null) { throw new InvalidOperationException("Invalid function ID."); } // Ensure that the function ID is still valid. Otherwise, ignore this message. FunctionResult successResult = new FunctionResult(true); BlobQueueRegistration registration; if (!_registrations.TryGetValue(functionId, out registration)) { return(successResult); } IStorageBlobContainer container = registration.BlobClient.GetContainerReference(message.ContainerName); string blobName = message.BlobName; IStorageBlob blob; switch (message.BlobType) { case StorageBlobType.PageBlob: blob = container.GetPageBlobReference(blobName); break; case StorageBlobType.BlockBlob: default: blob = container.GetBlockBlobReference(blobName); break; } // Ensure the blob still exists with the same ETag. string possibleETag = await _eTagReader.GetETagAsync(blob, cancellationToken); if (possibleETag == null) { // If the blob no longer exists, just ignore this message. return(successResult); } // If the blob still exists but the ETag is different, delete the message but do a fast path notification. if (!String.Equals(message.ETag, possibleETag, StringComparison.Ordinal)) { _blobWrittenWatcher.Notify(blob); return(successResult); } //// If the blob still exists and its ETag is still valid, execute. //// Note: it's possible the blob could change/be deleted between now and when the function executes. Guid?parentId = await _causalityReader.GetWriterAsync(blob, cancellationToken); TriggeredFunctionData input = new TriggeredFunctionData { ParentId = parentId, TriggerValue = blob }; return(await registration.Executor.TryExecuteAsync(input, cancellationToken)); }
public void ExecuteAsync_IfMessageIsFunctionIdIsRegistered_GetsETag(BlobType expectedBlobType) { // Arrange string expectedContainerName = "container"; string expectedBlobName = TestBlobName; string functionId = "FunctionId"; Mock<IBlobETagReader> mock = new Mock<IBlobETagReader>(MockBehavior.Strict); mock.Setup(r => r.GetETagAsync(It.Is<IStorageBlob>(b => b.BlobType == (StorageBlobType)expectedBlobType && b.Name == expectedBlobName && b.Container.Name == expectedContainerName), It.IsAny<CancellationToken>())) .Returns(Task.FromResult("ETag")) .Verifiable(); IBlobETagReader eTagReader = mock.Object; BlobQueueTriggerExecutor product = CreateProductUnderTest(eTagReader); BlobQueueRegistration registration = new BlobQueueRegistration { BlobClient = CreateClient(), Executor = CreateDummyTriggeredFunctionExecutor() }; product.Register(functionId, registration); BlobTriggerMessage triggerMessage = new BlobTriggerMessage { FunctionId = functionId, BlobType = (StorageBlobType)expectedBlobType, ContainerName = expectedContainerName, BlobName = expectedBlobName, ETag = "OriginalETag" }; IStorageQueueMessage message = CreateMessage(triggerMessage); // Act Task task = product.ExecuteAsync(message, CancellationToken.None); // Assert task.WaitUntilCompleted(); mock.Verify(); }
private static IStorageQueueMessage CreateMessage(BlobTriggerMessage triggerMessage) { return CreateMessage(JsonConvert.SerializeObject(triggerMessage)); }
private static IStorageQueueMessage CreateMessage(string functionId, string eTag) { BlobTriggerMessage triggerMessage = new BlobTriggerMessage { FunctionId = functionId, BlobType = StorageBlobType.BlockBlob, ContainerName = "container", BlobName = TestBlobName, ETag = eTag }; return CreateMessage(triggerMessage); }
public async Task <FunctionResult> ExecuteAsync(IStorageBlob value, CancellationToken cancellationToken) { // Avoid unnecessary network calls for non-matches. First, check to see if the blob matches this trigger. IReadOnlyDictionary <string, object> bindingData = _input.CreateBindingData(value.ToBlobPath()); if (bindingData == null) { // Blob is not a match for this trigger. return(new FunctionResult(true)); } // Next, check to see if the blob currently exists (and, if so, what the current ETag is). string possibleETag = await _eTagReader.GetETagAsync(value, cancellationToken); if (possibleETag == null) { // If the blob doesn't exist and have an ETag, don't trigger on it. return(new FunctionResult(true)); } IStorageBlockBlob receiptBlob = _receiptManager.CreateReference(_hostId, _functionId, value.Container.Name, value.Name, possibleETag); // Check for the completed receipt. If it's already there, noop. BlobReceipt unleasedReceipt = await _receiptManager.TryReadAsync(receiptBlob, cancellationToken); if (unleasedReceipt != null && unleasedReceipt.IsCompleted) { return(new FunctionResult(true)); } else if (unleasedReceipt == null) { // Try to create (if not exists) an incomplete receipt. if (!await _receiptManager.TryCreateAsync(receiptBlob, cancellationToken)) { // Someone else just created the receipt; wait to try to trigger until later. // Alternatively, we could just ignore the return result and see who wins the race to acquire the // lease. return(new FunctionResult(false)); } } string leaseId = await _receiptManager.TryAcquireLeaseAsync(receiptBlob, cancellationToken); if (leaseId == null) { // If someone else owns the lease and just took over this receipt or deleted it; // wait to try to trigger until later. return(new FunctionResult(false)); } ExceptionDispatchInfo exceptionInfo; try { // Check again for the completed receipt. If it's already there, noop. BlobReceipt receipt = await _receiptManager.TryReadAsync(receiptBlob, cancellationToken); Debug.Assert(receipt != null); // We have a (30 second) lease on the blob; it should never disappear on us. if (receipt.IsCompleted) { await _receiptManager.ReleaseLeaseAsync(receiptBlob, leaseId, cancellationToken); return(new FunctionResult(true)); } // We've successfully acquired a lease to enqueue the message for this blob trigger. Enqueue the message, // complete the receipt and release the lease. // Enqueue a message: function ID + blob path + ETag BlobTriggerMessage message = new BlobTriggerMessage { FunctionId = _functionId, BlobType = value.BlobType, ContainerName = value.Container.Name, BlobName = value.Name, ETag = possibleETag }; await _queueWriter.EnqueueAsync(message, cancellationToken); // Complete the receipt & release the lease await _receiptManager.MarkCompletedAsync(receiptBlob, leaseId, cancellationToken); await _receiptManager.ReleaseLeaseAsync(receiptBlob, leaseId, cancellationToken); return(new FunctionResult(true)); } catch (Exception exception) { exceptionInfo = ExceptionDispatchInfo.Capture(exception); } Debug.Assert(exceptionInfo != null); await _receiptManager.ReleaseLeaseAsync(receiptBlob, leaseId, cancellationToken); exceptionInfo.Throw(); FunctionResult result = new FunctionResult(exceptionInfo.SourceException); return(result); // Keep the compiler happy; we'll never get here. }
public async Task <FunctionResult> ExecuteAsync(QueueMessage value, CancellationToken cancellationToken) { BlobTriggerMessage message = JsonConvert.DeserializeObject <BlobTriggerMessage>(value.MessageText, JsonSerialization.Settings); if (message == null) { throw new InvalidOperationException("Invalid blob trigger message."); } string functionId = message.FunctionId; if (functionId == null) { throw new InvalidOperationException("Invalid function ID."); } // Ensure that the function ID is still valid. Otherwise, ignore this message. FunctionResult successResult = new FunctionResult(true); if (!_registrations.TryGetValue(functionId, out BlobQueueRegistration registration)) { Logger.FunctionNotFound(_logger, message.BlobName, functionId, value.MessageId); return(successResult); } var container = registration.BlobServiceClient.GetBlobContainerClient(message.ContainerName); string blobName = message.BlobName; BlobBaseClient blob; BlobProperties blobProperties; try { (blob, blobProperties) = await container.GetBlobReferenceFromServerAsync(blobName, cancellationToken).ConfigureAwait(false); } catch (RequestFailedException exception) when(exception.IsNotFound() || exception.IsOk()) { // If the blob no longer exists, just ignore this message. Logger.BlobNotFound(_logger, blobName, value.MessageId); return(successResult); } // Ensure the blob still exists with the same ETag. string possibleETag = blobProperties.ETag.ToString(); // If the blob still exists but the ETag is different, delete the message but do a fast path notification. if (!string.Equals(message.ETag, possibleETag, StringComparison.Ordinal)) { _blobWrittenWatcher.Notify(new Extensions.Storage.Blobs.BlobWithContainer <BlobBaseClient>(container, blob)); return(successResult); } // If the blob still exists and its ETag is still valid, execute. // Note: it's possible the blob could change/be deleted between now and when the function executes. Guid?parentId = await _causalityReader.GetWriterAsync(blob, cancellationToken).ConfigureAwait(false); // Include the queue details here. IDictionary <string, string> details = PopulateTriggerDetails(value); if (blobProperties.CreatedOn != null) { details[BlobCreatedKey] = blobProperties.CreatedOn.ToString(Constants.DateTimeFormatString, CultureInfo.InvariantCulture); } if (blobProperties.LastModified != null) { details[BlobLastModifiedKey] = blobProperties.LastModified.ToString(Constants.DateTimeFormatString, CultureInfo.InvariantCulture); } TriggeredFunctionData input = new TriggeredFunctionData { ParentId = parentId, TriggerValue = blob, TriggerDetails = details }; return(await registration.Executor.TryExecuteAsync(input, cancellationToken).ConfigureAwait(false)); }
public async Task EnqueueAsync(BlobTriggerMessage message, CancellationToken cancellationToken) { string contents = JsonConvert.SerializeObject(message, JsonSerialization.Settings); await _queue.AddMessageAndCreateIfNotExistsAsync(_queue.CreateMessage(contents), cancellationToken); _watcher.Notify(_queue.Name); }
public async Task <FunctionResult> ExecuteAsync(BlobTriggerExecutorContext context, CancellationToken cancellationToken) { BlobBaseClient value = context.Blob.BlobClient; BlobTriggerSource triggerSource = context.TriggerSource; string pollId = context.PollId; // Avoid unnecessary network calls for non-matches. First, check to see if the blob matches this trigger. IReadOnlyDictionary <string, object> bindingData = _input.CreateBindingData(value.ToBlobPath()); if (bindingData == null) { string pattern = new BlobPath(_input.ContainerNamePattern, _input.BlobNamePattern).ToString(); Logger.BlobDoesNotMatchPattern(_logger, _functionDescriptor.LogName, value.Name, pattern, pollId, triggerSource); // Blob is not a match for this trigger. return(new FunctionResult(true)); } // Next, check to see if the blob currently exists (and, if so, what the current ETag is). BlobProperties blobProperties = await value.FetchPropertiesOrNullIfNotExistAsync(cancellationToken).ConfigureAwait(false); string possibleETag = null; if (blobProperties != null) { possibleETag = blobProperties.ETag.ToString(); } if (possibleETag == null) { Logger.BlobHasNoETag(_logger, _functionDescriptor.LogName, value.Name, pollId, triggerSource); // If the blob doesn't exist and have an ETag, don't trigger on it. return(new FunctionResult(true)); } var receiptBlob = _receiptManager.CreateReference(_hostId, _functionDescriptor.Id, value.BlobContainerName, value.Name, possibleETag); // Check for the completed receipt. If it's already there, noop. BlobReceipt unleasedReceipt = await _receiptManager.TryReadAsync(receiptBlob, cancellationToken).ConfigureAwait(false); if (unleasedReceipt != null && unleasedReceipt.IsCompleted) { Logger.BlobAlreadyProcessed(_logger, _functionDescriptor.LogName, value.Name, possibleETag, pollId, triggerSource); return(new FunctionResult(true)); } else if (unleasedReceipt == null) { // Try to create (if not exists) an incomplete receipt. if (!await _receiptManager.TryCreateAsync(receiptBlob, cancellationToken).ConfigureAwait(false)) { // Someone else just created the receipt; wait to try to trigger until later. // Alternatively, we could just ignore the return result and see who wins the race to acquire the // lease. return(new FunctionResult(false)); } } string leaseId = await _receiptManager.TryAcquireLeaseAsync(receiptBlob, cancellationToken).ConfigureAwait(false); if (leaseId == null) { // If someone else owns the lease and just took over this receipt or deleted it; // wait to try to trigger until later. return(new FunctionResult(false)); } try { // Check again for the completed receipt. If it's already there, noop. BlobReceipt receipt = await _receiptManager.TryReadAsync(receiptBlob, cancellationToken).ConfigureAwait(false); Debug.Assert(receipt != null); // We have a (30 second) lease on the blob; it should never disappear on us. if (receipt.IsCompleted) { Logger.BlobAlreadyProcessed(_logger, _functionDescriptor.LogName, value.Name, possibleETag, pollId, triggerSource); await _receiptManager.ReleaseLeaseAsync(receiptBlob, leaseId, cancellationToken).ConfigureAwait(false); return(new FunctionResult(true)); } // We've successfully acquired a lease to enqueue the message for this blob trigger. Enqueue the message, // complete the receipt and release the lease. // Enqueue a message: function ID + blob path + ETag BlobTriggerMessage message = new BlobTriggerMessage { FunctionId = _functionDescriptor.Id, BlobType = blobProperties.BlobType, ContainerName = value.BlobContainerName, BlobName = value.Name, ETag = possibleETag }; var(queueName, messageId) = await _queueWriter.EnqueueAsync(message, cancellationToken).ConfigureAwait(false); Logger.BlobMessageEnqueued(_logger, _functionDescriptor.LogName, value.Name, messageId, queueName, pollId, triggerSource); // Complete the receipt await _receiptManager.MarkCompletedAsync(receiptBlob, leaseId, cancellationToken).ConfigureAwait(false); return(new FunctionResult(true)); } finally { await _receiptManager.ReleaseLeaseAsync(receiptBlob, leaseId, cancellationToken).ConfigureAwait(false); } }