Beispiel #1
0
        private async Task <IActionResult> HandlePostAsync(IDurableClient durableClient, IAsyncCollector <string> commandMonitor, HttpRequestMessage requestMessage, ILogger log)
        {
            IOrchestratorCommand command = null;

            try
            {
                command = await requestMessage.Content
                          .ReadAsJsonAsync <IOrchestratorCommand>()
                          .ConfigureAwait(false);

                if (command is null)
                {
                    return(new BadRequestResult());
                }

                command.Validate(throwOnValidationError: true);
            }
            catch (ValidationException exc)
            {
                log.LogError(exc, $"Command {command?.CommandId} failed validation");

                return(new BadRequestResult());
            }

            if (TryGetOrchestratorCommandHandler(command, out var commandHandler))
            {
                ICommandResult commandResult = null;

                try
                {
                    await commandAuditWriter
                    .AuditAsync(command)
                    .ConfigureAwait(false);

                    commandResult = await commandHandler
                                    .HandleAsync(command, durableClient)
                                    .ConfigureAwait(false);

                    commandResult ??= await durableClient
                    .GetCommandResultAsync(command)
                    .ConfigureAwait(false);

                    if (commandResult is null)
                    {
                        throw new NullReferenceException($"Unable to resolve result information for command {command.CommandId}");
                    }
                }
                catch (Exception exc)
                {
                    commandResult ??= command.CreateResult();
                    commandResult.Errors.Add(exc);

                    // there are some edge cases that affect our action result
                    // by returning specific result objects / status codes:

                    switch (exc)
                    {
                    case NotImplementedException notImplementedException:

                        // indicator something in the command's payload can't be processed

                        return(new BadRequestResult());

                    case NotSupportedException notSupportedException:

                        // indicator for a duplicate command

                        return(new System.Web.Http.ConflictResult());
                    }
                }
                finally
                {
                    if (commandResult.RuntimeStatus.IsFinal())
                    {
                        await commandAuditWriter
                        .AuditAsync(command, commandResult)
                        .ConfigureAwait(false);
                    }
                    else
                    {
                        await commandMonitor
                        .AddAsync(command.CommandId.ToString())
                        .ConfigureAwait(false);
                    }
                }

                return(CreateCommandResultResponse(commandResult));
            }
            else
            {
                return(new BadRequestResult());
            }

            bool TryGetOrchestratorCommandHandler(IOrchestratorCommand orchestratorCommand, out IOrchestratorCommandHandler orchestratorCommandHandler)
            {
                using var scope = httpContextAccessor.HttpContext.RequestServices.CreateScope();

                orchestratorCommandHandler = scope.ServiceProvider
                                             .GetServices <IOrchestratorCommandHandler>()
                                             .SingleOrDefault(handler => handler.CanHandle(orchestratorCommand));

                return(!(orchestratorCommandHandler is null));
            }
        }
Beispiel #2
0
        public async Task RunCommandMonitorQueue(
            [QueueTrigger(CommandMonitorQueue)] CloudQueueMessage commandMessage,
            [Queue(CommandMonitorQueue)] CloudQueue commandMonitor,
            [DurableClient] IDurableClient durableClient,
            ILogger log)
        {
            if (commandMessage is null)
            {
                throw new ArgumentNullException(nameof(commandMessage));
            }

            if (commandMonitor is null)
            {
                throw new ArgumentNullException(nameof(commandMonitor));
            }

            if (durableClient is null)
            {
                throw new ArgumentNullException(nameof(durableClient));
            }

            log ??= NullLogger.Instance;

            // there is no error handler on purpose - this way we can leverage
            // the function runtime capabilities for poisened message queues
            // and don't need to handle this on our own.

            if (Guid.TryParse(commandMessage.AsString, out var commandId))
            {
                var command = await durableClient
                              .GetCommandAsync(commandId)
                              .ConfigureAwait(false);

                if (command is null)
                {
                    // we could find a command based on the enqueued command id - warn and forget

                    log.LogWarning($"Monitoring command failed: Could not find command {commandId}");
                }
                else
                {
                    var commandResult = await durableClient
                                        .GetCommandResultAsync(commandId)
                                        .ConfigureAwait(false);

                    await commandAuditWriter
                    .AuditAsync(command, commandResult)
                    .ConfigureAwait(false);

                    if (!(commandResult?.RuntimeStatus.IsFinal() ?? false))
                    {
                        // the command result is still not in a final state - as we want to monitor the command until it is done,
                        // we are going to re-enqueue the command ID with a visibility offset to delay the next result lookup.

                        await commandMonitor
                        .AddMessageAsync(new CloudQueueMessage(commandId.ToString()), null, TimeSpan.FromSeconds(10), null, null)
                        .ConfigureAwait(false);
                    }
                }
            }
            else
            {
                // we expect that the queue message is a valid guid (command ID) - warn and forget

                log.LogWarning($"Monitoring command failed: Invalid command ID ({commandMessage.AsString})");
            }
        }