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); }
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; } }