summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--awx/main/models/workflow.py18
-rw-r--r--awx/main/tests/functional/models/test_workflow.py52
-rw-r--r--docs/workflow.md16
3 files changed, 76 insertions, 10 deletions
diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py
index 2b23b0adbc..60a5f151f2 100644
--- a/awx/main/models/workflow.py
+++ b/awx/main/models/workflow.py
@@ -514,19 +514,21 @@ class WorkflowJob(UnifiedJob, WorkflowJobOptions, SurveyJobMixin, JobNotificatio
return 0
def get_ancestor_workflows(self):
+ """Returns a list of WFJTs that are indirect parents of this workflow job
+ say WFJTs are set up to spawn in order of A->B->C, and this workflow job
+ came from C, then C is the parent and [B, A] will be returned from this.
+ """
ancestors = []
- wj = self
- wj_ids = set([])
- while True:
- wj_ids.add(wj.id)
- wj = wj.get_workflow_job()
- if wj.id in wj_ids:
+ wj_ids = set([self.pk])
+ wj = self.get_workflow_job()
+ while wj and wj.workflow_job_template_id:
+ if wj.pk in wj_ids:
logger.critical('Cycles detected in the workflow jobs graph, '
'this is not normal and suggests task manager degeneracy.')
break
- if (not wj) or (not wj.workflow_job_template_id):
- break
+ wj_ids.add(wj.pk)
ancestors.append(wj.workflow_job_template_id)
+ wj = wj.get_workflow_job()
return ancestors
def get_notification_templates(self):
diff --git a/awx/main/tests/functional/models/test_workflow.py b/awx/main/tests/functional/models/test_workflow.py
index c61c9a1926..5b57326242 100644
--- a/awx/main/tests/functional/models/test_workflow.py
+++ b/awx/main/tests/functional/models/test_workflow.py
@@ -215,3 +215,55 @@ class TestWorkflowJobTemplate:
wfjt2.validate_unique()
wfjt2 = WorkflowJobTemplate(name='foo', organization=None)
wfjt2.validate_unique()
+
+
+@pytest.mark.django_db
+def test_workflow_ancestors(organization):
+ # Spawn order of templates grandparent -> parent -> child
+ # create child WFJT and workflow job
+ child = WorkflowJobTemplate.objects.create(organization=organization, name='child')
+ child_job = WorkflowJob.objects.create(
+ workflow_job_template=child,
+ launch_type='workflow'
+ )
+ # create parent WFJT and workflow job, and link it up
+ parent = WorkflowJobTemplate.objects.create(organization=organization, name='parent')
+ parent_job = WorkflowJob.objects.create(
+ workflow_job_template=parent,
+ launch_type='workflow'
+ )
+ WorkflowJobNode.objects.create(
+ workflow_job=parent_job,
+ unified_job_template=child,
+ job=child_job
+ )
+ # create grandparent WFJT and workflow job and link it up
+ grandparent = WorkflowJobTemplate.objects.create(organization=organization, name='grandparent')
+ grandparent_job = WorkflowJob.objects.create(
+ workflow_job_template=grandparent,
+ launch_type='schedule'
+ )
+ WorkflowJobNode.objects.create(
+ workflow_job=grandparent_job,
+ unified_job_template=parent,
+ job=parent_job
+ )
+ # ancestors method gives a list of WFJT ids
+ assert child_job.get_ancestor_workflows() == [parent.pk, grandparent.pk]
+
+
+@pytest.mark.django_db
+def test_workflow_ancestors_recursion_prevention(organization):
+ # This is toxic database data, this tests that it doesn't create an infinite loop
+ wfjt = WorkflowJobTemplate.objects.create(organization=organization, name='child')
+ wfj = WorkflowJob.objects.create(
+ workflow_job_template=wfjt,
+ launch_type='workflow'
+ )
+ WorkflowJobNode.objects.create(
+ workflow_job=wfj,
+ unified_job_template=wfjt,
+ job=wfj # well, this is a problem
+ )
+ # mostly, we just care that this assertion finishes in finite time
+ assert wfj.get_ancestor_workflows() == []
diff --git a/docs/workflow.md b/docs/workflow.md
index 7a16a70d57..6b3b713e14 100644
--- a/docs/workflow.md
+++ b/docs/workflow.md
@@ -1,7 +1,7 @@
## Tower Workflow Overview
Workflows are structured compositions of Tower job resources. The only job of a workflow is to trigger other jobs in specific orders to achieve certain goals, such as tracking the full set of jobs that were part of a release process as a single unit.
-A workflow has an associated tree-graph that is composed of multiple nodes. Each node in the tree has one associated job template (job template, inventory update, or project update) along with related resources that, if defined, will override the associated job template resources (i.e. credential, inventory, etc.) if the job template associated with the node is chosen to run.
+A workflow has an associated tree-graph that is composed of multiple nodes. Each node in the tree has one associated job template (job template, inventory update, project update, or workflow job template) along with related resources that, if defined, will override the associated job template resources (i.e. credential, inventory, etc.) if the job template associated with the node is chosen to run.
## Usage Manual
@@ -26,6 +26,18 @@ See the document on saved launch configurations for how these are processed
when the job is launched, and the API validation involved in building
the launch configurations on workflow nodes.
+#### Workflows as Workflow Nodes
+
+A workflow can be added as a node in another workflow. The child workflow is the associated
+`unified_job_template` that the node references, when that node is added to the parent workflow.
+When the parent workflow dispatches that node, then the child workflow will begin running, and
+the parent will resume execution of that branch when the child workflow finishes.
+Branching into success / failed pathways is decided based on the status of the child workflow.
+
+In the event that spawning the workflow would result in recursion, the child workflow
+will be marked as failed with a message explaining that recursion was detected.
+This is to prevent saturation of the task system with an infinite chain of workflows.
+
### Tree-Graph Formation and Restrictions
The tree-graph structure of a workflow is enforced by associating workflow job template nodes via endpoints `/workflow_job_template_nodes/\d+/*_nodes/`, where `*` has options `success`, `failure` and `always`. However there are restrictions that must be enforced when setting up new connections. Here are the three restrictions that will raise validation error when break:
* Cycle restriction: According to tree definition, no cycle is allowed.
@@ -83,7 +95,7 @@ Artifact support starts in Ansible and is carried through in Tower. The `set_sta
* No CRUD actions are possible on workflow job nodes by any user, and they may only be deleted by deleting their workflow job.
* Workflow jobs can be deleted by superusers and org admins of the organization of its associated workflow job template, and no one else.
* Verify that workflow job template nodes can be created under, or (dis)associated with workflow job templates.
-* Verify that only the permitted types of job template types can be associated with a workflow job template node. Currently the permitted types are *job templates, inventory sources and projects*.
+* Verify that the permitted types of job template types can be associated with a workflow job template node. Currently the permitted types are *job templates, inventory sources, projects, and workflow job templates*.
* Verify that workflow job template nodes under the same workflow job template can be associated to form parent-child relationship of decision trees. In specific, one node takes another as its child node by POSTing another node's id to one of the three endpoints: `/success_nodes/`, `/failure_nodes/` and `/always_nodes/`.
* Verify that workflow job template nodes are not allowed to have invalid association. Any attempt that causes invalidity will trigger 400-level response. The three types of invalid associations are cycle, convergence(multiple parent).
* Verify that a workflow job template can be successfully copied and the created workflow job template does not miss any field that should be copied or intentionally modified.