예제 #1
0
        public async Task <GroupMembershipMessageResponse> CollectMessagesAsync([ActivityTrigger] GraphUpdaterFunctionRequest request)
        {
            await _loggingRepository.LogMessageAsync(new LogMessage { Message = nameof(MessageCollectorFunction) + " function started", RunId = request.RunId });

            var body = new GroupMembershipMessage
            {
                Body                 = JsonConvert.DeserializeObject <GroupMembership>(request.Message),
                LockToken            = request.MessageLockToken,
                IsCancelationMessage = request.IsCancelationRequest
            };

            var handleNewMessageResult = await _messageCollector.HandleNewMessageAsync(body, request.MessageSessionId);

            await _loggingRepository.LogMessageAsync(new LogMessage { Message = nameof(MessageCollectorFunction) + " function completed", RunId = request.RunId });

            return(handleNewMessageResult);
        }
        private async Task <SessionTrackerAction> TrackLastMessageTimeout(IDurableOrchestrationClient starter, IMessageSession messageSession, string messageId)
        {
            if (_sessionsTracker.TryGetValue(messageSession.SessionId, out var sessionTracker))
            {
                if (sessionTracker.ReceivedLastMessage)
                {
                    return(SessionTrackerAction.Continue);
                }

                if (messageId != sessionTracker.LatestMessageId)
                {
                    return(SessionTrackerAction.Stop);
                }

                int.TryParse(_configuration["GraphUpdater:LastMessageWaitTimeout"], out int timeOut);
                timeOut = timeOut == 0 ? 10 : timeOut;

                var elapsedTime = DateTime.UtcNow - sessionTracker.LastAccessTime;
                if (elapsedTime.TotalMinutes >= timeOut)
                {
                    _sessionsTracker.Remove(messageSession.SessionId, out _);
                    await messageSession.CompleteAsync(sessionTracker.LockTokens);

                    await messageSession.CloseAsync();

                    var cancelationRequest = new GraphUpdaterFunctionRequest
                    {
                        IsCancelationRequest = true,
                        MessageSessionId     = messageSession.SessionId,
                        Message = JsonConvert.SerializeObject(new GroupMembership
                        {
                            SyncJobPartitionKey = sessionTracker.JobPartitionKey,
                            SyncJobRowKey       = sessionTracker.JobRowKey,
                            RunId = sessionTracker.RunId
                        }),
                    };

                    await starter.StartNewAsync(nameof(OrchestratorFunction), cancelationRequest);

                    return(SessionTrackerAction.Stop);
                }
            }

            return(SessionTrackerAction.Continue);
        }
        public async Task RunAsync(
            [ServiceBusTrigger("%membershipQueueName%", Connection = "differenceQueueConnection", IsSessionsEnabled = true)] Message message,
            [DurableClient] IDurableOrchestrationClient starter, IMessageSession messageSession)
        {
            await _loggingRepository.LogMessageAsync(new LogMessage { Message = nameof(StarterFunction) + " function started" });

            var messageDetails = _messageService.GetMessageProperties(message);
            var graphRequest   = new GraphUpdaterFunctionRequest()
            {
                Message          = Encoding.UTF8.GetString(messageDetails.Body),
                MessageSessionId = messageDetails.SessionId,
                MessageLockToken = messageDetails.LockToken
            };

            var groupMembership = JsonConvert.DeserializeObject <GroupMembership>(graphRequest.Message);

            SetSessionTracker(messageDetails, groupMembership);

            var source     = new CancellationTokenSource();
            var renew      = RenewMessages(starter, messageSession, source, messageDetails.MessageId);
            var instanceId = await starter.StartNewAsync(nameof(OrchestratorFunction), graphRequest);

            var completedGroupMembershipMessages = default(List <GroupMembershipMessage>);
            var isLastMessage = false;
            var orchestratorRuntimeStatusCodesWorthRetrying = new OrchestrationRuntimeStatus[]
            {
                OrchestrationRuntimeStatus.ContinuedAsNew,
                OrchestrationRuntimeStatus.Running,
                OrchestrationRuntimeStatus.Pending
            };

            var result = default(DurableOrchestrationStatus);

            /*Understanding the Retry policy
             * We have a lot of sub-second sync execution so the first query would ensure we cater to those queries
             * We also have a lot of syncs that take less than 10 seconds. Having a exponetial backoff 1.25^1 would mean we would be waiting 90 seconds per sync instead of 10 seconds.
             * Hence the logic to ensure retryAttempt 1 is done after 10 seconds. Following this we go back to the exponetial backoff.
             */

            var retryPolicy = Policy
                              .HandleResult <DurableOrchestrationStatus>(status => orchestratorRuntimeStatusCodesWorthRetrying.Contains(status.RuntimeStatus))
                              .WaitAndRetryAsync(
                MAX_RETRY_ATTEMPTS,
                retryAttempt =>
            {
                if (retryAttempt == 1)
                {
                    return(TimeSpan.FromSeconds(FIRST_RETRY_DELAY_IN_SECONDS));
                }
                else
                {
                    return(TimeSpan.FromMinutes(Math.Pow(1.25, retryAttempt - 1)));
                }
            }
                );

            await retryPolicy.ExecuteAsync(async() =>
            {
                result = await starter.GetStatusAsync(instanceId);
                return(result);
            });

            if (result.RuntimeStatus == OrchestrationRuntimeStatus.Failed || result.RuntimeStatus == OrchestrationRuntimeStatus.Terminated)
            {
                await _loggingRepository.LogMessageAsync(new LogMessage { Message = $"Error: Status of instance {result.InstanceId} is {result.RuntimeStatus}. The error message is : {result.Output}" });

                // stop renewing the message session
                source.Cancel();
            }
            else
            {
                await _loggingRepository.LogMessageAsync(new LogMessage { Message = $"Instance processing completed for {result.InstanceId}" });

                var orchestratorResponseOutput = JsonConvert.DeserializeObject <GroupMembershipMessageResponse>(result.Output.ToString());
                completedGroupMembershipMessages = orchestratorResponseOutput.CompletedGroupMembershipMessages;
                isLastMessage = orchestratorResponseOutput.ShouldCompleteMessage;
            }

            if (isLastMessage)
            {
                var completedLockTokens = completedGroupMembershipMessages.Select(x => x.LockToken);
                await messageSession.CompleteAsync(completedLockTokens);

                await messageSession.CloseAsync();

                source.Cancel();
            }

            await _loggingRepository.LogMessageAsync(new LogMessage { Message = nameof(StarterFunction) + " function completed" });
        }
예제 #4
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;
            }
        }