Skip to content

Commit

Permalink
#1347 Making sure workflow nodes in error keep the current execution …
Browse files Browse the repository at this point in the history
…output
  • Loading branch information
dcoraboeuf committed Sep 13, 2024
1 parent 0ab833a commit ef3b346
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ class WorkflowEngineImpl(
instance.errorNode(
node.id,
throwable = null,
message = result.message
message = result.message,
output = result.output,
)
)
stopWorkflow(workflowInstanceId)
Expand Down Expand Up @@ -154,7 +155,7 @@ class WorkflowEngineImpl(
}
} catch (any: Throwable) {
// Stores the node error status
workflowInstanceStore.store(instance.errorNode(node.id, throwable = any, message = null))
workflowInstanceStore.store(instance.errorNode(node.id, throwable = any, message = null, output = null))
// Stopping the workflow
stopWorkflow(workflowInstanceId)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ data class WorkflowInstance(
node.success(output)
}

fun errorNode(nodeId: String, throwable: Throwable?, message: String?) = updateNode(nodeId) { node ->
node.error(throwable, message)
fun errorNode(nodeId: String, throwable: Throwable?, message: String?, output: JsonNode?) = updateNode(nodeId) { node ->
node.error(throwable, message, output)
}

fun progressNode(nodeId: String, output: JsonNode) = updateNode(nodeId) { node ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ data class WorkflowInstanceNode(
error = null,
)

fun error(throwable: Throwable?, message: String?, time: LocalDateTime = Time.now) = WorkflowInstanceNode(
fun error(throwable: Throwable?, message: String?, output: JsonNode?, time: LocalDateTime = Time.now) = WorkflowInstanceNode(
id = id,
status = WorkflowInstanceNodeStatus.ERROR,
startTime = startTime,
endTime = time,
output = null,
output = output ?: this.output,
error = message ?: throwable?.message ?: "Unknown error in $id node",
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ data class WorkflowNodeExecutorResult(
) {
companion object {

fun error(message: String) = WorkflowNodeExecutorResult(
fun error(message: String, output: JsonNode?) = WorkflowNodeExecutorResult(
type = WorkflowNodeExecutorResultType.ERROR,
message = message,
output = null,
output = output,
)

fun success(output: JsonNode?) = WorkflowNodeExecutorResult(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,14 @@ class WorkflowNotificationChannelNodeExecutor(
return if (result != null) {
when (result.type) {
NotificationResultType.OK -> WorkflowNodeExecutorResult.success(result.output?.asJson())
NotificationResultType.ONGOING -> WorkflowNodeExecutorResult.error("Notification is still ongoing")
NotificationResultType.NOT_CONFIGURED -> WorkflowNodeExecutorResult.error("Notification is not configured")
NotificationResultType.INVALID_CONFIGURATION -> WorkflowNodeExecutorResult.error("Notification configuration is invalid")
NotificationResultType.DISABLED -> WorkflowNodeExecutorResult.error("Notification is disabled")
NotificationResultType.ERROR -> WorkflowNodeExecutorResult.error(result.message ?: "Unknown error")
NotificationResultType.ONGOING -> WorkflowNodeExecutorResult.error("Notification is still ongoing", result.output?.asJson())
NotificationResultType.NOT_CONFIGURED -> WorkflowNodeExecutorResult.error("Notification is not configured", result.output?.asJson())
NotificationResultType.INVALID_CONFIGURATION -> WorkflowNodeExecutorResult.error("Notification configuration is invalid", result.output?.asJson())
NotificationResultType.DISABLED -> WorkflowNodeExecutorResult.error("Notification is disabled", result.output?.asJson())
NotificationResultType.ERROR -> WorkflowNodeExecutorResult.error(result.message ?: "Unknown error", result.output?.asJson())
}
} else {
WorkflowNodeExecutorResult.error("Notification did not return any result")
WorkflowNodeExecutorResult.error("Notification did not return any result", null)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,88 @@ class ACCDSLWorkflowNotificationChannel : AbstractACCDSLWorkflowsTestSupport() {
}
}

@Test
fun `Workflow node must still contain the build URL in its output when the remote job fails`() {
// Job to call
val job = uid("job_")
// Jenkins configuration
val jenkinsConfName = uid("j_")
ontrack.configurations.jenkins.create(
JenkinsConfiguration(
name = jenkinsConfName,
url = "any",
user = "any",
password = "any",
)
)
// Defining a workflow
val workflowName = uid("w-")
val yaml = """
name: $workflowName
nodes:
- id: start
executorId: notification
data:
channel: mock-jenkins
channelConfig:
config: $jenkinsConfName
job: /mock/$job
callMode: SYNC
parameters:
- name: result
value: FAILURE
- name: waiting
value: 1000
""".trimIndent()

project {
// Subscribe to new branches
subscribe(
name = "Test",
channel = "workflow",
channelConfig = mapOf(
"workflow" to WorkflowTestSupport.yamlWorkflowToJson(yaml)
),
keywords = null,
events = listOf(
"new_branch",
),
)
// Creating a branch to trigger the workflow
branch {}

// Gets the workflow instance ID from the output of the notification


waitUntil(
task = "Getting the workflow instance id",
timeout = 30_000L,
interval = 500L,
) {
val instanceId = getWorkflowInstanceId()
instanceId.isNullOrBlank().not()
}

// Getting the instance ID
val instanceId = getWorkflowInstanceId() ?: fail("Cannot get the workflow instance ID")

// Waits until the workflow is finished
val instance = waitUntilWorkflowFinished(
instanceId = instanceId,
returnInstanceOnError = true,
)

// Checks that the node is marked as success & contains the build URL
assertNotNull(instance.getWorkflowInstanceNode("start"), "Node accessed") { node ->
assertTrue(!node.error.isNullOrBlank(), "There is an error")
assertNotNull(node.output) { output ->
val buildUrl = output.path("buildUrl").asText()
assertTrue(buildUrl.isNotBlank(), "Build URL must not be blank")
}
}
}
}

@Test
fun `Workflow node contains the build URL while the node is running`() {
// Job to call
Expand Down Expand Up @@ -146,9 +228,8 @@ class ACCDSLWorkflowNotificationChannel : AbstractACCDSLWorkflowsTestSupport() {
branch {}

// Gets the workflow instance ID from the output of the notification


waitUntil(
task = "Getting the workflow instance id",
timeout = 30_000L,
interval = 500L,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ data class WorkflowInstance(
val finished: Boolean,
val nodesExecutions: List<WorkflowInstanceNode>,
) {
fun getExecutionOutput(nodeId: String): JsonNode? {
return nodesExecutions.firstOrNull { it.id == nodeId }?.output
fun getWorkflowInstanceNode(nodeId: String): WorkflowInstanceNode? {
return nodesExecutions.firstOrNull { it.id == nodeId }
}

fun getExecutionOutput(nodeId: String): JsonNode? =
getWorkflowInstanceNode(nodeId)?.output
}

0 comments on commit ef3b346

Please sign in to comment.