diff --git a/awx/api/serializers.py b/awx/api/serializers.py index cda572a4ee..81ff15f0f7 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -3677,7 +3677,7 @@ class WorkflowJobTemplateNodeSerializer(LaunchConfigurationBaseSerializer): class Meta: model = WorkflowJobTemplateNode fields = ('*', 'workflow_job_template', '-name', '-description', 'id', 'url', 'related', - 'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes',) + 'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes', 'all_parents_must_converge',) def get_related(self, obj): res = super(WorkflowJobTemplateNodeSerializer, self).get_related(obj) @@ -3716,8 +3716,8 @@ class WorkflowJobNodeSerializer(LaunchConfigurationBaseSerializer): class Meta: model = WorkflowJobNode fields = ('*', 'job', 'workflow_job', '-name', '-description', 'id', 'url', 'related', - 'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes', - 'do_not_run',) + 'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes', + 'all_parents_must_converge', 'do_not_run',) def get_related(self, obj): res = super(WorkflowJobNodeSerializer, self).get_related(obj) diff --git a/awx/main/migrations/0102_v370_workflow_convergence_api_toggle.py b/awx/main/migrations/0102_v370_workflow_convergence_api_toggle.py new file mode 100644 index 0000000000..340949e8f6 --- /dev/null +++ b/awx/main/migrations/0102_v370_workflow_convergence_api_toggle.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.4 on 2020-01-07 19:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0100_v370_projectupdate_job_tags'), + ] + + operations = [ + migrations.AddField( + model_name='workflowjobnode', + name='all_parents_must_converge', + field=models.BooleanField(default=False, help_text='If enabled then the node will only run if all of the parent nodes have met the criteria to reach this node', verbose_name='self'), + ), + migrations.AddField( + model_name='workflowjobtemplatenode', + name='all_parents_must_converge', + field=models.BooleanField(default=False, help_text='If enabled then the node will only run if all of the parent nodes have met the criteria to reach this node', verbose_name='self'), + ), + ] diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index 2719d59c32..1617014540 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -698,12 +698,6 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique 'Credential', related_name='%(class)ss', ) - # convergence_behavior = models.TextField( - # blank=True, - # default='AND', - # editable=True, - # help_text=_('The behavior by a convergence node') - # ) def get_absolute_url(self, request=None): RealClass = self.get_real_instance_class() @@ -1451,4 +1445,3 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique @property def is_containerized(self): return False - diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index de2767e2d7..51733e10d4 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -79,6 +79,12 @@ class WorkflowNodeBase(CreatedModifiedModel, LaunchTimeConfig): symmetrical=False, related_name='%(class)ss_always', ) + all_parents_must_converge = models.BooleanField( + 'self', + default=False, + help_text=_("If enabled then the node will only run if all of the parent nodes " + "have met the criteria to reach this node") + ) unified_job_template = models.ForeignKey( 'UnifiedJobTemplate', related_name='%(class)ss', @@ -102,7 +108,7 @@ class WorkflowNodeBase(CreatedModifiedModel, LaunchTimeConfig): ''' return ['workflow_job', 'unified_job_template', 'extra_data', 'survey_passwords', - 'inventory', 'credentials', 'char_prompts'] + 'inventory', 'credentials', 'char_prompts', 'all_parents_must_converge'] def create_workflow_job_node(self, **kwargs): ''' @@ -208,11 +214,6 @@ class WorkflowJobNode(WorkflowNodeBase): "semantics will mark this True if the node is in a path that will " "decidedly not be ran. A value of False means the node may not run."), ) - all_parents_must_converge = models.BooleanField( - default=False, - help_text=_("If enabled then the node will only run if all of the parent nodes " - "have met the criteria to reach this node") - ) def get_absolute_url(self, request=None): return reverse('api:workflow_job_node_detail', kwargs={'pk': self.pk}, request=request) diff --git a/awx/main/scheduler/dag_workflow.py b/awx/main/scheduler/dag_workflow.py index de07751d3b..25e94b0a73 100644 --- a/awx/main/scheduler/dag_workflow.py +++ b/awx/main/scheduler/dag_workflow.py @@ -78,37 +78,23 @@ class WorkflowDAG(SimpleDAG): if obj.id in node_ids_visited: continue node_ids_visited.add(obj.id) - + # import sdb + # sdb.set_trace() if obj.do_not_run is True: continue + elif obj.job: + if obj.job.status in ['failed', 'error', 'canceled']: + nodes.extend(self.get_children(obj, 'failure_nodes') + + self.get_children(obj, 'always_nodes')) + elif obj.job.status == 'successful': + nodes.extend(self.get_children(obj, 'success_nodes') + + self.get_children(obj, 'always_nodes')) + elif obj.unified_job_template is None: + nodes.extend(self.get_children(obj, 'failure_nodes') + + self.get_children(obj, 'always_nodes')) else: - if obj.all_parents_must_converge is True: - if self._are_relevant_parents_finished(n): - # if the current node is a convergence node and all the - # relevant parents are finished then the node should run - parent_nodes = [p['node_object'] for p in self.get_parents(obj)] - successful_convergence = True - for p in parent_nodes: - if obj not in self.get_children(p, p.job.status): - # if the child list doesn't include the obj, then the parent didn't - # meet the criteria needed to run the child, meaning it's a DNR - successful_convergence = False - if successful_convergence == True: - nodes_found.append(n) - elif obj.all_parents_must_converge is False: - if obj.job: - if obj.job.status in ['failed', 'error', 'canceled']: - nodes.extend(self.get_children(obj, 'failure_nodes') + - self.get_children(obj, 'always_nodes')) - elif obj.job.status == 'successful': - nodes.extend(self.get_children(obj, 'success_nodes') + - self.get_children(obj, 'always_nodes')) - elif obj.unified_job_template is None: - nodes.extend(self.get_children(obj, 'failure_nodes') + - self.get_children(obj, 'always_nodes')) - else: - if self._are_relevant_parents_finished(n) is True: - nodes_found.append(n) + if self._are_relevant_parents_finished(n) is True: + nodes_found.append(n) return [n['node_object'] for n in nodes_found] def cancel_node_jobs(self): @@ -198,7 +184,6 @@ class WorkflowDAG(SimpleDAG): Return a boolean ''' def _should_mark_node_dnr(self, node, parent_nodes): - #BECCAH TODO Gonna have to update this too for p in parent_nodes: if p.do_not_run is True: pass @@ -234,5 +219,14 @@ class WorkflowDAG(SimpleDAG): if self._should_mark_node_dnr(node, parent_nodes): obj.do_not_run = True nodes_marked_do_not_run.append(node) + if obj.all_parents_must_converge: + if self._are_relevant_parents_finished(node): + # if the current node is a convergence node and all the + # parents are finished then check to see if all parents + # met their success criteria to run the convergence child + parent_nodes = [p['node_object'] for p in self.get_parents(obj)] + if not all(obj in self.get_children(p, p.job.status) for p in parent_nodes): + obj.do_not_run = True + nodes_marked_do_not_run.append(node) - return [n['node_object'] for n in nodes_marked_do_not_run] \ No newline at end of file + return [n['node_object'] for n in nodes_marked_do_not_run]