From 2546cbdbb6c1d941b12cade4a599e6f880f4e09d Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 15 Apr 2014 00:22:57 -0400 Subject: [PATCH] AC-504 Capture role when available for job events. --- awx/api/serializers.py | 2 +- awx/main/models/jobs.py | 14 +++++-- awx/main/tests/base.py | 16 +++++++- awx/main/tests/tasks.py | 48 ++++++++++++++++++++-- awx/plugins/callback/job_event_callback.py | 17 +++++--- 5 files changed, 81 insertions(+), 16 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 71c854d990..0d5718c423 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1328,7 +1328,7 @@ class JobEventSerializer(BaseSerializer): model = JobEvent fields = ('*', '-name', '-description', 'job', 'event', 'event_display', 'event_data', 'event_level', 'failed', - 'changed', 'host', 'parent', 'play', 'task') + 'changed', 'host', 'parent', 'play', 'task', 'role') def get_related(self, obj): res = super(JobEventSerializer, self).get_related(obj) diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 25a5e4f0ce..2492b51dd1 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -512,7 +512,7 @@ class JobEvent(CreatedModifiedModel): return reverse('api:job_event_detail', args=(self.pk,)) def __unicode__(self): - return u'%s @ %s' % (self.get_event_display(), self.created.isoformat()) + return u'%s @ %s' % (self.get_event_display2(), self.created.isoformat()) @property def event_level(self): @@ -521,11 +521,14 @@ class JobEvent(CreatedModifiedModel): def get_event_display2(self): msg = self.get_event_display() if self.event == 'playbook_on_play_start': - if self.play is not None: + if self.play: msg = "%s (%s)" % (msg, self.play) elif self.event == 'playbook_on_task_start': - if self.task is not None: - msg = "%s (%s)" % (msg, self.task) + if self.task: + if self.role: + msg = '%s (%s | %s)' % (msg, self.role, self.task) + else: + msg = "%s (%s)" % (msg, self.task) # Change display for runner events trigged by async polling. Some of # these events may not show in most cases, due to filterting them out @@ -637,6 +640,9 @@ class JobEvent(CreatedModifiedModel): self.task = self.event_data.get('task', '').strip() if 'task' not in update_fields: update_fields.append('task') + self.role = self.event_data.get('role', '').strip() + if 'role' not in update_fields: + update_fields.append('role') # Only update job event hierarchy and related models during post # processing (after running job). post_process = kwargs.pop('post_process', False) diff --git a/awx/main/tests/base.py b/awx/main/tests/base.py index fa4033e140..09b30b5523 100644 --- a/awx/main/tests/base.py +++ b/awx/main/tests/base.py @@ -126,7 +126,7 @@ class BaseTestMixin(object): return results def make_project(self, name, description='', created_by=None, - playbook_content=''): + playbook_content='', role_playbooks=None): if not os.path.exists(settings.PROJECTS_ROOT): os.makedirs(settings.PROJECTS_ROOT) # Create temp project directory. @@ -139,13 +139,24 @@ class BaseTestMixin(object): test_playbook_file = os.fdopen(handle, 'w') test_playbook_file.write(playbook_content) test_playbook_file.close() + # Role playbooks are specified as a dict of role name and the + # content of tasks/main.yml playbook. + role_playbooks = role_playbooks or {} + for role_name, role_playbook_content in role_playbooks.items(): + role_tasks_dir = os.path.join(project_dir, 'roles', role_name, 'tasks') + if not os.path.exists(role_tasks_dir): + os.makedirs(role_tasks_dir) + role_tasks_playbook_path = os.path.join(role_tasks_dir, 'main.yml') + with open(role_tasks_playbook_path, 'w') as f: + f.write(role_playbook_content) return Project.objects.create( name=name, description=description, local_path=os.path.basename(project_dir), created_by=created_by, #scm_type='git', default_playbook='foo.yml', ) - def make_projects(self, created_by, count=1, playbook_content=''): + def make_projects(self, created_by, count=1, playbook_content='', + role_playbooks=None): results = [] for x in range(0, count): self.object_ctr = self.object_ctr + 1 @@ -154,6 +165,7 @@ class BaseTestMixin(object): description="proj%s" % x, created_by=created_by, playbook_content=playbook_content, + role_playbooks=role_playbooks, )) return results diff --git a/awx/main/tests/tasks.py b/awx/main/tests/tasks.py index f1f4115715..f6cb961d68 100644 --- a/awx/main/tests/tasks.py +++ b/awx/main/tests/tasks.py @@ -88,6 +88,26 @@ TEST_ASYNC_NOWAIT_PLAYBOOK = ''' poll: 0 ''' +TEST_PLAYBOOK_WITH_ROLES = ''' +- hosts: test-group + gather_facts: false + roles: + - some_stuff + - more_stuff + - {role: stuff, tags: stuff} +''' + +TEST_ROLE_PLAYBOOK = ''' +- name: some task in a role + command: test 1 = 1 +''' + +TEST_ROLE_PLAYBOOKS = { + 'some_stuff': TEST_ROLE_PLAYBOOK, + 'more_stuff': TEST_ROLE_PLAYBOOK, + 'stuff': TEST_ROLE_PLAYBOOK, +} + TEST_VAULT_PLAYBOOK = '''$ANSIBLE_VAULT;1.1;AES256 35623233333035633365383330323835353564346534363762366465316263363463396162656432 6562643539396330616265616532656466353639303338650a313466333663646431646663333739 @@ -230,8 +250,9 @@ class RunJobTest(BaseCeleryTest): self.cloud_credential = Credential.objects.create(**opts) return self.cloud_credential - def create_test_project(self, playbook_content): - self.project = self.make_projects(self.normal_django_user, 1, playbook_content)[0] + def create_test_project(self, playbook_content, role_playbooks=None): + self.project = self.make_projects(self.normal_django_user, 1, + playbook_content, role_playbooks)[0] self.organization.projects.add(self.project) def create_test_job_template(self, **kwargs): @@ -299,7 +320,7 @@ class RunJobTest(BaseCeleryTest): def check_job_events(self, job, runner_status='ok', plays=1, tasks=1, async=False, async_timeout=False, async_nowait=False, - check_ignore_errors=False): + check_ignore_errors=False, has_roles=False): job_events = job.job_events.all() if False and async: print @@ -323,6 +344,7 @@ class RunJobTest(BaseCeleryTest): self.assertFalse(evt.host, evt) self.assertFalse(evt.play, evt) self.assertFalse(evt.task, evt) + self.assertFalse(evt.role, evt) self.assertEqual(evt.failed, should_be_failed) if not async: self.assertEqual(evt.changed, should_be_changed) @@ -335,6 +357,7 @@ class RunJobTest(BaseCeleryTest): self.assertFalse(evt.host, evt) self.assertTrue(evt.play, evt) self.assertFalse(evt.task, evt) + self.assertFalse(evt.role, evt) self.assertEqual(evt.failed, should_be_failed) if not async: self.assertEqual(evt.changed, should_be_changed) @@ -347,6 +370,10 @@ class RunJobTest(BaseCeleryTest): self.assertFalse(evt.host, evt) self.assertTrue(evt.play, evt) self.assertTrue(evt.task, evt) + if has_roles: + self.assertTrue(evt.role, evt) + else: + self.assertFalse(evt.role, evt) self.assertEqual(evt.failed, should_be_failed) if not async: self.assertEqual(evt.changed, should_be_changed) @@ -367,6 +394,10 @@ class RunJobTest(BaseCeleryTest): self.assertEqual(evt.host, self.host) self.assertTrue(evt.play, evt) self.assertTrue(evt.task, evt) + if has_roles: + self.assertTrue(evt.role, evt) + else: + self.assertFalse(evt.role, evt) self.assertEqual(evt.failed, should_be_failed) if not async: self.assertEqual(evt.changed, should_be_changed) @@ -1049,3 +1080,14 @@ class RunJobTest(BaseCeleryTest): self.assertEqual(job.unreachable_hosts.count(), 0) self.assertEqual(job.skipped_hosts.count(), 0) self.assertEqual(job.processed_hosts.count(), 1) + + def test_run_job_with_roles(self): + self.create_test_project(TEST_PLAYBOOK_WITH_ROLES, TEST_ROLE_PLAYBOOKS) + job_template = self.create_test_job_template() + job = self.create_test_job(job_template=job_template) + self.assertEqual(job.status, 'new') + self.assertFalse(job.passwords_needed_to_start) + self.assertTrue(job.signal_start()) + job = Job.objects.get(pk=job.pk) + self.check_job_result(job, 'successful') + self.check_job_events(job, 'ok', 1, 3, has_roles=True) diff --git a/awx/plugins/callback/job_event_callback.py b/awx/plugins/callback/job_event_callback.py index 860d736701..9df967f3f0 100644 --- a/awx/plugins/callback/job_event_callback.py +++ b/awx/plugins/callback/job_event_callback.py @@ -167,12 +167,17 @@ class CallbackModule(object): response.raise_for_status() def _log_event(self, event, **event_data): - play = getattr(getattr(self, 'play', None), 'name', '') - if play and event not in self.EVENTS_WITHOUT_PLAY: - event_data['play'] = play - task = getattr(getattr(self, 'task', None), 'name', '') - if task and event not in self.EVENTS_WITHOUT_TASK: - event_data['task'] = task + play = getattr(self, 'play', None) + play_name = getattr(play, 'name', '') + if play_name and event not in self.EVENTS_WITHOUT_PLAY: + event_data['play'] = play_name + task = getattr(self, 'task', None) + task_name = getattr(task, 'name', '') + role_name = getattr(task, 'role_name', '') + if task_name and event not in self.EVENTS_WITHOUT_TASK: + event_data['task'] = task_name + if role_name and event not in self.EVENTS_WITHOUT_TASK: + event_data['role'] = role_name if self.callback_consumer_port: self._post_job_event_queue_msg(event, event_data) else: