diff --git a/awx/api/views.py b/awx/api/views.py index a0d383d758..9598d21b42 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -2207,6 +2207,7 @@ class JobRelaunch(RetrieveAPIView, GenericAPIView): if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + obj.launch_type = 'relaunch' new_job = obj.copy() result = new_job.signal_start(**request.DATA) if not result: diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 6df76c895a..ebdca5a469 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -284,6 +284,9 @@ class JobTemplate(UnifiedJobTemplate, JobOptions): return errors def _update_unified_job_kwargs(self, **kwargs): + if 'launch_type' in kwargs and kwargs['launch_type'] == 'relaunch': + return kwargs + # Job Template extra_vars extra_vars = self.extra_vars_dict diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index 00deb50691..e9b5e4bfd2 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -343,6 +343,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique LAUNCH_TYPE_CHOICES = [ ('manual', _('Manual')), # Job was started manually by a user. + ('relaunch', _('Relaunch')), # Job was started via relaunch. ('callback', _('Callback')), # Job was started via host callback. ('scheduled', _('Scheduled')), # Job was started from a schedule. ('dependency', _('Dependency')), # Job was started as a dependency of another job. diff --git a/awx/main/tests/jobs/__init__.py b/awx/main/tests/jobs/__init__.py index bf5eedafe7..092826ccf0 100644 --- a/awx/main/tests/jobs/__init__.py +++ b/awx/main/tests/jobs/__init__.py @@ -5,6 +5,7 @@ from __future__ import absolute_import from .jobs_monolithic import * # noqa from .job_launch import * # noqa +from .job_relaunch import * # noqa from .survey_password import * # noqa from .start_cancel import * # noqa from .base import * # noqa diff --git a/awx/main/tests/jobs/job_relaunch.py b/awx/main/tests/jobs/job_relaunch.py new file mode 100644 index 0000000000..04ae95ba75 --- /dev/null +++ b/awx/main/tests/jobs/job_relaunch.py @@ -0,0 +1,75 @@ +# Copyright (c) 2015 Ansible, Inc. +# All Rights Reserved + +# Python +from __future__ import absolute_import +import json + +# Django +from django.core.urlresolvers import reverse + +# AWX +from awx.main.models import * # noqa +from awx.main.tests.base import BaseLiveServerTest +from .base import BaseJobTestMixin + +__all__ = ['JobRelaunchTest',] + +class JobRelaunchTest(BaseJobTestMixin, BaseLiveServerTest): + + def test_job_relaunch(self): + job = self.make_job(self.jt_ops_east_run, self.user_sue, 'success') + url = reverse('api:job_relaunch', args=(job.pk,)) + with self.current_user(self.user_sue): + response = self.post(url, {}, expect=201) + j = Job.objects.get(pk=response['job']) + self.assertTrue(j.status == 'successful') + self.assertEqual(j.launch_type, 'relaunch') + # Test with a job that prompts for SSH and sudo passwords. + job = self.make_job(self.jt_sup_run, self.user_sue, 'success') + url = reverse('api:job_start', args=(job.pk,)) + with self.current_user(self.user_sue): + response = self.get(url) + self.assertEqual(set(response['passwords_needed_to_start']), + set(['ssh_password', 'become_password'])) + data = dict() + response = self.post(url, data, expect=400) + data['ssh_password'] = 'sshpass' + response = self.post(url, data, expect=400) + data2 = dict(become_password='sudopass') + response = self.post(url, data2, expect=400) + data.update(data2) + response = self.post(url, data, expect=202) + job = Job.objects.get(pk=job.pk) + + # Create jt with no extra_vars + # Launch j1 with runtime extra_vars + # Assign extra_vars to jt backing job + # Relaunch j1 + # j2 should not contain jt extra_vars + def test_relaunch_job_does_not_inherit_jt_extra_vars(self): + jt_extra_vars = { + "hello": "world" + } + j_extra_vars = { + "goodbye": "cruel universe" + } + job = self.make_job(self.jt_ops_east_run, self.user_sue, 'success', extra_vars=j_extra_vars) + url = reverse('api:job_relaunch', args=(job.pk,)) + with self.current_user(self.user_sue): + response = self.post(url, {}, expect=201) + j = Job.objects.get(pk=response['job']) + self.assertTrue(j.status == 'successful') + + self.jt_ops_east_run.extra_vars = jt_extra_vars + self.jt_ops_east_run.save() + + response = self.post(url, {}, expect=201) + j = Job.objects.get(pk=response['job']) + self.assertTrue(j.status == 'successful') + + resp_extra_vars = json.loads(response['extra_vars']) + self.assertNotIn("hello", resp_extra_vars) + self.assertEqual(resp_extra_vars, j_extra_vars) + + diff --git a/awx/main/tests/jobs/start_cancel.py b/awx/main/tests/jobs/start_cancel.py index 8aae66b402..54ff2255d9 100644 --- a/awx/main/tests/jobs/start_cancel.py +++ b/awx/main/tests/jobs/start_cancel.py @@ -115,30 +115,6 @@ class JobStartCancelTest(BaseJobTestMixin, BaseLiveServerTest): # FIXME: Test with other users, test when passwords are required. - def test_job_relaunch(self): - job = self.make_job(self.jt_ops_east_run, self.user_sue, 'success') - url = reverse('api:job_relaunch', args=(job.pk,)) - with self.current_user(self.user_sue): - response = self.post(url, {}, expect=201) - j = Job.objects.get(pk=response['job']) - self.assertTrue(j.status == 'successful') - # Test with a job that prompts for SSH and sudo passwords. - job = self.make_job(self.jt_sup_run, self.user_sue, 'success') - url = reverse('api:job_start', args=(job.pk,)) - with self.current_user(self.user_sue): - response = self.get(url) - self.assertEqual(set(response['passwords_needed_to_start']), - set(['ssh_password', 'become_password'])) - data = dict() - response = self.post(url, data, expect=400) - data['ssh_password'] = 'sshpass' - response = self.post(url, data, expect=400) - data2 = dict(become_password='sudopass') - response = self.post(url, data2, expect=400) - data.update(data2) - response = self.post(url, data, expect=202) - job = Job.objects.get(pk=job.pk) - def test_job_cancel(self): #job = self.job_ops_east_run job = self.make_job(self.jt_ops_east_run, self.user_sue, 'new')