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);
 }
Example #7
0
        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);
            }
        }