Beispiel #1
0
        public async Task <GroupMembershipMessageResponse> HandleNewMessageAsync(GroupMembershipMessage body, string messageSessionId)
        {
            if (body.IsCancelationMessage)
            {
                _receivedMessages.TryRemove(messageSessionId, out _);
                return(new GroupMembershipMessageResponse());
            }

            _loggingRepository.SyncJobProperties = new Dictionary <string, string>()
            {
                { "LockToken", body.LockToken }, { "RowKey", body.Body.SyncJobRowKey }, { "PartitionKey", body.Body.SyncJobPartitionKey }, { "TargetOfficeGroupId", body.Body.Destination.ObjectId.ToString() }
            };

            var receivedSoFar = _receivedMessages.AddOrUpdate(messageSessionId, new List <GroupMembershipMessage> {
                body
            }, (key, messages) =>
            {
                messages.Add(body); return(messages);
            });

            var handleNewMessageResponse = new GroupMembershipMessageResponse()
            {
                ShouldCompleteMessage = false
            };

            await _loggingRepository.LogMessageAsync(new LogMessage
            {
                RunId   = body.Body.RunId,
                Message = $"Got a message in {nameof(MessageCollector)}. " +
                          $"The message we just received has {body.Body.SourceMembers.Count} users and is {(body.Body.IsLastMessage ? "" : "not ")}the last message in its session. " +
                          $"There are currently {_receivedMessages.Count} sessions in flight. " +
                          $"The current session, the one with ID {messageSessionId}, has {receivedSoFar.Count} messages with {receivedSoFar.Sum(x => x.Body.SourceMembers.Count)} users in total."
            });

            if (body.Body.IsLastMessage)
            {
                if (!_receivedMessages.TryRemove(messageSessionId, out var allReceivedMessages))
                {
                    // someone else got to it first. shouldn't happen, but it's good to be prepared.
                    return(handleNewMessageResponse);
                }

                var received = GroupMembership.Merge(allReceivedMessages.Select(x => x.Body));

                await _loggingRepository.LogMessageAsync(new LogMessage
                {
                    RunId   = body.Body.RunId,
                    Message = $"This message completed the session, so I'm going to sync {received.SourceMembers.Count} users."
                });

                handleNewMessageResponse.CompletedGroupMembershipMessages = allReceivedMessages;
                handleNewMessageResponse.ShouldCompleteMessage            = true;

                return(handleNewMessageResponse);
            }
            else
            {
                await _loggingRepository.LogMessageAsync(new LogMessage
                {
                    RunId   = body.Body.RunId,
                    Message = "This is not the last message, so not doing anything right now."
                });
            }

            _loggingRepository.SyncJobProperties = null;
            return(handleNewMessageResponse);
        }
Beispiel #2
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;
            }
        }