diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 64a002f062..f441e8f441 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -615,54 +615,59 @@ class JobEvent(BaseModel): # If update_fields has been specified, add our field names to it, # if it hasn't been specified, then we're just doing a normal save. update_fields = kwargs.get('update_fields', []) - res = self.event_data.get('res', None) - # Workaround for Ansible 1.2, where the runner_on_async_ok event is - # created even when the async task failed. Change the event to be - # correct. - if self.event == 'runner_on_async_ok': - try: - if res.get('failed', False) or res.get('rc', 0) != 0: - self.event = 'runner_on_async_failed' - except (AttributeError, TypeError): - pass - if self.event in self.FAILED_EVENTS: - if not self.event_data.get('ignore_errors', False): - self.failed = True - if 'failed' not in update_fields: - update_fields.append('failed') - if isinstance(res, dict) and res.get('changed', False): - self.changed = True - if 'changed' not in update_fields: - update_fields.append('changed') - if self.event == 'playbook_on_stats': - try: - failures_dict = self.event_data.get('failures', {}) - dark_dict = self.event_data.get('dark', {}) - self.failed = bool(sum(failures_dict.values()) + - sum(dark_dict.values())) - if 'failed' not in update_fields: - update_fields.append('failed') - changed_dict = self.event_data.get('changed', {}) - self.changed = bool(sum(changed_dict.values())) + # Skip normal checks on save if we're only updating failed/changed + # flags triggered from a child event. + from_parent_update = kwargs.pop('from_parent_update', False) + if not from_parent_update: + res = self.event_data.get('res', None) + # Workaround for Ansible 1.2, where the runner_on_async_ok event is + # created even when the async task failed. Change the event to be + # correct. + if self.event == 'runner_on_async_ok': + try: + if res.get('failed', False) or res.get('rc', 0) != 0: + self.event = 'runner_on_async_failed' + except (AttributeError, TypeError): + pass + if self.event in self.FAILED_EVENTS: + if not self.event_data.get('ignore_errors', False): + self.failed = True + if 'failed' not in update_fields: + update_fields.append('failed') + if isinstance(res, dict) and res.get('changed', False): + self.changed = True if 'changed' not in update_fields: update_fields.append('changed') - except (AttributeError, TypeError): + if self.event == 'playbook_on_stats': + try: + failures_dict = self.event_data.get('failures', {}) + dark_dict = self.event_data.get('dark', {}) + self.failed = bool(sum(failures_dict.values()) + + sum(dark_dict.values())) + if 'failed' not in update_fields: + update_fields.append('failed') + changed_dict = self.event_data.get('changed', {}) + self.changed = bool(sum(changed_dict.values())) + if 'changed' not in update_fields: + update_fields.append('changed') + except (AttributeError, TypeError): + pass + try: + if not self.host and self.event_data.get('host', ''): + self.host = self.job.inventory.hosts.get(name=self.event_data['host']) + if 'host' not in update_fields: + update_fields.append('host') + except (Host.DoesNotExist, AttributeError): pass - try: - if not self.host and self.event_data.get('host', ''): - self.host = self.job.inventory.hosts.get(name=self.event_data['host']) - if 'host' not in update_fields: - update_fields.append('host') - except (Host.DoesNotExist, AttributeError): - pass - self.play = self.event_data.get('play', '') - self.task = self.event_data.get('task', '') - self.parent = self._find_parent() - update_fields.extend(['play', 'task', 'parent']) + self.play = self.event_data.get('play', '') + self.task = self.event_data.get('task', '') + self.parent = self._find_parent() + update_fields.extend(['play', 'task', 'parent']) super(JobEvent, self).save(*args, **kwargs) - self.update_parent_failed_and_changed() - self.update_hosts() - self.update_host_summary_from_stats() + if not from_parent_update: + self.update_parent_failed_and_changed() + self.update_hosts() + self.update_host_summary_from_stats() def update_parent_failed_and_changed(self): # Propagage failed and changed flags to parent events. @@ -676,7 +681,7 @@ class JobEvent(BaseModel): parent.changed = True update_fields.append('changed') if update_fields: - parent.save(update_fields=update_fields) + parent.save(update_fields=update_fields, from_parent_update=True) parent.update_parent_failed_and_changed() def update_hosts(self, extra_hosts=None): diff --git a/awx/main/tests/jobs.py b/awx/main/tests/jobs.py index 41e9414b21..399564c879 100644 --- a/awx/main/tests/jobs.py +++ b/awx/main/tests/jobs.py @@ -1364,16 +1364,14 @@ class JobTransactionTest(BaseJobTestMixin, django.test.LiveServerTestCase): finally: t.join(20) - # FIXME: This test isn't working for now. - def _test_get_job_detail_while_job_running(self): - self.proj_async = self.make_project('async', 'async test', - self.user_sue, TEST_ASYNC_PLAYBOOK) - self.org_eng.projects.add(self.proj_async) - job = self.job_ops_east_run - job.project = self.proj_async - job.playbook = self.proj_async.playbooks[0] - job.verbosity = 3 - job.save() + def test_for_job_deadlocks(self): + # Create lots of extra test hosts to trigger job event callbacks + job = self.job_eng_run + inv = job.inventory + for x in xrange(100): + h = inv.hosts.create(name='local-%d' % x) + for g in inv.groups.all(): + g.hosts.add(h) job_detail_url = reverse('api:job_detail', args=(job.pk,)) job_detail_url = urlparse.urljoin(self.live_server_url, job_detail_url)