diff --git a/awx/api/views.py b/awx/api/views.py index ac0d49136d..f1047b6bfd 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -2869,6 +2869,14 @@ class WorkflowJobCancel(RetrieveAPIView): is_job_cancel = True new_in_310 = True + def post(self, request, *args, **kwargs): + obj = self.get_object() + if obj.can_cancel: + obj.cancel() + return Response(status=status.HTTP_202_ACCEPTED) + else: + return self.http_method_not_allowed(request, *args, **kwargs) + class SystemJobTemplateList(ListAPIView): model = SystemJobTemplate diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index 158efabeb4..892209ea50 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -424,3 +424,7 @@ class WorkflowJob(UnifiedJob, WorkflowJobOptions, JobNotificationMixin, Workflow def start_celery_task(self, opts, error_callback, success_callback): return None + def cancel(self): + if self.can_cancel: + self.status = 'canceled' + self.save(update_fields=['status']) diff --git a/awx/main/scheduler/__init__.py b/awx/main/scheduler/__init__.py index 7c7e9e5ab3..aa0e223420 100644 --- a/awx/main/scheduler/__init__.py +++ b/awx/main/scheduler/__init__.py @@ -130,7 +130,9 @@ class TaskManager(): def process_finished_workflow_jobs(self, workflow_jobs): for workflow_job in workflow_jobs: dag = WorkflowDAG(workflow_job) - if dag.is_workflow_done(): + if workflow_job.status == 'canceled': + dag.bfs_nodes_to_cancel() + elif dag.is_workflow_done(): if workflow_job._has_failed(): workflow_job.status = 'failed' else: diff --git a/awx/main/scheduler/dag_workflow.py b/awx/main/scheduler/dag_workflow.py index 7be2c70489..742c337dc0 100644 --- a/awx/main/scheduler/dag_workflow.py +++ b/awx/main/scheduler/dag_workflow.py @@ -48,6 +48,29 @@ class WorkflowDAG(SimpleDAG): nodes.extend(children_all) return [n['node_object'] for n in nodes_found] + def bfs_nodes_to_cancel(self): + root_nodes = self.get_root_nodes() + nodes = root_nodes + + for index, n in enumerate(nodes): + obj = n['node_object'] + job = obj.job + + if not job: + continue + elif job.can_cancel: + job.cancel() + elif job.status == 'failed': + children_failed = self.get_dependencies(obj, 'failure_nodes') + children_always = self.get_dependencies(obj, 'always_nodes') + children_all = children_failed + children_always + nodes.extend(children_all) + elif job.status == 'successful': + children_success = self.get_dependencies(obj, 'success_nodes') + children_always = self.get_dependencies(obj, 'always_nodes') + children_all = children_success + children_always + nodes.extend(children_all) + def is_workflow_done(self): root_nodes = self.get_root_nodes() nodes = root_nodes