public async Task <CommandResult> Handle(AppendStepLogCommand request, CancellationToken cancellationToken) { var stopwatch = new Stopwatch(); stopwatch.Start(); var step = await _entitiesRepository.GetFirstOrDefaultAsync <Step>(e => e.Id == request.StepId); if (StepStatuses.IsCompleteStatus(step.Status)) { throw new InvalidStepStatusException("Cannot append log to step, step status is complete with " + step.Status); } step.UpdateJournal(new Domain.Entities.JournalEntries.JournalEntry() { CreatedBy = request.CreatedBy, CreatedOn = DateTime.UtcNow, Updates = new List <Domain.ValueObjects.Update>() { new Update() { Type = UpdateType.Append, FieldName = "logs", Value = new StepLog() { CreatedOn = DateTime.UtcNow, Message = request.Log }, } } }); //await _entitiesRepository.UpdateStep(step); var createdWorkflowTemplateId = await _node.Handle(new AddShardWriteOperation() { Data = step, WaitForSafeWrite = true, Operation = ConsensusCore.Domain.Enums.ShardOperationOptions.Update }); return(new CommandResult() { ElapsedMs = stopwatch.ElapsedMilliseconds, ObjectRefId = step.Id.ToString(), Type = CommandResultTypes.Update }); }
public async Task <IActionResult> Create(CreateStepCommand command, bool?wait_for_completion, string timeout = "30s") { var stopwatch = new Stopwatch(); stopwatch.Start(); try { command.CreatedBy = ClaimsUtility.GetId(User); var result = await Mediator.Send(command); Step step = (await Mediator.Send(new GetEntityQuery <Step>() { Expression = s => s.Id == new Guid(result.ObjectRefId), Exclude = (s) => s.Journal })).Result; if (wait_for_completion.HasValue && wait_for_completion.Value) { var ms = DateTimeMathsUtility.GetMs(timeout); while (!StepStatuses.IsCompleteStatus(step.Status) && stopwatch.ElapsedMilliseconds < ms) { step = (await Mediator.Send(new GetEntityQuery <Step>() { Expression = s => s.Id == new Guid(result.ObjectRefId) })).Result; } } return(Ok(new HttpCommandResult <Step>("step", result, step))); } catch (BaseException e) { Logger.LogError(e.Message); stopwatch.Stop(); return(BadRequest(e.ToExceptionResult(stopwatch.ElapsedMilliseconds))); } }
public async Task <CommandResult> Handle(CompleteStepCommand request, CancellationToken cancellationToken) { var stopwatch = new Stopwatch(); stopwatch.Start(); var stepToComplete = await _entitiesRepository.GetFirstOrDefaultAsync <Step>(s => s.Id == request.Id); if (stepToComplete.IsComplete()) { // if (JsonConvert.SerializeObject(stepToComplete.Outputs) == JsonConvert.SerializeObject(request.Outputs)) // { Logger.LogWarning("Step " + request.Id + " is already complete with status " + stepToComplete.Status + "."); throw new DuplicateStepUpdateException(); //} } if (!StepStatuses.IsCompleteStatus(request.Status)) { throw new InvalidStepStatusInputException(request.Status + " is not a valid completion status."); } var stepTemplate = await _entitiesRepository.GetFirstOrDefaultAsync <StepTemplate>(st => st.ReferenceId == stepToComplete.StepTemplateId); if (request.Outputs == null) { request.Outputs = new Dictionary <string, object>(); } var botkey = await _entitiesRepository.GetFirstOrDefaultAsync <BotKey>(bk => bk.Id == request.BotId); var unencryptedOuputs = DynamicDataUtility.DecryptDynamicData(stepTemplate.OutputDefinitions, request.Outputs, EncryptionProtocol.RSA, botkey.PublicEncryptionKey, true); var finalUpdate = new List <Domain.ValueObjects.Update>() { new Update() { Type = UpdateType.Override, FieldName = "status", Value = request.Status, }, new Update() { Type = UpdateType.Override, FieldName = "outputs", Value = DynamicDataUtility.EncryptDynamicData(stepTemplate.OutputDefinitions, unencryptedOuputs, EncryptionProtocol.AES256, ClusterStateService.GetEncryptionKey()) }, new Update() { Type = UpdateType.Override, FieldName = "statuscode", Value = request.StatusCode, } }; if (request.Log != null) { finalUpdate.Add( new Update() { Type = UpdateType.Append, FieldName = "logs", Value = new StepLog() { CreatedOn = DateTime.UtcNow, Message = request.Log }, }); } stepToComplete.UpdateJournal(new Domain.Entities.JournalEntries.JournalEntry() { CreatedOn = DateTime.UtcNow, CreatedBy = request.CreatedBy, Updates = finalUpdate }); await _node.Handle(new AddShardWriteOperation() { Data = stepToComplete, WaitForSafeWrite = true, Operation = ConsensusCore.Domain.Enums.ShardOperationOptions.Update }); Logger.LogInformation("Updated step " + stepToComplete.Id + " with status " + stepToComplete.Status); var updatedStep = await _entitiesRepository.GetFirstOrDefaultAsync <Step>(s => s.Id == stepToComplete.Id); if (updatedStep.WorkflowId != null) { var workflow = await _entitiesRepository.GetFirstOrDefaultAsync <Workflow>(w => w.Id == updatedStep.WorkflowId.Value); if (workflow == null) { throw new MissingWorkflowException("Failed to continue workflow " + updatedStep.WorkflowId + " as workflow does not exist."); } //Get the workflow template var workflowTemplate = await _entitiesRepository.GetFirstOrDefaultAsync <WorkflowTemplate>(wt => wt.ReferenceId == workflow.WorkflowTemplateId); if (workflowTemplate == null) { throw new WorkflowTemplateNotFoundException("Failed to continue workflow " + updatedStep.WorkflowId + " workflow template " + workflow.WorkflowTemplateId + "."); } //Get all the steps related to this task var workflowSteps = (await _entitiesRepository.GetAsync <Step>(s => s.WorkflowId == updatedStep.WorkflowId.Value)).ToList(); foreach (var workflowStep in workflowSteps) { workflowStep.Outputs = DynamicDataUtility.DecryptDynamicData((await _entitiesRepository.GetFirstOrDefaultAsync <StepTemplate>(st => st.ReferenceId == workflowStep.StepTemplateId)).OutputDefinitions, workflowStep.Outputs, EncryptionProtocol.AES256, ClusterStateService.GetEncryptionKey()); } //Keep track of whether a step has been added bool stepCreated = false; //Evaluate all logic blocks that have not been completed var logicBlocks = workflowTemplate.LogicBlocks.Where(lb => !workflow.CompletedLogicBlocks.Contains(lb.Key) && lb.Value.Dependencies.ContainsStep(updatedStep.Name)).ToList(); foreach (var logicBlock in logicBlocks) { Logger.LogInformation("Processing logic block " + logicBlock.Key + " for workflow " + workflow.Id); var lockId = Guid.NewGuid(); bool lockObtained = false; while (!lockObtained) { while (_clusterStateService.IsLogicBlockLocked(updatedStep.WorkflowId.Value, logicBlock.Key)) { Console.WriteLine("Found " + ("workflow:" + updatedStep.WorkflowId + ">logicBlock:" + logicBlock) + " Lock"); await Task.Delay(1000); } int entryNumber = await _clusterStateService.LockLogicBlock(lockId, updatedStep.WorkflowId.Value, logicBlock.Key); //Check whether you got the lock lockObtained = _clusterStateService.WasLockObtained(lockId, updatedStep.WorkflowId.Value, logicBlock.Key); } //When the logic block is released, recheck whether this logic block has been evaluated workflow = await _entitiesRepository.GetFirstOrDefaultAsync <Workflow>(w => w.Id == updatedStep.WorkflowId.Value); workflow.Inputs = DynamicDataUtility.DecryptDynamicData(workflowTemplate.InputDefinitions, workflow.Inputs, EncryptionProtocol.AES256, ClusterStateService.GetEncryptionKey()); //If the logic block is ready to be processed, submit the steps if (logicBlock.Value.Dependencies.Evaluate(workflowSteps) && !workflow.CompletedLogicBlocks.Contains(logicBlock.Key)) { foreach (var substep in logicBlock.Value.SubsequentSteps) { if (workflowSteps.Where(s => s.Name == substep.Key).Count() > 0) { Logger.LogCritical("Encountered duplicate subsequent step for workflow " + workflow.Id + ". Ignoring the generation of duplicate."); } else { var verifiedInputs = new Dictionary <string, object>(); foreach (var mapping in substep.Value.Mappings) { //find the mapping with the highest priority var highestPriorityReference = WorkflowTemplateUtility.GetHighestPriorityReference(mapping.Value.OutputReferences, workflowSteps.ToArray()); //if the highest priority reference is null, there are no mappings if (highestPriorityReference == null && mapping.Value.DefaultValue == null) { } // If default value is higher priority else if (mapping.Value.DefaultValue != null && (highestPriorityReference == null || highestPriorityReference.Priority < mapping.Value.DefaultValue.Priority)) { verifiedInputs.Add(mapping.Key, mapping.Value.DefaultValue.Value); } // If the step ref is not -1 it is a step in the array but the workflow else if (highestPriorityReference.StepName != ReservedValues.WorkflowStartStepName) { var parentStep = workflowSteps.Where(ss => ss.Name == highestPriorityReference.StepName).FirstOrDefault(); //If there is a AND and there is no parent step, throw a error if (parentStep == null) { throw new InvalidWorkflowProcessingException("Missing source for mapping " + mapping.Key + " from step " + highestPriorityReference.StepName); } else { if (parentStep != null) { try { // Get the output value based on the pre-requisite id var outPutValue = DynamicDataUtility.GetData(parentStep.Outputs, highestPriorityReference.OutputId); // Validate it is in the definition verifiedInputs.Add(mapping.Key, outPutValue.Value); } catch (Exception e) { //TO DO Move this to logger Console.WriteLine("Found error at mapping " + mapping.Key + " for step " + substep.Key); throw e; } } } } //Get the value from the workflow ref else { // Validate it is in the definition verifiedInputs.Add(mapping.Key, DynamicDataUtility.GetData(workflow.Inputs, highestPriorityReference.OutputId).Value); } } stepCreated = true; //Add the step TODO, add step priority await _mediator.Send(new CreateStepCommand() { StepTemplateId = substep.Value.StepTemplateId, CreatedBy = SystemUsers.QUEUE_MANAGER, Inputs = verifiedInputs, WorkflowId = workflow.Id, Name = substep.Key //create the step with the right subsequent step }); } } //Mark it as evaluated workflow.UpdateJournal( new JournalEntry() { CreatedBy = request.CreatedBy, CreatedOn = DateTime.UtcNow, Updates = new List <Update>() { new Update() { FieldName = "completedlogicblocks", Type = UpdateType.Append, Value = logicBlock.Key } } }); //await _workflowsRepository.UpdateWorkflow(workflow); await _node.Handle(new AddShardWriteOperation() { Data = workflow, WaitForSafeWrite = true, Operation = ConsensusCore.Domain.Enums.ShardOperationOptions.Update }); } await _clusterStateService.UnlockLogicBlock(lockId, updatedStep.WorkflowId.Value, logicBlock.Key); } //Check if there are no longer any steps that are unassigned or assigned var workflowStatus = workflow.Status; workflowSteps = (await _entitiesRepository.GetAsync <Step>(s => s.WorkflowId == updatedStep.WorkflowId.Value)).ToList(); var highestStatus = StepStatuses.GetHighestPriority(workflowSteps.Select(s => s.Status).ToArray()); var newWorkflowStatus = stepCreated ? WorkflowStatuses.ConvertStepStatusToWorkflowStatus(StepStatuses.Unassigned) : WorkflowStatuses.ConvertStepStatusToWorkflowStatus(highestStatus); if (newWorkflowStatus != workflow.Status) { workflow.UpdateJournal( new JournalEntry() { CreatedBy = request.CreatedBy, CreatedOn = DateTime.UtcNow, Updates = new List <Update>() { new Update() { FieldName = "status", Type = UpdateType.Override, Value = newWorkflowStatus } } }); //await _workflowsRepository.UpdateWorkflow(workflow); await _node.Handle(new AddShardWriteOperation() { Data = workflow, WaitForSafeWrite = true, Operation = ConsensusCore.Domain.Enums.ShardOperationOptions.Update }); } } return(new CommandResult() { ObjectRefId = request.Id.ToString(), ElapsedMs = stopwatch.ElapsedMilliseconds, Type = CommandResultTypes.Update }); }