From 0154c41eb2411bf685025d20eb2f3d8f6d7dfd06 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 3 Oct 2016 13:43:44 -0400 Subject: [PATCH] switch to UJT method of creating new jobs --- awx/main/models/workflow.py | 26 ++- .../tests/unit/models/test_workflow_unit.py | 162 +++++++++++------- 2 files changed, 121 insertions(+), 67 deletions(-) diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index 94a5706942..91983ca673 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -124,6 +124,14 @@ class WorkflowNodeBase(CreatedModifiedModel): data['missing'] = missing_dict return data + @classmethod + def _get_workflow_job_field_names(cls): + ''' + Return field names that should be copied from template node to job node. + ''' + return ['workflow_job', 'unified_job_template', + 'inventory', 'credential', 'char_prompts'] + class WorkflowJobTemplateNode(WorkflowNodeBase): # TODO: Ensure the API forces workflow_job_template being set workflow_job_template = models.ForeignKey( @@ -138,6 +146,18 @@ class WorkflowJobTemplateNode(WorkflowNodeBase): def get_absolute_url(self): return reverse('api:workflow_job_template_node_detail', args=(self.pk,)) + def create_workflow_job_node(self, **kwargs): + ''' + Create a new workflow job node based on this workflow node. + ''' + create_kwargs = {} + for field_name in self._get_workflow_job_field_names(): + if field_name in kwargs: + create_kwargs[field_name] = kwargs[field_name] + elif hasattr(self, field_name): + create_kwargs[field_name] = getattr(self, field_name) + return WorkflowJobNode.objects.create(**create_kwargs) + class WorkflowJobNode(WorkflowNodeBase): job = models.ForeignKey( 'UnifiedJob', @@ -278,11 +298,7 @@ class WorkflowJobInheritNodesMixin(object): Create a WorkflowJobNode for each WorkflowJobTemplateNode ''' def _create_workflow_job_nodes(self, old_nodes): - return [WorkflowJobNode.objects.create( - workflow_job=self, unified_job_template=old_node.unified_job_template, - inventory=old_node.inventory, credential=old_node.credential, - char_prompts=old_node.char_prompts - ) for old_node in old_nodes] + return [old_node.create_workflow_job_node(workflow_job=self) for old_node in old_nodes] def _map_workflow_job_nodes(self, old_nodes, new_nodes): node_ids_map = {} diff --git a/awx/main/tests/unit/models/test_workflow_unit.py b/awx/main/tests/unit/models/test_workflow_unit.py index 5fe7bfebd7..4445a758d9 100644 --- a/awx/main/tests/unit/models/test_workflow_unit.py +++ b/awx/main/tests/unit/models/test_workflow_unit.py @@ -3,7 +3,7 @@ import pytest from awx.main.models.jobs import JobTemplate from awx.main.models import Inventory, Credential, Project from awx.main.models.workflow import ( - WorkflowJobTemplateNode, WorkflowJobInheritNodesMixin, + WorkflowJobTemplate, WorkflowJobTemplateNode, WorkflowJobInheritNodesMixin, WorkflowJob, WorkflowJobNode ) @@ -18,15 +18,13 @@ class TestWorkflowJobInheritNodesMixin(): return [WorkflowJobTemplateNode(unified_job_template=job_templates[i]) for i in range(0, 10)] def test__create_workflow_job_nodes(self, mocker, job_template_nodes): - workflow_job_node_create = mocker.patch('awx.main.models.WorkflowJobNode.objects.create') + workflow_job_node_create = mocker.patch('awx.main.models.WorkflowJobTemplateNode.create_workflow_job_node') mixin = WorkflowJobInheritNodesMixin() mixin._create_workflow_job_nodes(job_template_nodes) for job_template_node in job_template_nodes: - workflow_job_node_create.assert_any_call( - workflow_job=mixin, unified_job_template=job_template_node.unified_job_template, - credential=None, inventory=None, char_prompts={}) + workflow_job_node_create.assert_any_call(workflow_job=mixin) class TestMapWorkflowJobNodes(): @pytest.fixture @@ -89,14 +87,18 @@ def workflow_job_unit(): return WorkflowJob(name='workflow', status='new') @pytest.fixture -def node_no_prompts(workflow_job_unit, job_template_factory): +def workflow_job_template_unit(): + return WorkflowJobTemplate(name='workflow') + +@pytest.fixture +def jt_ask(job_template_factory): # note: factory sets ask_xxxx_on_launch to true for inventory & credential jt = job_template_factory(name='example-jt', persisted=False).job_template jt.ask_job_type_on_launch = True jt.ask_skip_tags_on_launch = True jt.ask_limit_on_launch = True jt.ask_tags_on_launch = True - return WorkflowJobNode(workflow_job=workflow_job_unit, unified_job_template=jt) + return jt @pytest.fixture def project_unit(): @@ -105,13 +107,49 @@ def project_unit(): example_prompts = dict(job_type='check', job_tags='quack', limit='duck', skip_tags='oink') @pytest.fixture -def node_with_prompts(node_no_prompts): - node_no_prompts.char_prompts = example_prompts - inv = Inventory(name='example-inv') - cred = Credential(name='example-inv', kind='ssh', username='asdf', password='asdf') - node_no_prompts.inventory = inv - node_no_prompts.credential = cred - return node_no_prompts +def job_node_no_prompts(workflow_job_unit, jt_ask): + return WorkflowJobNode(workflow_job=workflow_job_unit, unified_job_template=jt_ask) + +@pytest.fixture +def job_node_with_prompts(job_node_no_prompts): + job_node_no_prompts.char_prompts = example_prompts + job_node_no_prompts.inventory = Inventory(name='example-inv') + job_node_no_prompts.credential = Credential(name='example-inv', kind='ssh', username='asdf', password='asdf') + return job_node_no_prompts + +@pytest.fixture +def wfjt_node_no_prompts(workflow_job_template_unit, jt_ask): + return WorkflowJobTemplateNode(workflow_job_template=workflow_job_template_unit, unified_job_template=jt_ask) + +@pytest.fixture +def wfjt_node_with_prompts(wfjt_node_no_prompts): + wfjt_node_no_prompts.char_prompts = example_prompts + wfjt_node_no_prompts.inventory = Inventory(name='example-inv') + wfjt_node_no_prompts.credential = Credential(name='example-inv', kind='ssh', username='asdf', password='asdf') + return wfjt_node_no_prompts + +class TestWorkflowJobCreate: + + def test_create_no_prompts(self, wfjt_node_no_prompts, workflow_job_unit, mocker): + mock_create = mocker.MagicMock() + with mocker.patch('awx.main.models.WorkflowJobNode.objects.create', mock_create): + wfjt_node_no_prompts.create_workflow_job_node(workflow_job=workflow_job_unit) + mock_create.assert_called_once_with( + char_prompts=wfjt_node_no_prompts.char_prompts, + inventory=None, credential=None, + unified_job_template=wfjt_node_no_prompts.unified_job_template, + workflow_job=workflow_job_unit) + + def test_create_with_prompts(self, wfjt_node_with_prompts, workflow_job_unit, mocker): + mock_create = mocker.MagicMock() + with mocker.patch('awx.main.models.WorkflowJobNode.objects.create', mock_create): + wfjt_node_with_prompts.create_workflow_job_node(workflow_job=workflow_job_unit) + mock_create.assert_called_once_with( + char_prompts=wfjt_node_with_prompts.char_prompts, + inventory=wfjt_node_with_prompts.inventory, + credential=wfjt_node_with_prompts.credential, + unified_job_template=wfjt_node_with_prompts.unified_job_template, + workflow_job=workflow_job_unit) class TestWorkflowJobNodeJobKWARGS: """ @@ -119,33 +157,33 @@ class TestWorkflowJobNodeJobKWARGS: launching a new job that corresponds to a workflow node. """ - def test_null_kwargs(self, node_no_prompts): - assert node_no_prompts.get_job_kwargs() == {} + def test_null_kwargs(self, job_node_no_prompts): + assert job_node_no_prompts.get_job_kwargs() == {} - def test_inherit_workflow_job_extra_vars(self, node_no_prompts): - workflow_job = node_no_prompts.workflow_job + def test_inherit_workflow_job_extra_vars(self, job_node_no_prompts): + workflow_job = job_node_no_prompts.workflow_job workflow_job.extra_vars = '{"a": 84}' - assert node_no_prompts.get_job_kwargs() == {'extra_vars': {'a': 84}} + assert job_node_no_prompts.get_job_kwargs() == {'extra_vars': {'a': 84}} - def test_char_prompts_and_res_node_prompts(self, node_with_prompts): - assert node_with_prompts.get_job_kwargs() == dict( - inventory=node_with_prompts.inventory.pk, - credential=node_with_prompts.credential.pk, + def test_char_prompts_and_res_node_prompts(self, job_node_with_prompts): + assert job_node_with_prompts.get_job_kwargs() == dict( + inventory=job_node_with_prompts.inventory.pk, + credential=job_node_with_prompts.credential.pk, **example_prompts) - def test_reject_some_node_prompts(self, node_with_prompts): - node_with_prompts.unified_job_template.ask_inventory_on_launch = False - node_with_prompts.unified_job_template.ask_job_type_on_launch = False - expect_kwargs = dict(inventory=node_with_prompts.inventory.pk, - credential=node_with_prompts.credential.pk, + def test_reject_some_node_prompts(self, job_node_with_prompts): + job_node_with_prompts.unified_job_template.ask_inventory_on_launch = False + job_node_with_prompts.unified_job_template.ask_job_type_on_launch = False + expect_kwargs = dict(inventory=job_node_with_prompts.inventory.pk, + credential=job_node_with_prompts.credential.pk, **example_prompts) expect_kwargs.pop('inventory') expect_kwargs.pop('job_type') - assert node_with_prompts.get_job_kwargs() == expect_kwargs + assert job_node_with_prompts.get_job_kwargs() == expect_kwargs - def test_no_accepted_project_node_prompts(self, node_with_prompts, project_unit): - node_with_prompts.unified_job_template = project_unit - assert node_with_prompts.get_job_kwargs() == {} + def test_no_accepted_project_node_prompts(self, job_node_no_prompts, project_unit): + job_node_no_prompts.unified_job_template = project_unit + assert job_node_no_prompts.get_job_kwargs() == {} class TestWorkflowWarnings: @@ -153,38 +191,38 @@ class TestWorkflowWarnings: Tests of warnings that show user errors in the construction of a workflow """ - def test_no_warn_project_node_no_prompts(self, node_no_prompts, project_unit): - node_no_prompts.unified_job_template = project_unit - assert node_no_prompts.get_prompts_warnings() == {} + def test_no_warn_project_node_no_prompts(self, job_node_no_prompts, project_unit): + job_node_no_prompts.unified_job_template = project_unit + assert job_node_no_prompts.get_prompts_warnings() == {} - def test_warn_project_node_reject_all_prompts(self, node_with_prompts, project_unit): - node_with_prompts.unified_job_template = project_unit - assert 'ignored' in node_with_prompts.get_prompts_warnings() - assert 'all' in node_with_prompts.get_prompts_warnings()['ignored'] + def test_warn_project_node_reject_all_prompts(self, job_node_with_prompts, project_unit): + job_node_with_prompts.unified_job_template = project_unit + assert 'ignored' in job_node_with_prompts.get_prompts_warnings() + assert 'all' in job_node_with_prompts.get_prompts_warnings()['ignored'] - def test_no_warn_accept_all_prompts(self, node_with_prompts): - assert node_with_prompts.get_prompts_warnings() == {} + def test_no_warn_accept_all_prompts(self, job_node_with_prompts): + assert job_node_with_prompts.get_prompts_warnings() == {} - def test_warn_reject_some_prompts(self, node_with_prompts): - node_with_prompts.unified_job_template.ask_credential_on_launch = False - node_with_prompts.unified_job_template.ask_job_type_on_launch = False - assert 'ignored' in node_with_prompts.get_prompts_warnings() - assert 'job_type' in node_with_prompts.get_prompts_warnings()['ignored'] - assert 'credential' in node_with_prompts.get_prompts_warnings()['ignored'] - assert len(node_with_prompts.get_prompts_warnings()['ignored']) == 2 + def test_warn_reject_some_prompts(self, job_node_with_prompts): + job_node_with_prompts.unified_job_template.ask_credential_on_launch = False + job_node_with_prompts.unified_job_template.ask_job_type_on_launch = False + assert 'ignored' in job_node_with_prompts.get_prompts_warnings() + assert 'job_type' in job_node_with_prompts.get_prompts_warnings()['ignored'] + assert 'credential' in job_node_with_prompts.get_prompts_warnings()['ignored'] + assert len(job_node_with_prompts.get_prompts_warnings()['ignored']) == 2 - def test_warn_scan_errors_node_prompts(self, node_with_prompts): - node_with_prompts.unified_job_template.job_type = 'scan' - node_with_prompts.job_type = 'run' - node_with_prompts.inventory = Inventory(name='different-inventory', pk=23) - assert 'ignored' in node_with_prompts.get_prompts_warnings() - assert 'job_type' in node_with_prompts.get_prompts_warnings()['ignored'] - assert 'inventory' in node_with_prompts.get_prompts_warnings()['ignored'] - assert len(node_with_prompts.get_prompts_warnings()['ignored']) == 2 + 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.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'] + assert 'inventory' in job_node_with_prompts.get_prompts_warnings()['ignored'] + assert len(job_node_with_prompts.get_prompts_warnings()['ignored']) == 2 - def test_warn_missing_fields(self, node_no_prompts): - node_no_prompts.inventory = None - assert 'missing' in node_no_prompts.get_prompts_warnings() - assert 'inventory' in node_no_prompts.get_prompts_warnings()['missing'] - assert 'credential' in node_no_prompts.get_prompts_warnings()['missing'] - assert len(node_no_prompts.get_prompts_warnings()['missing']) == 2 + def test_warn_missing_fields(self, job_node_no_prompts): + job_node_no_prompts.inventory = None + assert 'missing' in job_node_no_prompts.get_prompts_warnings() + assert 'inventory' in job_node_no_prompts.get_prompts_warnings()['missing'] + assert 'credential' in job_node_no_prompts.get_prompts_warnings()['missing'] + assert len(job_node_no_prompts.get_prompts_warnings()['missing']) == 2