diff --git a/awx/main/signals.py b/awx/main/signals.py index 3180b4e4fd..81a2a4b26c 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -134,3 +134,43 @@ def migrate_children_from_inactive_group_to_parent_groups(sender, **kwargs): child_group, parent_group, instance) parent_group.children.add(child_group) parent_group.children.remove(instance) + +# Update host pointers to last_job and last_job_host_summary when a job is +# marked inactive or deleted. + +def _update_host_last_jhs(host): + jhs_qs = JobHostSummary.objects.filter(job__active=True, host__pk=host.pk) + try: + jhs = jhs_qs.order_by('-job__pk')[0] + except IndexError: + jhs = None + update_fields = [] + last_job = jhs.job if jhs else None + if host.last_job != last_job: + host.last_job = last_job + update_fields.append('last_job') + if host.last_job_host_summary != jhs: + host.last_job_host_summary = jhs + update_fields.append('last_job_host_summary') + if update_fields: + host.save(update_fields=update_fields) + +@receiver(post_save, sender=Job) +def update_host_last_job_when_job_marked_inactive(sender, **kwargs): + instance = kwargs['instance'] + hosts_qs = Host.objects.filter(active=True, last_job__pk=instance.pk) + for host in hosts_qs: + _update_host_last_jhs(host) + +@receiver(pre_delete, sender=Job) +def save_host_pks_before_job_delete(sender, **kwargs): + instance = kwargs['instance'] + hosts_qs = Host.objects.filter(active=True, last_job__pk=instance.pk) + instance._saved_hosts_pks = set(hosts_qs.values_list('pk', flat=True)) + +@receiver(post_delete, sender=Job) +def update_host_last_job_after_job_deleted(sender, **kwargs): + instance = kwargs['instance'] + hosts_pks = getattr(instance, '_saved_hosts_pks', []) + for host in Host.objects.filter(pk__in=hosts_pks): + _update_host_last_jhs(host) diff --git a/awx/main/tests/tasks.py b/awx/main/tests/tasks.py index ba593233f2..39901659a4 100644 --- a/awx/main/tests/tasks.py +++ b/awx/main/tests/tasks.py @@ -1,11 +1,17 @@ # Copyright (c) 2013 AnsibleWorks, Inc. # All Rights Reserved. +# Python import os import shutil import tempfile + +# Django from django.conf import settings from django.test.utils import override_settings +from django.utils.timezone import now + +# AWX from awx.main.models import * from awx.main.tests.base import BaseLiveServerTest from awx.main.tasks import RunJob @@ -203,7 +209,7 @@ class RunJobTest(BaseCeleryTest): def create_test_job_template(self, **kwargs): opts = { - 'name': 'test-job-template', + 'name': 'test-job-template %s' % str(now()), 'inventory': self.inventory, 'project': self.project, 'credential': self.credential, @@ -223,7 +229,7 @@ class RunJobTest(BaseCeleryTest): self.job = job_template.create_job(**kwargs) else: opts = { - 'name': 'test-job', + 'name': 'test-job %s' % str(now()), 'inventory': self.inventory, 'project': self.project, 'credential': self.credential, @@ -397,6 +403,7 @@ class RunJobTest(BaseCeleryTest): self.assertEqual(job.unreachable_hosts.count(), 0) self.assertEqual(job.skipped_hosts.count(), 0) self.assertEqual(job.processed_hosts.count(), 1) + return job def test_check_job(self): self.create_test_project(TEST_PLAYBOOK) @@ -424,6 +431,7 @@ class RunJobTest(BaseCeleryTest): self.assertEqual(job.unreachable_hosts.count(), 0) self.assertEqual(job.skipped_hosts.count(), 1) self.assertEqual(job.processed_hosts.count(), 1) + return job def test_run_job_that_fails(self): self.create_test_project(TEST_PLAYBOOK2) @@ -539,6 +547,11 @@ class RunJobTest(BaseCeleryTest): job.name = '_'.join(job.name.split('_')[3:]) or 'undeleted job' job.active = True job.save() + # Need to manually update last_job on host... + host = Host.objects.get(pk=self.host.pk) + host.last_job = job + host.last_job_host_summary = JobHostSummary.objects.get(job=job, host=host) + host.save() job.inventory.update_has_active_failures() self.host = Host.objects.get(pk=self.host.pk) self.assertTrue(self.host.has_active_failures) @@ -555,6 +568,23 @@ class RunJobTest(BaseCeleryTest): self.inventory = Inventory.objects.get(pk=self.inventory.pk) self.assertFalse(self.inventory.has_active_failures) + def test_update_host_last_job_when_job_removed(self): + job1 = self.test_run_job() + job2 = self.test_run_job() + self.host = Host.objects.get(pk=self.host.pk) + self.assertEqual(self.host.last_job, job2) + self.assertEqual(self.host.last_job_host_summary.job, job2) + # Delete job2 (should update host to point to job1). + job2.delete() + self.host = Host.objects.get(pk=self.host.pk) + self.assertEqual(self.host.last_job, job1) + self.assertEqual(self.host.last_job_host_summary.job, job1) + # Mark job1 inactive (should update host.last_job to None). + job1.mark_inactive() + self.host = Host.objects.get(pk=self.host.pk) + self.assertEqual(self.host.last_job, None) + self.assertEqual(self.host.last_job_host_summary, None) + def test_check_job_where_task_would_fail(self): self.create_test_project(TEST_PLAYBOOK2) job_template = self.create_test_job_template()