From 04d6fb1f95150c8c3fc7cea83bf8571a8fb45362 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 9 May 2017 09:42:53 -0400 Subject: [PATCH] SCM inventory cancel propagation For manually initiated inventory updates, also cancel the source project of "sync" type, like jobs do For automatic inventory updates spawned from source project update, of launch type "scm", handle contigency cases --- awx/main/models/inventory.py | 2 ++ awx/main/models/projects.py | 7 ++++++ awx/main/tasks.py | 9 +++++++ awx/main/tests/functional/test_tasks.py | 31 ++++++++++++++++++++++++- 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 3cd94c3446..00d873e658 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -1393,6 +1393,8 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin): res = super(InventoryUpdate, self).cancel(job_explanation=job_explanation) if res: map(lambda x: x.cancel(job_explanation=self._build_job_explanation()), self.get_dependent_jobs()) + if self.launch_type != 'scm' and self.source_project_update: + self.source_project_update.cancel(job_explanation=job_explanation) return res diff --git a/awx/main/models/projects.py b/awx/main/models/projects.py index 05125a8f8e..899141dca2 100644 --- a/awx/main/models/projects.py +++ b/awx/main/models/projects.py @@ -501,6 +501,13 @@ class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin): update_fields.append('scm_delete_on_next_update') parent_instance.save(update_fields=update_fields) + def cancel(self, job_explanation=None): + res = super(ProjectUpdate, self).cancel(job_explanation=job_explanation) + if res and self.launch_type != 'sync': + for inv_src in self.scm_inventory_updates.filter(status='running'): + inv_src.cancel(job_explanation='Source project update `{}` was canceled.'.format(self.name)) + return res + ''' JobNotificationMixin ''' diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 08a0b46bdc..14bfade27b 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -1391,6 +1391,15 @@ class RunProjectUpdate(BaseTask): except Exception as e: # A failed file update does not block other actions logger.error('Encountered error updating project dependent inventory: {}'.format(e)) + + # Stop all dependent inventory updates if project update was canceled + project_update.refresh_from_db() + if project_update.cancel_flag: + break + # Don't update inventory scm_revision if update was canceled + local_inv_update.refresh_from_db() + if local_inv_update.cancel_flag: + continue inv_src.scm_last_revision = scm_revision inv_src.save(update_fields=['scm_last_revision']) diff --git a/awx/main/tests/functional/test_tasks.py b/awx/main/tests/functional/test_tasks.py index ee80e85a7a..c0441536b1 100644 --- a/awx/main/tests/functional/test_tasks.py +++ b/awx/main/tests/functional/test_tasks.py @@ -3,7 +3,7 @@ import mock import os from awx.main.tasks import RunProjectUpdate, RunInventoryUpdate -from awx.main.models import ProjectUpdate, InventoryUpdate +from awx.main.models import ProjectUpdate, InventoryUpdate, InventorySource @pytest.fixture @@ -43,3 +43,32 @@ class TestDependentInventoryUpdate: inv_update = InventoryUpdate.objects.first() iu_run_mock.assert_called_once_with(inv_update.id) assert inv_update.source_project_update_id == proj_update.pk + + def test_dependent_inventory_project_cancel(self, project, inventory): + ''' + Test that dependent inventory updates exhibit good behavior on cancel + of the source project update + ''' + task = RunProjectUpdate() + proj_update = ProjectUpdate.objects.create(project=project) + + kwargs = dict( + source_project=project, + source='scm', + source_path='inventory_file', + update_on_project_update=True, + inventory=inventory + ) + + is1 = InventorySource.objects.create(name="test-scm-inv", **kwargs) + is2 = InventorySource.objects.create(name="test-scm-inv2", **kwargs) + + def user_cancels_project(pk): + ProjectUpdate.objects.all().update(cancel_flag=True) + + with mock.patch.object(RunInventoryUpdate, 'run') as iu_run_mock: + iu_run_mock.side_effect = user_cancels_project + task._update_dependent_inventories(proj_update, [is1, is2]) + # Verify that it bails after 1st update, detecting a cancel + assert is2.inventory_updates.count() == 0 + iu_run_mock.assert_called_once()