From bbf185cd0b00c6e05d48226abda5593b612a23a7 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 27 Oct 2016 10:17:37 -0400 Subject: [PATCH 1/2] Add awx/public to clean target --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index a4b38e1cf6..8b79000e06 100644 --- a/Makefile +++ b/Makefile @@ -229,6 +229,7 @@ clean-venv: # Remove temporary build files, compiled Python files. clean: clean-rpm clean-deb clean-ui clean-tar clean-packer clean-bundle + rm -rf awx/public rm -rf awx/lib/site-packages rm -rf dist/* rm -rf tmp From 76fae6c05218e53226570460faa42881809e5c62 Mon Sep 17 00:00:00 2001 From: Alan Rominger Date: Thu, 27 Oct 2016 12:21:31 -0400 Subject: [PATCH 2/2] Revert "Introduce workflow failure condition" --- awx/api/serializers.py | 31 ++++++++------- ...rkflow_job_template_workflow_nodes_list.md | 14 ------- awx/api/views.py | 14 ------- .../0045_v310_workflow_failure_condition.py | 24 ------------ awx/main/models/workflow.py | 25 +----------- awx/main/scheduler/__init__.py | 8 ++-- .../tests/functional/models/test_workflow.py | 39 ------------------- .../tests/unit/models/test_workflow_unit.py | 4 +- 8 files changed, 21 insertions(+), 138 deletions(-) delete mode 100644 awx/api/templates/api/workflow_job_template_workflow_nodes_list.md delete mode 100644 awx/main/migrations/0045_v310_workflow_failure_condition.py diff --git a/awx/api/serializers.py b/awx/api/serializers.py index d681fac426..d6aa39f706 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -2242,22 +2242,18 @@ class WorkflowJobListSerializer(WorkflowJobSerializer, UnifiedJobListSerializer) pass class WorkflowNodeBaseSerializer(BaseSerializer): - job_type = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None) - job_tags = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None) - limit = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None) - skip_tags = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None) + job_type = serializers.SerializerMethodField() + job_tags = serializers.SerializerMethodField() + limit = serializers.SerializerMethodField() + skip_tags = serializers.SerializerMethodField() success_nodes = serializers.PrimaryKeyRelatedField(many=True, read_only=True) failure_nodes = serializers.PrimaryKeyRelatedField(many=True, read_only=True) always_nodes = serializers.PrimaryKeyRelatedField(many=True, read_only=True) - fail_on_job_failure = serializers.BooleanField( - help_text=('If set to true, and if the job runs and fails, ' - 'the workflow is marked as failed.'), - default=True) class Meta: fields = ('*', '-name', '-description', 'id', 'url', 'related', 'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes', - 'inventory', 'credential', 'job_type', 'job_tags', 'skip_tags', 'limit', 'skip_tags', 'fail_on_job_failure') + 'inventory', 'credential', 'job_type', 'job_tags', 'skip_tags', 'limit', 'skip_tags') def get_related(self, obj): res = super(WorkflowNodeBaseSerializer, self).get_related(obj) @@ -2265,12 +2261,17 @@ class WorkflowNodeBaseSerializer(BaseSerializer): res['unified_job_template'] = obj.unified_job_template.get_absolute_url() return res - def validate(self, attrs): - # char_prompts go through different validation, so remove them here - for fd in ['job_type', 'job_tags', 'skip_tags', 'limit']: - if fd in attrs: - attrs.pop(fd) - return super(WorkflowNodeBaseSerializer, self).validate(attrs) + def get_job_type(self, obj): + return obj.char_prompts.get('job_type', None) + + def get_job_tags(self, obj): + return obj.char_prompts.get('job_tags', None) + + def get_skip_tags(self, obj): + return obj.char_prompts.get('skip_tags', None) + + def get_limit(self, obj): + return obj.char_prompts.get('limit', None) class WorkflowJobTemplateNodeSerializer(WorkflowNodeBaseSerializer): diff --git a/awx/api/templates/api/workflow_job_template_workflow_nodes_list.md b/awx/api/templates/api/workflow_job_template_workflow_nodes_list.md deleted file mode 100644 index e6b078a23f..0000000000 --- a/awx/api/templates/api/workflow_job_template_workflow_nodes_list.md +++ /dev/null @@ -1,14 +0,0 @@ -# Workflow Job Template Workflow Node List - -Workflow nodes reference templates to execute and define the ordering -in which to execute them. After a job in this workflow finishes, -the subsequent actions are to: - - - run nodes contained in "failure_nodes" or "always_nodes" if job failed - - run nodes contained in "success_nodes" or "always_nodes" if job succeeded - -The workflow is marked as failed if any jobs run as part of that workflow fail -and have the field `fail_on_job_failure` set to true. If not, the workflow -job is marked as successful. - -{% include "api/sub_list_create_api_view.md" %} \ No newline at end of file diff --git a/awx/api/views.py b/awx/api/views.py index d22c030965..66980e141d 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -2404,7 +2404,6 @@ class JobTemplateLabelList(DeleteLastUnattachLabelMixin, SubListCreateAttachDeta serializer_class = LabelSerializer parent_model = JobTemplate relationship = 'labels' - new_in_300 = True def post(self, request, *args, **kwargs): # If a label already exists in the database, attach it instead of erroring out @@ -2698,7 +2697,6 @@ class WorkflowJobTemplateList(ListCreateAPIView): model = WorkflowJobTemplate serializer_class = WorkflowJobTemplateListSerializer always_allow_superuser = False - new_in_310 = True # TODO: RBAC ''' @@ -2716,12 +2714,10 @@ class WorkflowJobTemplateDetail(RetrieveUpdateDestroyAPIView): model = WorkflowJobTemplate serializer_class = WorkflowJobTemplateSerializer always_allow_superuser = False - new_in_310 = True class WorkflowJobTemplateLabelList(JobTemplateLabelList): parent_model = WorkflowJobTemplate - new_in_310 = True # TODO: @@ -2729,7 +2725,6 @@ class WorkflowJobTemplateLaunch(GenericAPIView): model = WorkflowJobTemplate serializer_class = EmptySerializer - new_in_310 = True def get(self, request, *args, **kwargs): data = {} @@ -2755,12 +2750,6 @@ class WorkflowJobTemplateWorkflowNodesList(SubListCreateAPIView): parent_model = WorkflowJobTemplate relationship = 'workflow_job_template_nodes' parent_key = 'workflow_job_template' - new_in_310 = True - - def update_raw_data(self, data): - for fd in ['job_type', 'job_tags', 'skip_tags', 'limit', 'skip_tags']: - data[fd] = None - return super(WorkflowJobTemplateWorkflowNodesList, self).update_raw_data(data) # TODO: class WorkflowJobTemplateJobsList(SubListAPIView): @@ -2776,14 +2765,12 @@ class WorkflowJobList(ListCreateAPIView): model = WorkflowJob serializer_class = WorkflowJobListSerializer - new_in_310 = True # TODO: class WorkflowJobDetail(RetrieveDestroyAPIView): model = WorkflowJob serializer_class = WorkflowJobSerializer - new_in_310 = True class WorkflowJobWorkflowNodesList(SubListAPIView): @@ -2793,7 +2780,6 @@ class WorkflowJobWorkflowNodesList(SubListAPIView): parent_model = WorkflowJob relationship = 'workflow_job_nodes' parent_key = 'workflow_job' - new_in_310 = True class SystemJobTemplateList(ListAPIView): diff --git a/awx/main/migrations/0045_v310_workflow_failure_condition.py b/awx/main/migrations/0045_v310_workflow_failure_condition.py deleted file mode 100644 index 9bafa0feb6..0000000000 --- a/awx/main/migrations/0045_v310_workflow_failure_condition.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0044_v310_project_playbook_files'), - ] - - operations = [ - migrations.AddField( - model_name='workflowjobnode', - name='fail_on_job_failure', - field=models.BooleanField(default=True), - ), - migrations.AddField( - model_name='workflowjobtemplatenode', - name='fail_on_job_failure', - field=models.BooleanField(default=True), - ), - ] diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index 3bc0608cfc..2848b38a4a 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -60,10 +60,6 @@ class WorkflowNodeBase(CreatedModifiedModel): default=None, on_delete=models.SET_NULL, ) - fail_on_job_failure = models.BooleanField( - blank=True, - default=True, - ) # Prompting-related fields inventory = models.ForeignKey( 'Inventory', @@ -97,22 +93,6 @@ class WorkflowNodeBase(CreatedModifiedModel): data[fd] = self.char_prompts[fd] return data - @property - def job_type(self): - return self.char_prompts.get('job_type', None) - - @property - def job_tags(self): - return self.char_prompts.get('job_tags', None) - - @property - def skip_tags(self): - return self.char_prompts.get('skip_tags', None) - - @property - def limit(self): - return self.char_prompts.get('limit', None) - def get_prompts_warnings(self): ujt_obj = self.unified_job_template if ujt_obj is None: @@ -157,7 +137,7 @@ class WorkflowNodeBase(CreatedModifiedModel): Return field names that should be copied from template node to job node. ''' return ['workflow_job', 'unified_job_template', - 'inventory', 'credential', 'char_prompts', 'fail_on_job_failure'] + 'inventory', 'credential', 'char_prompts'] class WorkflowJobTemplateNode(WorkflowNodeBase): # TODO: Ensure the API forces workflow_job_template being set @@ -403,9 +383,6 @@ class WorkflowJob(UnifiedJob, WorkflowJobOptions, JobNotificationMixin, Workflow from awx.main.tasks import RunWorkflowJob return RunWorkflowJob - def _has_failed(self): - return self.workflow_job_nodes.filter(job__status='failed', fail_on_job_failure=True).exists() - def socketio_emit_data(self): return {} diff --git a/awx/main/scheduler/__init__.py b/awx/main/scheduler/__init__.py index 6ecdc09b37..b93ce6956e 100644 --- a/awx/main/scheduler/__init__.py +++ b/awx/main/scheduler/__init__.py @@ -73,12 +73,10 @@ def process_finished_workflow_jobs(workflow_jobs): dag = WorkflowDAG(workflow_job) if dag.is_workflow_done(): with transaction.atomic(): - if workflow_job._has_failed(): - workflow_job.status = 'failed' - else: - workflow_job.status = 'successful' + # TODO: detect if wfj failed + workflow_job.status = 'completed' workflow_job.save() - workflow_job.websocket_emit_status(workflow_job.status) + workflow_job.websocket_emit_status('completed') def rebuild_graph(): """Regenerate the task graph by refreshing known tasks from Tower, purging diff --git a/awx/main/tests/functional/models/test_workflow.py b/awx/main/tests/functional/models/test_workflow.py index aa622d996a..cc61c34ed9 100644 --- a/awx/main/tests/functional/models/test_workflow.py +++ b/awx/main/tests/functional/models/test_workflow.py @@ -90,42 +90,3 @@ class TestWorkflowJobTemplate: assert len(parent_qs) == 1 assert parent_qs[0] == wfjt.workflow_job_template_nodes.all()[1] -@pytest.mark.django_db -class TestWorkflowJobFailure: - @pytest.fixture - def wfj(self): - return WorkflowJob.objects.create(name='test-wf-job') - - def test_workflow_has_failed(self, wfj): - """ - Test that a single failed node with fail_on_job_failure = true - leads to the entire WF being marked as failed - """ - job = Job.objects.create(name='test-job', status='failed') - # Node has a failed job connected - WorkflowJobNode.objects.create(workflow_job=wfj, job=job) - assert wfj._has_failed() - - def test_workflow_not_failed_unran_job(self, wfj): - """ - Test that an un-ran node will not mark workflow job as failed - """ - WorkflowJobNode.objects.create(workflow_job=wfj) - assert not wfj._has_failed() - - def test_workflow_not_failed_successful_job(self, wfj): - """ - Test that a sucessful node will not mark workflow job as failed - """ - job = Job.objects.create(name='test-job', status='successful') - WorkflowJobNode.objects.create(workflow_job=wfj, job=job) - assert not wfj._has_failed() - - def test_workflow_not_failed_failed_job_but_okay(self, wfj): - """ - Test that a failed node will not mark workflow job as failed - if the fail_on_job_failure is set to false - """ - job = Job.objects.create(name='test-job', status='failed') - WorkflowJobNode.objects.create(workflow_job=wfj, job=job, fail_on_job_failure=False) - assert not wfj._has_failed() diff --git a/awx/main/tests/unit/models/test_workflow_unit.py b/awx/main/tests/unit/models/test_workflow_unit.py index bc7e9b5bce..9df61ffe97 100644 --- a/awx/main/tests/unit/models/test_workflow_unit.py +++ b/awx/main/tests/unit/models/test_workflow_unit.py @@ -139,7 +139,6 @@ class TestWorkflowJobCreate: char_prompts=wfjt_node_no_prompts.char_prompts, inventory=None, credential=None, unified_job_template=wfjt_node_no_prompts.unified_job_template, - fail_on_job_failure=True, workflow_job=workflow_job_unit) def test_create_with_prompts(self, wfjt_node_with_prompts, workflow_job_unit, mocker): @@ -151,7 +150,6 @@ class TestWorkflowJobCreate: inventory=wfjt_node_with_prompts.inventory, credential=wfjt_node_with_prompts.credential, unified_job_template=wfjt_node_with_prompts.unified_job_template, - fail_on_job_failure=True, workflow_job=workflow_job_unit) @mock.patch('awx.main.models.workflow.WorkflowNodeBase.get_parent_nodes', lambda self: []) @@ -217,7 +215,7 @@ class TestWorkflowWarnings: def test_warn_scan_errors_node_prompts(self, job_node_with_prompts): job_node_with_prompts.unified_job_template.job_type = 'scan' - job_node_with_prompts.char_prompts['job_type'] = 'run' + job_node_with_prompts.job_type = 'run' job_node_with_prompts.inventory = Inventory(name='different-inventory', pk=23) assert 'ignored' in job_node_with_prompts.get_prompts_warnings() assert 'job_type' in job_node_with_prompts.get_prompts_warnings()['ignored']