From 748f9879f624f2cbafd5bb7279857cee2103cae6 Mon Sep 17 00:00:00 2001 From: Charles d'Avernas Date: Fri, 25 Oct 2024 15:00:51 +0200 Subject: [PATCH] fix(Solution): Added a new `suspended` workflow instance status phase Signed-off-by: Charles d'Avernas --- .../CancelWorkflowInstanceCommand.cs | 2 +- .../ResumeWorkflowInstanceCommand.cs | 2 +- .../SuspendWorkflowInstanceCommand.cs | 2 +- .../WorkflowInstanceStatusPhase.cs | 21 ++++++++++++++++++- .../Services/WorkflowInstanceHandler.cs | 2 +- .../Services/RunnerApplication.cs | 2 +- .../Services/WorkflowExecutionContext.cs | 4 ++-- .../Services/WorkflowExecutor.cs | 2 +- .../Services/MockWorkflowInstanceApiClient.cs | 6 +++--- 9 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/api/Synapse.Api.Application/Commands/WorkflowInstances/CancelWorkflowInstanceCommand.cs b/src/api/Synapse.Api.Application/Commands/WorkflowInstances/CancelWorkflowInstanceCommand.cs index 4af077aba..d8a934fae 100644 --- a/src/api/Synapse.Api.Application/Commands/WorkflowInstances/CancelWorkflowInstanceCommand.cs +++ b/src/api/Synapse.Api.Application/Commands/WorkflowInstances/CancelWorkflowInstanceCommand.cs @@ -51,7 +51,7 @@ public virtual async Task HandleAsync(CancelWorkflowInstanceCo var workflowInstanceReference = new ResourceReference(command.Name, command.Namespace); var original = await resources.GetAsync(command.Name, command.Namespace, cancellationToken).ConfigureAwait(false) ?? throw new ProblemDetailsException(new(ProblemTypes.NotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, ProblemDescriptions.ResourceNotFound.Format(workflowInstanceReference.ToString()))); - if (!string.IsNullOrWhiteSpace(original.Status?.Phase) && original.Status?.Phase != WorkflowInstanceStatusPhase.Pending && original.Status?.Phase != WorkflowInstanceStatusPhase.Running && original.Status?.Phase != WorkflowInstanceStatusPhase.Waiting) + if (!string.IsNullOrWhiteSpace(original.Status?.Phase) && original.Status?.Phase != WorkflowInstanceStatusPhase.Pending && original.Status?.Phase != WorkflowInstanceStatusPhase.Running && original.Status?.Phase != WorkflowInstanceStatusPhase.Suspended && original.Status?.Phase != WorkflowInstanceStatusPhase.Waiting) throw new ProblemDetailsException(new(ProblemTypes.AdmissionFailed, ProblemTitles.AdmissionFailed, (int)HttpStatusCode.BadRequest, $"The workflow instance '{workflowInstanceReference}' is in an expected phase '{original.Status?.Phase}'")); var updated = original.Clone()!; updated.Status ??= new(); diff --git a/src/api/Synapse.Api.Application/Commands/WorkflowInstances/ResumeWorkflowInstanceCommand.cs b/src/api/Synapse.Api.Application/Commands/WorkflowInstances/ResumeWorkflowInstanceCommand.cs index f66877a74..6a10a66bb 100644 --- a/src/api/Synapse.Api.Application/Commands/WorkflowInstances/ResumeWorkflowInstanceCommand.cs +++ b/src/api/Synapse.Api.Application/Commands/WorkflowInstances/ResumeWorkflowInstanceCommand.cs @@ -51,7 +51,7 @@ public virtual async Task HandleAsync(ResumeWorkflowInstanceCo var workflowInstanceReference = new ResourceReference(command.Name, command.Namespace); var original = await resources.GetAsync(command.Name, command.Namespace, cancellationToken).ConfigureAwait(false) ?? throw new ProblemDetailsException(new(ProblemTypes.NotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, ProblemDescriptions.ResourceNotFound.Format(workflowInstanceReference.ToString()))); - if (!string.IsNullOrWhiteSpace(original.Status?.Phase) && original.Status?.Phase != WorkflowInstanceStatusPhase.Waiting) throw new ProblemDetailsException(new(ProblemTypes.AdmissionFailed, ProblemTitles.AdmissionFailed, (int)HttpStatusCode.BadRequest, $"The workflow instance '{workflowInstanceReference}' is in an expected phase '{original.Status?.Phase}'")); + if (!string.IsNullOrWhiteSpace(original.Status?.Phase) && original.Status?.Phase != WorkflowInstanceStatusPhase.Suspended) throw new ProblemDetailsException(new(ProblemTypes.AdmissionFailed, ProblemTitles.AdmissionFailed, (int)HttpStatusCode.BadRequest, $"The workflow instance '{workflowInstanceReference}' is in an expected phase '{original.Status?.Phase}'")); var updated = original.Clone()!; updated.Status ??= new(); updated.Status.Phase = WorkflowInstanceStatusPhase.Running; diff --git a/src/api/Synapse.Api.Application/Commands/WorkflowInstances/SuspendWorkflowInstanceCommand.cs b/src/api/Synapse.Api.Application/Commands/WorkflowInstances/SuspendWorkflowInstanceCommand.cs index 7c0c12153..f8ab3d64d 100644 --- a/src/api/Synapse.Api.Application/Commands/WorkflowInstances/SuspendWorkflowInstanceCommand.cs +++ b/src/api/Synapse.Api.Application/Commands/WorkflowInstances/SuspendWorkflowInstanceCommand.cs @@ -54,7 +54,7 @@ public virtual async Task HandleAsync(SuspendWorkflowInstanceC if (original.Status?.Phase != WorkflowInstanceStatusPhase.Running) throw new ProblemDetailsException(new(ProblemTypes.AdmissionFailed, ProblemTitles.AdmissionFailed, (int)HttpStatusCode.BadRequest, $"The workflow instance '{workflowInstanceReference}' is in an expected phase '{original.Status?.Phase}'")); var updated = original.Clone()!; updated.Status ??= new(); - updated.Status.Phase = WorkflowInstanceStatusPhase.Waiting; + updated.Status.Phase = WorkflowInstanceStatusPhase.Suspended; var jsonPatch = JsonPatchUtility.CreateJsonPatchFromDiff(original, updated); await resources.PatchStatusAsync(new(PatchType.JsonPatch, jsonPatch), command.Name, command.Namespace, cancellationToken: cancellationToken).ConfigureAwait(false); return this.Ok(); diff --git a/src/core/Synapse.Core/WorkflowInstanceStatusPhase.cs b/src/core/Synapse.Core/WorkflowInstanceStatusPhase.cs index 7c9459c94..d01d3cde6 100644 --- a/src/core/Synapse.Core/WorkflowInstanceStatusPhase.cs +++ b/src/core/Synapse.Core/WorkflowInstanceStatusPhase.cs @@ -28,7 +28,11 @@ public static class WorkflowInstanceStatusPhase /// public const string Running = "running"; /// - /// Indicates that the workflow's execution is waiting for user or event input + /// Indicates that the workflow's execution has been suspended + /// + public const string Suspended = "suspended"; + /// + /// Indicates that the workflow's execution is waiting for event(s) /// public const string Waiting = "waiting"; /// @@ -44,4 +48,19 @@ public static class WorkflowInstanceStatusPhase /// public const string Faulted = "faulted"; + /// + /// Gets a new containing all workflow instance status phases + /// + /// A new containing all workflow instance status phases + public static IEnumerable AsEnumerable() + { + yield return Pending; + yield return Running; + yield return Suspended; + yield return Waiting; + yield return Completed; + yield return Cancelled; + yield return Faulted; + } + } \ No newline at end of file diff --git a/src/operator/Synapse.Operator/Services/WorkflowInstanceHandler.cs b/src/operator/Synapse.Operator/Services/WorkflowInstanceHandler.cs index f89105ab9..d0de68eb7 100644 --- a/src/operator/Synapse.Operator/Services/WorkflowInstanceHandler.cs +++ b/src/operator/Synapse.Operator/Services/WorkflowInstanceHandler.cs @@ -170,7 +170,7 @@ protected virtual async Task GetServiceAccountAsync(Cancellation /// A boolean indicating whether or not the handler should resume the execution of the handled workflow instance protected virtual bool ShouldResumeExecution(string? statusPhase) { - if (statusPhase == WorkflowInstanceStatusPhase.Waiting) + if (statusPhase == WorkflowInstanceStatusPhase.Suspended) { this._suspended = true; return false; diff --git a/src/runner/Synapse.Runner/Services/RunnerApplication.cs b/src/runner/Synapse.Runner/Services/RunnerApplication.cs index 86dfd9f21..1a7f9ec2d 100644 --- a/src/runner/Synapse.Runner/Services/RunnerApplication.cs +++ b/src/runner/Synapse.Runner/Services/RunnerApplication.cs @@ -124,7 +124,7 @@ protected virtual async Task OnHandleStatusPhaseChangedAsync(string? phase, Canc { switch (phase) { - case WorkflowInstanceStatusPhase.Waiting: + case WorkflowInstanceStatusPhase.Suspended: await this.OnSuspendAsync(cancellationToken).ConfigureAwait(false); break; case WorkflowInstanceStatusPhase.Cancelled: diff --git a/src/runner/Synapse.Runner/Services/WorkflowExecutionContext.cs b/src/runner/Synapse.Runner/Services/WorkflowExecutionContext.cs index af13cf913..4b1aa0f93 100644 --- a/src/runner/Synapse.Runner/Services/WorkflowExecutionContext.cs +++ b/src/runner/Synapse.Runner/Services/WorkflowExecutionContext.cs @@ -698,11 +698,11 @@ public virtual async Task SkipAsync(TaskInstance task, object? res /// public virtual async Task SuspendAsync(CancellationToken cancellationToken = default) { - if (this.Instance.Status?.Phase == WorkflowInstanceStatusPhase.Waiting) return; + if (this.Instance.Status?.Phase == WorkflowInstanceStatusPhase.Suspended) return; using var @lock = await this.Lock.LockAsync(cancellationToken).ConfigureAwait(false); var originalInstance = this.Instance.Clone(); this.Instance.Status ??= new(); - this.Instance.Status.Phase = WorkflowInstanceStatusPhase.Waiting; + this.Instance.Status.Phase = WorkflowInstanceStatusPhase.Suspended; var run = this.Instance.Status.Runs?.LastOrDefault(); if (run != null) run.EndedAt = DateTimeOffset.Now; var jsonPatch = JsonPatchUtility.CreateJsonPatchFromDiff(originalInstance, this.Instance); diff --git a/src/runner/Synapse.Runner/Services/WorkflowExecutor.cs b/src/runner/Synapse.Runner/Services/WorkflowExecutor.cs index 20a133c3c..26c3948dc 100644 --- a/src/runner/Synapse.Runner/Services/WorkflowExecutor.cs +++ b/src/runner/Synapse.Runner/Services/WorkflowExecutor.cs @@ -108,7 +108,7 @@ public virtual async Task ExecuteAsync(CancellationToken cancellationToken = def await this.StartAsync(this.CancellationTokenSource.Token).ConfigureAwait(false); break; case WorkflowInstanceStatusPhase.Running: - case WorkflowInstanceStatusPhase.Waiting: + case WorkflowInstanceStatusPhase.Suspended: await this.ResumeAsync(this.CancellationTokenSource.Token).ConfigureAwait(false); break; default: diff --git a/tests/Synapse.UnitTests/Services/MockWorkflowInstanceApiClient.cs b/tests/Synapse.UnitTests/Services/MockWorkflowInstanceApiClient.cs index 49cf10a41..5e758e623 100644 --- a/tests/Synapse.UnitTests/Services/MockWorkflowInstanceApiClient.cs +++ b/tests/Synapse.UnitTests/Services/MockWorkflowInstanceApiClient.cs @@ -37,7 +37,7 @@ public async Task SuspendAsync(string name, string @namespace, CancellationToken if (original.Status?.Phase != WorkflowInstanceStatusPhase.Running) throw new ProblemDetailsException(new(ProblemTypes.AdmissionFailed, ProblemTitles.AdmissionFailed, (int)HttpStatusCode.BadRequest, $"The workflow instance '{workflowInstanceReference}' is in an expected phase '{original.Status?.Phase}'")); var updated = original.Clone()!; updated.Status ??= new(); - updated.Status.Phase = WorkflowInstanceStatusPhase.Waiting; + updated.Status.Phase = WorkflowInstanceStatusPhase.Suspended; var jsonPatch = JsonPatchUtility.CreateJsonPatchFromDiff(original, updated); await this.Resources.PatchStatusAsync(new(PatchType.JsonPatch, jsonPatch), name, @namespace, cancellationToken: cancellationToken).ConfigureAwait(false); } @@ -49,7 +49,7 @@ public async Task ResumeAsync(string name, string @namespace, CancellationToken var workflowInstanceReference = new ResourceReference(name, @namespace); var original = await this.Resources.GetAsync(name, @namespace, cancellationToken).ConfigureAwait(false) ?? throw new ProblemDetailsException(new(ProblemTypes.NotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, ProblemDescriptions.ResourceNotFound.Format(workflowInstanceReference.ToString()))); - if (original.Status?.Phase != WorkflowInstanceStatusPhase.Waiting) throw new ProblemDetailsException(new(ProblemTypes.AdmissionFailed, ProblemTitles.AdmissionFailed, (int)HttpStatusCode.BadRequest, $"The workflow instance '{workflowInstanceReference}' is in an expected phase '{original.Status?.Phase}'")); + if (original.Status?.Phase != WorkflowInstanceStatusPhase.Suspended) throw new ProblemDetailsException(new(ProblemTypes.AdmissionFailed, ProblemTitles.AdmissionFailed, (int)HttpStatusCode.BadRequest, $"The workflow instance '{workflowInstanceReference}' is in an expected phase '{original.Status?.Phase}'")); var updated = original.Clone()!; updated.Status ??= new(); updated.Status.Phase = WorkflowInstanceStatusPhase.Running; @@ -64,7 +64,7 @@ public async Task CancelAsync(string name, string @namespace, CancellationToken var workflowInstanceReference = new ResourceReference(name, @namespace); var original = await this.Resources.GetAsync(name, @namespace, cancellationToken).ConfigureAwait(false) ?? throw new ProblemDetailsException(new(ProblemTypes.NotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, ProblemDescriptions.ResourceNotFound.Format(workflowInstanceReference.ToString()))); - if (original.Status?.Phase != WorkflowInstanceStatusPhase.Pending && original.Status?.Phase != WorkflowInstanceStatusPhase.Running && original.Status?.Phase != WorkflowInstanceStatusPhase.Waiting) + if (original.Status?.Phase != WorkflowInstanceStatusPhase.Pending && original.Status?.Phase != WorkflowInstanceStatusPhase.Running && original.Status?.Phase != WorkflowInstanceStatusPhase.Suspended) throw new ProblemDetailsException(new(ProblemTypes.AdmissionFailed, ProblemTitles.AdmissionFailed, (int)HttpStatusCode.BadRequest, $"The workflow instance '{workflowInstanceReference}' is in an expected phase '{original.Status?.Phase}'")); var updated = original.Clone()!; updated.Status ??= new();