예제 #1
0
        public async Task OrchestrateAggregateLoad([OrchestrationTrigger] IDurableOrchestrationContext orchestrationContext, ILogger log)
        {
            await Task.WhenAll(
                orchestrationContext.CallSubOrchestratorAsync(nameof(OrchestrateAuthors), new { }),
                orchestrationContext.CallSubOrchestratorAsync(nameof(OrchestratePublishers), new { })
                );

            await Task.WhenAll(
                orchestrationContext.CallSubOrchestratorAsync(nameof(OrchestrateBooks), new { }));

            await Task.WhenAll(
                orchestrationContext.CallSubOrchestratorAsync(nameof(OrchestrateOrders), new { }),
                orchestrationContext.CallSubOrchestratorAsync(nameof(OrchestrateStoreInventory), new { })
                );

            await Task.WhenAll(
                orchestrationContext.CallSubOrchestratorAsync(nameof(OrchestrateCustomers), new { })
                );
        }
예제 #2
0
 public static Task DurableFunctionWithSubOrchestration(
     [OrchestrationTrigger] IDurableOrchestrationContext context)
 {
     return(context.CallSubOrchestratorAsync(nameof(SubOrchestration), null));
 }
예제 #3
0
        public static async Task RunOrchestration(
            [OrchestrationTrigger] IDurableOrchestrationContext functionContext,
            ILogger log)
        {
            if (functionContext is null)
            {
                throw new ArgumentNullException(nameof(functionContext));
            }

            var command       = functionContext.GetInput <ProviderProjectCreateCommand>();
            var commandResult = command.CreateResult();
            var commandLog    = functionContext.CreateReplaySafeLogger(log ?? NullLogger.Instance);

            using (log.BeginCommandScope(command))
            {
                try
                {
                    await functionContext
                    .EnsureAuthorizedAsync()
                    .ConfigureAwait(true);

                    await functionContext
                    .CallOperationAsync(nameof(ProjectCreateActivity), command.Payload)
                    .ConfigureAwait(true);

                    await functionContext
                    .CallActivityWithRetryAsync(nameof(ProjectInitializeActivity), command.Payload)
                    .ConfigureAwait(true);

                    await functionContext
                    .CallSubOrchestratorAsync(nameof(ProjectSyncOrchestration), command.Payload)
                    .ConfigureAwait(true);

                    commandResult.Result = new ProviderOutput {
                        Properties = new Dictionary <string, string>()
                    };
                }
                catch (Exception exc)
                {
                    commandResult ??= command.CreateResult();
                    commandResult.Errors.Add(exc);

                    throw exc.AsSerializable();
                }
                finally
                {
                    var commandException = commandResult.Errors?.ToException();

                    if (commandException is null)
                    {
                        functionContext.SetCustomStatus($"Command succeeded", commandLog);
                    }
                    else
                    {
                        functionContext.SetCustomStatus($"Command failed: {commandException.Message}", commandLog, commandException);
                    }

                    functionContext.SetOutput(commandResult);
                }
            }
        }
예제 #4
0
        public async Task Run(
            [OrchestrationTrigger] IDurableOrchestrationContext context,
            ILogger log)
        {
            if (!_featureOptions.EnableTimeOffSync)
            {
                return;
            }

            var teamModel = context.GetInput <TeamModel>();

            var weeks = context.CurrentUtcDateTime.Date
                        .Range(_options.PastWeeks, _options.FutureWeeks, _options.StartDayOfWeek);

            // give the wfm provider connector the opportunity to prepare the data for the sync if necessary
            await context.CallActivityAsync(nameof(PrepareSyncActivity), new PrepareSyncModel
            {
                Type               = SyncType.TimeOff,
                TeamId             = teamModel.TeamId,
                WfmId              = teamModel.WfmBuId,
                FirstWeekStartDate = weeks.Min(),
                LastWeekStartDate  = weeks.Max(),
                TimeZoneInfoId     = teamModel.TimeZoneInfoId
            });

            var weekModels = weeks
                             .Select(startDate => new WeekModel
            {
                StartDate = startDate,
                WfmBuId   = teamModel.WfmBuId,
                TeamId    = teamModel.TeamId
            });

            var weekTasks = weekModels
                            .Select(weekModel => context.CallSubOrchestratorAsync <bool>(nameof(TimeOffWeekOrchestrator), weekModel));

            var allTasks = Task.WhenAll(weekTasks);

            bool changesProcessed = false;

            try
            {
                var results = await allTasks;
                changesProcessed = results.Any(b => b);
            }
            finally
            {
                // always commit what we have successfully applied to Teams
                if (_options.DraftShiftsEnabled && changesProcessed)
                {
                    var shareModel = new ShareModel
                    {
                        TeamId    = teamModel.TeamId,
                        StartDate = weekModels.Min(w => w.StartDate),
                        EndDate   = weekModels.Max(w => w.StartDate).AddWeek()
                    };
                    await context.CallActivityAsync(nameof(ShareActivity), shareModel);
                }
            }

            if (allTasks.Status == TaskStatus.Faulted)
            {
                log.LogAggregateOrchestrationError(allTasks.Exception, teamModel, nameof(TimeOffOrchestrator));
            }
        }
        public async Task RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
        {
            var collectionPeriod = await context.CallActivityAsync <CollectionPeriod>(nameof(GetActiveCollectionPeriod), null);

            if (!context.IsReplaying)
            {
                _logger.LogInformation("[IncentivePaymentOrchestrator] Incentive Payment process started for collection period {collectionPeriod}", collectionPeriod);
            }

            context.SetCustomStatus("SettingActivePeriodToInProgress");
            await context.CallActivityAsync(nameof(SetActivePeriodToInProgress), null);

            context.SetCustomStatus("GettingPayableLegalEntities");
            var payableLegalEntities = await context.CallActivityAsync <List <PayableLegalEntityDto> >(nameof(GetPayableLegalEntities), collectionPeriod);

            context.SetCustomStatus("CalculatingPayments");
            var calculatePaymentTasks = new List <Task>();

            foreach (var legalEntity in payableLegalEntities)
            {
                var calculatePaymentTask = context.CallSubOrchestratorAsync(nameof(CalculatePaymentsForAccountLegalEntityOrchestrator), new AccountLegalEntityCollectionPeriod {
                    AccountId = legalEntity.AccountId, AccountLegalEntityId = legalEntity.AccountLegalEntityId, CollectionPeriod = collectionPeriod
                });
                calculatePaymentTasks.Add(calculatePaymentTask);
            }

            await Task.WhenAll(calculatePaymentTasks);

            context.SetCustomStatus("GettingUnsentClawbackLegalEntities");
            var clawbackLegalEntities = await context.CallActivityAsync <List <ClawbackLegalEntityDto> >(nameof(GetUnsentClawbacks), collectionPeriod);

            if (!context.IsReplaying)
            {
                context.SetCustomStatus("LoggingUnsentClawbacks");
                foreach (var legalEntity in clawbackLegalEntities)
                {
                    _logger.LogInformation($"Unsent clawback for AccountId : {legalEntity.AccountId}, AccountLegalEntityId : {legalEntity.AccountLegalEntityId}, Collection Year : {collectionPeriod.Year}, Period : {collectionPeriod.Period}");
                }
            }

            context.SetCustomStatus("WaitingForPaymentApproval");

            var paymentsApproved = await context.WaitForExternalEvent <bool>("PaymentsApproved");

            if (!paymentsApproved)
            {
                context.SetCustomStatus("PaymentsRejected");
                _logger.LogInformation("[IncentivePaymentOrchestrator] Calculated payments for collection period {collectionPeriod} have been rejected", collectionPeriod);
                return;
            }

            context.SetCustomStatus("SendingPayments");
            _logger.LogInformation("[IncentivePaymentOrchestrator] Calculated payments for collection period {collectionPeriod} have been approved", collectionPeriod);

            var sendPaymentTasks = new List <Task>();

            foreach (var legalEntity in payableLegalEntities)
            {
                var sendPaymentTask = context.CallActivityAsync(nameof(SendPaymentRequestsForAccountLegalEntity), new AccountLegalEntityCollectionPeriod {
                    AccountId = legalEntity.AccountId, AccountLegalEntityId = legalEntity.AccountLegalEntityId, CollectionPeriod = collectionPeriod
                });
                sendPaymentTasks.Add(sendPaymentTask);
            }
            await Task.WhenAll(sendPaymentTasks);

            context.SetCustomStatus("PaymentsSent");

            context.SetCustomStatus("SendingClawbacks");
            var sendClawbackTasks = new List <Task>();

            foreach (var legalEntity in clawbackLegalEntities)
            {
                var sendClawbackTask = context.CallActivityAsync(nameof(SendClawbacksForAccountLegalEntity), new AccountLegalEntityCollectionPeriod {
                    AccountId = legalEntity.AccountId, AccountLegalEntityId = legalEntity.AccountLegalEntityId, CollectionPeriod = collectionPeriod
                });
                sendClawbackTasks.Add(sendClawbackTask);
            }
            await Task.WhenAll(sendClawbackTasks);

            context.SetCustomStatus("ClawbacksSent");

            context.SetCustomStatus("CompletingPaymentProcessing");
            await context.CallActivityAsync(nameof(CompletePaymentProcess), new CompletePaymentProcessInput { CompletionDateTime = DateTime.UtcNow, CollectionPeriod = collectionPeriod });

            context.SetCustomStatus("PaymentProcessingCompleted");

            _logger.LogInformation("[IncentivePaymentOrchestrator] Incentive Payment process completed for collection period {collectionPeriod}", collectionPeriod);
        }
예제 #6
0
        public async Task <SignerResult[]> MainOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext context)
        {
            WorkflowStartModel request = context.GetInput <WorkflowStartModel>();

            // Database update, has to be done in an activity
            await context.CallActivityAsync(nameof(MarkWorkflowStarted), request.RequestId);

            // there is also a variant that supports retries

            // Prepare email send tasks
            string[] signerEmails   = request.SignerEmails;
            var      emailSendTasks = new List <Task>(signerEmails.Length);

            for (int i = 0; i < signerEmails.Length; i++)
            {
                Task sendTask = context.CallActivityAsync(nameof(SendPleaseSignEmail), new EmailSendParameters
                {
                    To           = signerEmails[i],
                    Subject      = request.Subject,
                    Message      = request.Message,
                    RequestId    = request.RequestId,
                    DocumentName = request.DocumentName
                });
                emailSendTasks.Add(sendTask);
            }

            // Fan out to send emails, activities triggered in parallel
            await Task.WhenAll(emailSendTasks);

            // Prepare parallel signing tasks
            var signingTasks = new List <Task <SignerResult> >(signerEmails.Length);

            for (int i = 0; i < signerEmails.Length; i++)
            {
                Task <SignerResult> signingTask = context.CallSubOrchestratorAsync <SignerResult>(nameof(WaitForSignature.WaitForSign), new WaitForSignParameters
                {
                    SignerEmail = signerEmails[i],
                    RequestId   = request.RequestId
                });
                signingTasks.Add(signingTask);
            }

            // Wait for result from each signer, another fan out
            SignerResult[] results = await Task.WhenAll(signingTasks);

            // Create signed document if everyone signed
            if (results.All(r => r.Result == SigningDecision.Signed))
            {
                await context.CallActivityAsync(nameof(CreateSignedDocument), new CreateSignedDocumentParameters
                {
                    RequestId = request.RequestId,
                    Results   = results
                });
            }

            // Send completion email to all signers
            var completionEmailSendTasks = new List <Task>(signerEmails.Length);

            for (int i = 0; i < signerEmails.Length; i++)
            {
                Task sendTask = context.CallActivityAsync(nameof(SendCompletionEmail), new SendCompletionEmailParameters
                {
                    RequestId    = request.RequestId,
                    To           = signerEmails[i],
                    DocumentName = request.DocumentName
                });
                completionEmailSendTasks.Add(sendTask);
            }

            // Fan out to send completion emails
            await Task.WhenAll(completionEmailSendTasks);

            // Finally, mark the workflow completed in the DB
            await context.CallActivityAsync(nameof(MarkWorkflowCompleted), request.RequestId);

            return(results);
        }
예제 #7
0
        public static async Task <string> MainOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext context,
            ILogger log)
        {
            DateTime startTime = context.CurrentUtcDateTime;

            if (!context.IsReplaying)
            {
                log.LogInformation($"Main orchestrator (v{AzTwitterSarVersion.get()}), start time {startTime}. Call activity: GetTweets");
            }

            string lastTweetId = context.GetInput <string>();

            // 1) Get tweets from Twitter API
            List <TweetProcessingData> tweetData = await context.CallActivityAsync <List <TweetProcessingData> >(
                "A_GetTweets", lastTweetId);

            // The most likely case is that there are NO new tweets, so we provide
            // the short circuit which skips all the processing in this block, and
            // thus avoids a lot of replaying by the durable function mechanism.
            if (tweetData.Count > 0)
            {
                if (!context.IsReplaying)
                {
                    log.LogInformation($"Got {tweetData.Count} new tweets; enter processing sub-orchestration.");
                }

                // 2) Process tweets one by one to find interesting ones.
                var parallelScoringTasks = new List <Task <TweetProcessingData> >();
                foreach (var tpd in tweetData) // processes in order
                {
                    if (!context.IsReplaying)
                    {
                        log.LogInformation($"Call sub-orchestration: P_ProcessTweet for tweet: {tpd.IdStr}");
                    }
                    Task <TweetProcessingData> processTask = context.CallSubOrchestratorAsync <TweetProcessingData>(
                        "P_ProcessTweet", tpd);
                    parallelScoringTasks.Add(processTask);
                }
                await Task.WhenAll(parallelScoringTasks);

                // Sort the list of analyzed tweets by ascending id (chronologically).
                List <TweetProcessingData> logList = (from pt in parallelScoringTasks
                                                      orderby Int64.Parse(pt.Result.IdStr)
                                                      select pt.Result).ToList();

                // Find the tweets that shall be published, chronological order.
                List <TweetProcessingData> publishList = (
                    from tpd in logList
                    where tpd.ShallBePublished
                    orderby Int64.Parse(tpd.IdStr)
                    select tpd).ToList();

                // Parallel section for postprocessing tasks.
                {
                    if (!context.IsReplaying)
                    {
                        log.LogInformation($"Publishing {publishList.Count} tweets; logging {logList.Count} tweets.");
                    }

                    List <Task <int> > parallelPostprocessingTasks = new List <Task <int> >();
                    // We know there is something in the log list, but publishing we
                    // trigger only if there is something to do for this activity.
                    if (publishList.Count > 0)
                    {
                        parallelPostprocessingTasks.Add(context.CallActivityAsync <int>("A_PublishTweets", publishList));
                    }
                    parallelPostprocessingTasks.Add(context.CallActivityAsync <int>("A_LogTweets", logList));
                    await Task.WhenAll(parallelPostprocessingTasks);
                }

                // We know there has been >= 1 tweet, so we update the last seen id,
                // which is passed to the next call.
                lastTweetId = logList[logList.Count - 1].IdStr;
            }
            else
            {
                if (!context.IsReplaying)
                {
                    log.LogInformation($"Got no new tweets.");
                }
            }

            DateTime currentTime  = context.CurrentUtcDateTime;
            int      delaySeconds = GetDelaySeconds(context, log, startTime, currentTime);

            if (!context.IsReplaying)
            {
                log.LogInformation($"Determined delay: {delaySeconds} seconds after current time {currentTime}.");
            }

            if (delaySeconds > 0)
            {
                DateTime nextTime = currentTime.AddSeconds(delaySeconds);
                if (!context.IsReplaying)
                {
                    log.LogInformation($"Setting wakeup time: {nextTime}.");
                }
                await context.CreateTimer(nextTime, CancellationToken.None);

                context.ContinueAsNew(lastTweetId);
            }

            return(lastTweetId);
        }
        public async Task RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log)
        {
            var syncJob = context.GetInput <SyncJob>();

            _log.SyncJobProperties = syncJob.ToDictionary();
            var runId = syncJob.RunId.GetValueOrDefault(context.NewGuid());

            _graphGroup.RunId = runId;
            List <AzureADUser> distinctUsers = null;

            if (!context.IsReplaying)
            {
                _ = _log.LogMessageAsync(new LogMessage {
                    Message = $"{nameof(OrchestratorFunction)} function started", RunId = runId
                });
            }
            var sourceGroups = await context.CallActivityAsync <AzureADGroup[]>(nameof(SourceGroupsReaderFunction), new SourceGroupsReaderRequest { SyncJob = syncJob, RunId = runId });

            try
            {
                if (sourceGroups.Length == 0)
                {
                    if (!context.IsReplaying)
                    {
                        _ = _log.LogMessageAsync(new LogMessage {
                            RunId = runId, Message = $"None of the source groups in {syncJob.Query} were valid guids. Marking job as errored."
                        });
                    }
                    await context.CallActivityAsync(nameof(EmailSenderFunction), new EmailSenderRequest { SyncJob = syncJob, RunId = runId });
                }
                else
                {
                    // Run multiple source group processing flows in parallel
                    var processingTasks = new List <Task <List <AzureADUser> > >();
                    foreach (var sourceGroup in sourceGroups)
                    {
                        var processTask = context.CallSubOrchestratorAsync <List <AzureADUser> >(nameof(SubOrchestratorFunction), new SecurityGroupRequest {
                            SyncJob = syncJob, SourceGroup = sourceGroup, RunId = runId
                        });
                        processingTasks.Add(processTask);
                    }
                    var tasks = await Task.WhenAll(processingTasks);

                    var users = new List <AzureADUser>();
                    foreach (var task in tasks)
                    {
                        users.AddRange(task);
                    }

                    distinctUsers = users.GroupBy(user => user.ObjectId).Select(userGrp => userGrp.First()).ToList();

                    if (!context.IsReplaying)
                    {
                        _ = _log.LogMessageAsync(new LogMessage {
                            RunId   = runId,
                            Message =
                                $"Found {users.Count - distinctUsers.Count} duplicate user(s). Read {distinctUsers.Count} users from source groups {syncJob.Query} to be synced into the destination group {syncJob.TargetOfficeGroupId}."
                        });
                    }
                }
            }
            catch (Exception ex)
            {
                _ = _log.LogMessageAsync(new LogMessage {
                    Message = "Caught unexpected exception, marking sync job as errored. Exception:\n" + ex, RunId = runId
                });
                distinctUsers = null;

                // make sure this gets thrown to where App Insights will handle it
                throw;
            }
            finally
            {
                if (distinctUsers != null)
                {
                    await context.CallActivityAsync(nameof(UsersSenderFunction), new UsersSenderRequest { SyncJob = syncJob, RunId = runId, Users = distinctUsers });
                }
                else
                {
                    syncJob.Enabled = false;
                    await context.CallActivityAsync(nameof(JobStatusUpdaterFunction), new JobStatusUpdaterRequest { SyncJob = syncJob, Status = SyncStatus.Error });
                }
            }

            if (!context.IsReplaying)
            {
                _ = _log.LogMessageAsync(new LogMessage {
                    Message = $"{nameof(OrchestratorFunction)} function completed", RunId = runId
                });
            }
        }
예제 #9
0
        public static async Task <ProcessImageResult> ProcessImage(
            [OrchestrationTrigger] IDurableOrchestrationContext context)
        {
            var image = context.GetInput <string>();

            // Sub Orchestrator with fan-out / fan-in pattern
            // It gets the image tags and Update index info for each tag in parallel
            var indexTagResults = await context.CallSubOrchestratorAsync <List <string> >(
                Constants.FunctionsNaming.ProcessImageTagsSubOrchestrator,
                image);

            var imageApproved = true;

            if (!indexTagResults.Contains("Food"))
            {
                // Human interaction pattern: we wait for Image human review
                // usually, we here send first some kind of Notification that Human approval is required
                // and then we wait for an External Event (or timeout)
                // Run this to send an external event to this orchestrator (true / false to approve/reject):
                // curl -X POST -d "true" "http://localhost:7071/runtime/webhooks/durabletask/instances/975c3b80-b0c2-47cc-b045-388a0dc2a7f1/raiseEvent/ApprovalEvent?taskHub=TestHubName&connection=Storage&code=edB8GO8I5bbq3Era86TalmoP6MP2ifnarCfc6vNlvFFb3SZ4VYwNWg==" -H "Content-Type: application/json"
                using (var timeoutCts = new CancellationTokenSource())
                {
                    var dueTime        = context.CurrentUtcDateTime.AddSeconds(30); // just for demo
                    var durableTimeout = context.CreateTimer(dueTime, timeoutCts.Token);

                    var approvalEvent = context.WaitForExternalEvent <bool>("ApprovalEvent");
                    if (approvalEvent == await Task.WhenAny(approvalEvent, durableTimeout))
                    {
                        timeoutCts.Cancel();
                        imageApproved = approvalEvent.Result;
                    }
                    else
                    {
                        imageApproved = false; // Monitor Timed out (we consider Image is not approved)
                    }
                }
            }

            if (!imageApproved)
            {
                var imageRejectedNotification = await context.CallActivityAsync <string>(
                    Constants.FunctionsNaming.SendRejectNotificationActivity,
                    image);

                return(new ProcessImageResult
                {
                    ImageName = image,
                    ImageSize = "0x0",
                    ImageTags = indexTagResults,
                    ErrorDetail = imageRejectedNotification,
                    ResultType = ProcessImageResultType.Rejected
                });
            }

            // At this point we´ve already processed Image tags, and the Image is validated (it´s a foodie Image!)
            // Next, we resize the image
            var resize = await context.CallActivityAsync <string>(Constants.FunctionsNaming.ResizeImageActivity, image);

            // Process is done. Send a notification and Orchestrator is done
            var notification = await context.CallActivityAsync <string>(Constants.FunctionsNaming.SendNotificationActivity, image);

            return(new ProcessImageResult
            {
                ImageName = image,
                ImageTags = indexTagResults,
                ErrorDetail = string.Empty,
                ResultType = ProcessImageResultType.Processed,
                ImageSize = resize
            });
        }
예제 #10
0
        public static async Task <object> ProcessVideo(
            [OrchestrationTrigger] IDurableOrchestrationContext ctx,
            ILogger log)
        {
            // get the input passed from the StartNewAsync() function
            var videoLocation = ctx.GetInput <string>();

            if (!ctx.IsReplaying)
            {
                log.LogInformation("About to call transcode video activity");
            }

            string transcodedLocation = null;
            string thumbnailLocation  = null;
            string withIntroLocation  = null;
            string approvalResult     = "Unknown";

            try
            {
                // Activity to return the bitrates
                var transcodeResults =
                    await ctx.CallSubOrchestratorAsync <VideoFileInfo[]>("O_TranscodeVideo", videoLocation);

                transcodedLocation =
                    transcodeResults
                    .OrderByDescending(r => r.BitRate)
                    .Select(r => r.Location)
                    .FirstOrDefault();

                // Durable functions api keeps track what activites has been performed and what has not been
                if (!ctx.IsReplaying)
                {
                    log.LogInformation("About to call extract thumbnail activity");
                }

                // Call an activity with retry options
                thumbnailLocation = await ctx.CallActivityWithRetryAsync <string>("A_ExtractThumbnail",
                                                                                  new RetryOptions(TimeSpan.FromSeconds(3), 4)
                {
                    Handle = ex => ex is InvalidOperationException
                },
                                                                                  transcodedLocation);

                if (!ctx.IsReplaying)
                {
                    log.LogInformation("About to call prepend intro activity");
                }

                withIntroLocation = await ctx.CallActivityAsync <string>("A_PrependIntro", transcodedLocation);

                // Call an activity without retrying
                await ctx.CallActivityAsync("A_SendApprovalRequestEmail", new ApprovalInfo()
                {
                    OrchestrationId = ctx.InstanceId,
                    VideoLocation   = withIntroLocation
                });

                // An exemple of a running task without timeout
                // approvalResult = await ctx.WaitForExternalEvent<string>("ApprovalResult");

                using (var cts = new CancellationTokenSource())
                {
                    // ctx saved dateTime has to be used to keep the orchestrator deterministic
                    var timeoutAt = ctx.CurrentUtcDateTime.AddSeconds(30);
                    // Puts the orchestration to sleep for x amount of time. Takes CancellationToken as
                    // a secondary argument, to be able to stop it. CancellationToken.None can be passed if not.
                    var timeoutTask  = ctx.CreateTimer(timeoutAt, cts.Token);
                    var approvalTaks = ctx.WaitForExternalEvent <string>("ApprovalResult");

                    // Passes in the result of any of the tasks that has been executed first
                    var winner = await Task.WhenAny(approvalTaks, timeoutTask);

                    if (winner == approvalTaks)
                    {
                        approvalResult = approvalTaks.Result;
                        cts.Cancel(); // cancelling the timeout task
                    }
                    else
                    {
                        approvalResult = "Timed Out!";
                    }
                }
                if (approvalResult == "Approved")
                {
                    await ctx.CallActivityAsync("A_PublishVideo", withIntroLocation);
                }
                else
                {
                    await ctx.CallActivityAsync("A_RejectVideo", withIntroLocation);
                }

                return(new
                {
                    Transcoded = transcodedLocation,
                    ThumbNail = thumbnailLocation,
                    WithIntro = withIntroLocation,
                    ApprovalResult = approvalResult
                });
            }
            catch (Exception e)
            {
                if (!ctx.IsReplaying)
                {
                    log.LogInformation($"Caught an error from an activity: {e.Message}");
                }

                await ctx.CallActivityAsync <string>("A_Cleanup",
                                                     new[] { transcodedLocation, thumbnailLocation, withIntroLocation });

                return(new
                {
                    Error = "Failed to process uploaded video",
                    Message = e.Message
                });
            }
        }
예제 #11
0
            public static async Task <string> SubOrchestrationTest([OrchestrationTrigger] IDurableOrchestrationContext ctx)
            {
                await ctx.CallSubOrchestratorAsync(nameof(NoOp), "NoOpInstanceId", null);

                return("done");
            }
 /// <summary>
 /// Schedules an orchestrator function named <paramref name="functionName"/> for execution.
 /// </summary>
 /// <param name="context">The context object.</param>
 /// <param name="functionName">The name of the orchestrator function to call.</param>
 /// <param name="input">The JSON-serializeable input to pass to the orchestrator function.</param>
 /// <returns>A durable task that completes when the called orchestrator function completes or fails.</returns>
 /// <exception cref="ArgumentException">
 /// The specified function does not exist, is disabled, or is not an orchestrator function.
 /// </exception>
 /// <exception cref="InvalidOperationException">
 /// The current thread is different than the thread which started the orchestrator execution.
 /// </exception>
 /// <exception cref="FunctionFailedException">
 /// The sub-orchestrator function failed with an unhandled exception.
 /// </exception>
 public static Task CallSubOrchestratorAsync(this IDurableOrchestrationContext context, string functionName, object input)
 {
     return(context.CallSubOrchestratorAsync <object>(functionName, input));
 }
        public static async Task <object> ProcessVideo(
            [OrchestrationTrigger] IDurableOrchestrationContext ctx,
            ILogger log)
        {
            log = ctx.CreateReplaySafeLogger(log);
            var videoLocation = ctx.GetInput <string>();

            log.LogInformation("About to call transcode video activity");

            string transcodedLocation = null;
            string thumbnailLocation  = null;
            string withIntroLocation  = null;
            string approvalResult     = "Unknown";

            try
            {
                ctx.SetCustomStatus("transcoding");
                var transcodeResults =
                    await ctx.CallSubOrchestratorAsync <VideoFileInfo[]>("O_TranscodeVideo", videoLocation);

                transcodedLocation = transcodeResults
                                     .OrderByDescending(r => r.BitRate)
                                     .Select(r => r.Location)
                                     .First();

                ctx.SetCustomStatus("extracting thumbnail");
                log.LogInformation("About to call extract thumbnail");

                thumbnailLocation = await
                                    ctx.CallActivityAsync <string>("A_ExtractThumbnail", transcodedLocation);

                ctx.SetCustomStatus("prepending intro");
                log.LogInformation("About to call prepend intro");

                withIntroLocation = await
                                    ctx.CallActivityAsync <string>("A_PrependIntro", transcodedLocation);

                ctx.SetCustomStatus("sending approval request email");
                await ctx.CallActivityAsync("A_SendApprovalRequestEmail", new ApprovalInfo()
                {
                    OrchestrationId = ctx.InstanceId,
                    VideoLocation   = withIntroLocation
                });

                ctx.SetCustomStatus("waiting for email response");
                try
                {
                    approvalResult = await ctx.WaitForExternalEvent <string>("ApprovalResult", TimeSpan.FromSeconds(30));
                }
                catch (TimeoutException)
                {
                    log.LogWarning("Timed out waiting for approval");
                    approvalResult = "Timed Out";
                }

                if (approvalResult == "Approved")
                {
                    ctx.SetCustomStatus("publishing video");
                    await ctx.CallActivityAsync("A_PublishVideo", withIntroLocation);
                }
                else
                {
                    ctx.SetCustomStatus("rejecting video");
                    await ctx.CallActivityAsync("A_RejectVideo", withIntroLocation);
                }
                ctx.SetCustomStatus("finished");
            }
            catch (Exception e)
            {
                log.LogError($"Caught an error from an activity: {e.Message}");

                ctx.SetCustomStatus("error: cleaning up");
                await
                ctx.CallActivityAsync <string>("A_Cleanup",
                                               new[] { transcodedLocation, thumbnailLocation, withIntroLocation });

                ctx.SetCustomStatus("finished with error");

                return(new
                {
                    Error = "Failed to process uploaded video",
                    Message = e.Message
                });
            }

            return(new
            {
                Transcoded = transcodedLocation,
                Thumbnail = thumbnailLocation,
                WithIntro = withIntroLocation,
                ApprovalResult = approvalResult
            });
        }
예제 #14
0
        public async Task <GroupMembershipMessageResponse> RunOrchestratorAsync([OrchestrationTrigger] IDurableOrchestrationContext context)
        {
            GroupMembership                groupMembership = null;
            GraphUpdaterFunctionRequest    graphRequest    = null;
            GroupMembershipMessageResponse messageResponse = null;
            SyncJob syncJob = null;

            graphRequest       = context.GetInput <GraphUpdaterFunctionRequest>();
            groupMembership    = JsonConvert.DeserializeObject <GroupMembership>(graphRequest.Message);
            graphRequest.RunId = groupMembership.RunId;

            try
            {
                syncJob = await context.CallActivityAsync <SyncJob>(nameof(JobReaderFunction),
                                                                    new JobReaderRequest
                {
                    JobPartitionKey = groupMembership.SyncJobPartitionKey,
                    JobRowKey       = groupMembership.SyncJobRowKey,
                    RunId           = groupMembership.RunId
                });

                await context.CallActivityAsync(nameof(LoggerFunction), new LoggerRequest { Message = $"{nameof(OrchestratorFunction)} function started", SyncJob = syncJob });

                messageResponse = await context.CallActivityAsync <GroupMembershipMessageResponse>(nameof(MessageCollectorFunction), graphRequest);

                if (graphRequest.IsCancelationRequest)
                {
                    await context.CallActivityAsync(nameof(LoggerFunction), new LoggerRequest { Message = $"Canceling session {graphRequest.MessageSessionId}", SyncJob = syncJob });

                    await context.CallActivityAsync(nameof(JobStatusUpdaterFunction),
                                                    CreateJobStatusUpdaterRequest(groupMembership.SyncJobPartitionKey, groupMembership.SyncJobRowKey,
                                                                                  SyncStatus.Error, groupMembership.MembershipObtainerDryRunEnabled, syncJob.ThresholdViolations, groupMembership.RunId));

                    return(messageResponse);
                }

                if (messageResponse.ShouldCompleteMessage)
                {
                    var isValidGroup = await context.CallActivityAsync <bool>(nameof(GroupValidatorFunction),
                                                                              new GroupValidatorRequest
                    {
                        RunId           = groupMembership.RunId,
                        GroupId         = groupMembership.Destination.ObjectId,
                        JobPartitionKey = groupMembership.SyncJobPartitionKey,
                        JobRowKey       = groupMembership.SyncJobRowKey
                    });

                    if (!isValidGroup)
                    {
                        await context.CallActivityAsync(nameof(JobStatusUpdaterFunction),
                                                        CreateJobStatusUpdaterRequest(groupMembership.SyncJobPartitionKey, groupMembership.SyncJobRowKey,
                                                                                      SyncStatus.Error, groupMembership.MembershipObtainerDryRunEnabled, syncJob.ThresholdViolations, groupMembership.RunId));

                        await context.CallActivityAsync(nameof(LoggerFunction), new LoggerRequest { Message = $"{nameof(OrchestratorFunction)} function did not complete", SyncJob = syncJob });

                        return(messageResponse);
                    }

                    var destinationGroupMembers = await context.CallSubOrchestratorAsync <List <AzureADUser> >(nameof(UsersReaderSubOrchestratorFunction),
                                                                                                               new UsersReaderRequest { SyncJob = syncJob });

                    var fullMembership = new GroupMembership
                    {
                        Destination         = groupMembership.Destination,
                        IsLastMessage       = groupMembership.IsLastMessage,
                        RunId               = groupMembership.RunId,
                        SourceMembers       = messageResponse.CompletedGroupMembershipMessages.SelectMany(x => x.Body.SourceMembers).ToList(),
                        SyncJobPartitionKey = groupMembership.SyncJobPartitionKey,
                        SyncJobRowKey       = groupMembership.SyncJobRowKey
                    };

                    var deltaResponse = await context.CallActivityAsync <DeltaResponse>(nameof(DeltaCalculatorFunction),
                                                                                        new DeltaCalculatorRequest
                    {
                        RunId                       = groupMembership.RunId,
                        GroupMembership             = fullMembership,
                        MembersFromDestinationGroup = destinationGroupMembers,
                    });

                    if (deltaResponse.GraphUpdaterStatus == GraphUpdaterStatus.Error ||
                        deltaResponse.GraphUpdaterStatus == GraphUpdaterStatus.ThresholdExceeded)
                    {
                        var updateRequest = CreateJobStatusUpdaterRequest(groupMembership.SyncJobPartitionKey, groupMembership.SyncJobRowKey,
                                                                          deltaResponse.SyncStatus, groupMembership.MembershipObtainerDryRunEnabled, syncJob.ThresholdViolations, groupMembership.RunId);

                        if (deltaResponse.GraphUpdaterStatus == GraphUpdaterStatus.ThresholdExceeded)
                        {
                            updateRequest.ThresholdViolations++;

                            if (updateRequest.ThresholdViolations >= _thresholdConfig.NumberOfThresholdViolationsToDisableJob)
                            {
                                updateRequest.Status = SyncStatus.ThresholdExceeded;
                            }
                        }

                        await context.CallActivityAsync(nameof(JobStatusUpdaterFunction), updateRequest);
                    }

                    if (deltaResponse.GraphUpdaterStatus != GraphUpdaterStatus.Ok)
                    {
                        await context.CallActivityAsync(nameof(LoggerFunction), new LoggerRequest { Message = $"{nameof(OrchestratorFunction)} function did not complete", SyncJob = syncJob });

                        return(messageResponse);
                    }

                    if (!deltaResponse.IsDryRunSync)
                    {
                        await context.CallSubOrchestratorAsync <GraphUpdaterStatus>(nameof(GroupUpdaterSubOrchestratorFunction),
                                                                                    CreateGroupUpdaterRequest(syncJob, deltaResponse.MembersToAdd, RequestType.Add, deltaResponse.IsInitialSync));

                        await context.CallSubOrchestratorAsync <GraphUpdaterStatus>(nameof(GroupUpdaterSubOrchestratorFunction),
                                                                                    CreateGroupUpdaterRequest(syncJob, deltaResponse.MembersToRemove, RequestType.Remove, deltaResponse.IsInitialSync));

                        if (deltaResponse.IsInitialSync)
                        {
                            var groupName = await context.CallActivityAsync <string>(nameof(GroupNameReaderFunction),
                                                                                     new GroupNameReaderRequest { RunId = groupMembership.RunId, GroupId = groupMembership.Destination.ObjectId });

                            var additonalContent = new[]
                            {
                                groupName,
                                groupMembership.Destination.ObjectId.ToString(),
                                deltaResponse.MembersToAdd.Count.ToString(),
                                deltaResponse.MembersToRemove.Count.ToString(),
                                _emailSenderAndRecipients.SupportEmailAddresses
                            };

                            await context.CallActivityAsync(nameof(EmailSenderFunction),
                                                            new EmailSenderRequest
                            {
                                ToEmail                 = deltaResponse.Requestor,
                                CcEmail                 = _emailSenderAndRecipients.SyncCompletedCCAddresses,
                                ContentTemplate         = SyncCompletedEmailBody,
                                AdditionalContentParams = additonalContent,
                                RunId = groupMembership.RunId
                            });
                        }
                    }

                    var message = GetUsersDataMessage(groupMembership.Destination.ObjectId, deltaResponse.MembersToAdd.Count, deltaResponse.MembersToRemove.Count);
                    await context.CallActivityAsync(nameof(LoggerFunction), new LoggerRequest { Message = message });

                    await context.CallActivityAsync(nameof(JobStatusUpdaterFunction),
                                                    CreateJobStatusUpdaterRequest(groupMembership.SyncJobPartitionKey, groupMembership.SyncJobRowKey,
                                                                                  SyncStatus.Idle, groupMembership.MembershipObtainerDryRunEnabled, 0, groupMembership.RunId));

                    var timeElapsedForJob = (context.CurrentUtcDateTime - deltaResponse.Timestamp).TotalSeconds;
                    _telemetryClient.TrackMetric(nameof(Metric.SyncJobTimeElapsedSeconds), timeElapsedForJob);

                    var syncCompleteEvent = new Dictionary <string, string>
                    {
                        { nameof(SyncJob.TargetOfficeGroupId), groupMembership.Destination.ObjectId.ToString() },
                        { nameof(SyncJob.Type), deltaResponse.SyncJobType },
                        { "Result", deltaResponse.SyncStatus == SyncStatus.Idle ? "Success": "Failure" },
                        { nameof(SyncJob.IsDryRunEnabled), deltaResponse.IsDryRunSync.ToString() },
                        { nameof(Metric.SyncJobTimeElapsedSeconds), timeElapsedForJob.ToString() },
                        { nameof(DeltaResponse.MembersToAdd), deltaResponse.MembersToAdd.Count.ToString() },
                        { nameof(DeltaResponse.MembersToRemove), deltaResponse.MembersToRemove.Count.ToString() },
                        { nameof(Metric.ProjectedMemberCount), fullMembership.SourceMembers.Count.ToString() }
                    };

                    _telemetryClient.TrackEvent(nameof(Metric.SyncComplete), syncCompleteEvent);
                }

                await context.CallActivityAsync(nameof(LoggerFunction), new LoggerRequest { Message = $"{nameof(OrchestratorFunction)} function completed", SyncJob = syncJob });

                return(messageResponse);
            }
            catch (Exception ex)
            {
                await context.CallActivityAsync(nameof(LoggerFunction), new LoggerRequest { Message = $"Caught unexpected exception, marking sync job as errored. Exception:\n{ex}", SyncJob = syncJob });

                if (groupMembership != null && !string.IsNullOrWhiteSpace(groupMembership.SyncJobPartitionKey) && !string.IsNullOrWhiteSpace(groupMembership.SyncJobRowKey))
                {
                    await context.CallActivityAsync(nameof(JobStatusUpdaterFunction),
                                                    CreateJobStatusUpdaterRequest(groupMembership.SyncJobPartitionKey, groupMembership.SyncJobRowKey,
                                                                                  SyncStatus.Error, groupMembership.MembershipObtainerDryRunEnabled, syncJob.ThresholdViolations, groupMembership.RunId));
                }

                throw;
            }
        }
 private static async Task PerformChangeOfCircumstances(IDurableOrchestrationContext context, ApprenticeshipIncentiveOutput incentive)
 {
     await context.CallSubOrchestratorAsync(nameof(ChangeOfCircumstanceOrchestrator),
                                            new LearnerChangeOfCircumstanceInput(incentive.Id, incentive.ULN));
 }