From d4879506c2f71cf874d5fa245e8e387d60927ae8 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 17 Dec 2014 16:04:25 -0500 Subject: [PATCH] Switch to using extra_vars for survey variables, fix up some unit tests related to that and some issues with system jobs --- awx/api/views.py | 14 +++++++----- awx/main/models/base.py | 5 ++++- awx/main/models/jobs.py | 4 ++-- awx/main/models/unified_jobs.py | 17 ++++++++------ awx/main/tests/jobs.py | 40 ++++++++++++++++----------------- 5 files changed, 44 insertions(+), 36 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index d7790a8530..c5cc0fdd16 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -1486,13 +1486,14 @@ class JobTemplateLaunch(GenericAPIView): request_data = {} else: request_data = request.DATA - validation_errors = obj.survey_variable_validation(request_data) - if validation_errors: - return Response(dict(errors=validation_errors), - status=status.HTTP_400_BAD_REQUEST) + if 'extra_vars' in request_data: + validation_errors = obj.survey_variable_validation(request_data['extra_vars']) + if validation_errors: + return Response(dict(errors=validation_errors), + status=status.HTTP_400_BAD_REQUEST) if obj.credential is None and ('credential' not in request.DATA and 'credential_id' not in request.DATA): return Response(dict(errors="Credential not provided"), status=status.HTTP_400_BAD_REQUEST) - new_job = obj.create_unified_job(**request.DATA) + new_job = obj.create_unified_job() result = new_job.signal_start(**request.DATA) if not result: data = dict(passwords_needed_to_start=new_job.passwords_needed_to_start) @@ -1773,7 +1774,8 @@ class SystemJobTemplateLaunch(GenericAPIView): extra_vars = {} else: extra_vars = {} - result = new_job.signal_start(**extra_vars) + ev = {'extra_vars': extra_vars} + result = new_job.signal_start(**ev) data = dict(system_job=new_job.id) return Response(data, status=status.HTTP_202_ACCEPTED) diff --git a/awx/main/models/base.py b/awx/main/models/base.py index 735ab58f88..84b3f86508 100644 --- a/awx/main/models/base.py +++ b/awx/main/models/base.py @@ -78,7 +78,10 @@ class VarsDictProperty(object): def __get__(self, obj, type=None): if obj is None: return self - v = getattr(obj, self.field).encode('utf-8') + v = getattr(obj, self.field) + if hasattr(v, 'items'): + return v + v = v.encode('utf-8') d = None try: d = json.loads(v.strip() or '{}') diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 31c853155f..133452aa1f 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -195,7 +195,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions): def _get_unified_job_field_names(cls): return ['name', 'description', 'job_type', 'inventory', 'project', 'playbook', 'credential', 'cloud_credential', 'forks', 'schedule', - 'limit', 'verbosity', 'extra_vars', 'job_tags', 'launch_type', + 'limit', 'verbosity', 'job_tags', 'extra_vars', 'launch_type', 'force_handlers', 'skip_tags', 'start_at_task'] def create_job(self, **kwargs): @@ -949,7 +949,7 @@ class SystemJobTemplate(UnifiedJobTemplate, SystemJobOptions): @classmethod def _get_unified_job_field_names(cls): - return ['name', 'description', 'job_type'] + return ['name', 'description', 'job_type', 'extra_vars'] def get_absolute_url(self): return reverse('api:system_job_template_detail', args=(self.pk,)) diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index e2911fa69e..4486dc88c5 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -306,7 +306,10 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique): value = value.id create_kwargs[id_field_name] = value elif field_name in kwargs: - create_kwargs[field_name] = kwargs[field_name] + if field_name == 'extra_vars' and type(kwargs[field_name]) == dict: + create_kwargs[field_name] = json.dumps(kwargs['extra_vars']) + else: + create_kwargs[field_name] = kwargs[field_name] elif hasattr(self, field_name): create_kwargs[field_name] = getattr(self, field_name) kwargs = self._update_unified_job_kwargs(**create_kwargs) @@ -692,9 +695,10 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique self.job_explanation = u'Missing needed fields: %s.' % missing_fields self.save(update_fields=['job_explanation']) return False - extra_data = dict([(field, kwargs[field]) for field in kwargs - if field not in needed]) - self.handle_extra_data(extra_data) + #extra_data = dict([(field, kwargs[field]) for field in kwargs + # if field not in needed]) + if 'extra_vars' in kwargs: + self.handle_extra_data(kwargs['extra_vars']) task_class().apply_async((self.pk,), opts, link_error=error_callback) return True @@ -715,9 +719,8 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique opts = dict([(field, kwargs.get(field, '')) for field in needed]) if not all(opts.values()): return False - extra_data = dict([(field, kwargs[field]) for field in kwargs - if field not in needed]) - self.handle_extra_data(extra_data) + if 'extra_vars' in kwargs: + self.handle_extra_data(kwargs['extra_vars']) # Save the pending status, and inform the SocketIO listener. self.update_fields(start_args=json.dumps(kwargs), status='pending') diff --git a/awx/main/tests/jobs.py b/awx/main/tests/jobs.py index 0d38297b74..c03c2ca5a3 100644 --- a/awx/main/tests/jobs.py +++ b/awx/main/tests/jobs.py @@ -968,7 +968,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase): launch_url = reverse('api:job_template_launch', args=(new_jt_id,)) response = self.get(launch_url) self.assertTrue('favorite_color' in response['variables_needed_to_start']) - response = self.post(launch_url, dict(favorite_color="green"), expect=202) + response = self.post(launch_url, dict(extra_vars=dict(favorite_color="green")), expect=202) job = Job.objects.get(pk=response["job"]) job_extra = json.loads(job.extra_vars) self.assertTrue("favorite_color" in job_extra) @@ -983,43 +983,43 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase): response = self.post(url, json.loads(TEST_SURVEY_REQUIREMENTS), expect=200) launch_url = reverse('api:job_template_launch', args=(new_jt_id,)) # Just the required answer should work - self.post(launch_url, dict(reqd_answer="foo"), expect=202) + self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo")), expect=202) # Short answer but requires a long answer - self.post(launch_url, dict(long_answer='a', reqd_answer="foo"), expect=400) + self.post(launch_url, dict(extra_vars=dict(long_answer='a', reqd_answer="foo")), expect=400) # Long answer but requires a short answer - self.post(launch_url, dict(short_answer='thisissomelongtext', reqd_answer="foo"), expect=400) + self.post(launch_url, dict(extra_vars=dict(short_answer='thisissomelongtext', reqd_answer="foo")), expect=400) # Long answer but missing required answer - self.post(launch_url, dict(long_answer='thisissomelongtext'), expect=400) + self.post(launch_url, dict(extra_vars=dict(long_answer='thisissomelongtext')), expect=400) # Integer that's not big enough - self.post(launch_url, dict(int_answer=0, reqd_answer="foo"), expect=400) + self.post(launch_url, dict(extra_vars=dict(int_answer=0, reqd_answer="foo")), expect=400) # Integer that's too big - self.post(launch_url, dict(int_answer=10, reqd_answer="foo"), expect=400) + self.post(launch_url, dict(extra_vars=dict(int_answer=10, reqd_answer="foo")), expect=400) # Integer that's just riiiiight - self.post(launch_url, dict(int_answer=3, reqd_answer="foo"), expect=202) + self.post(launch_url, dict(extra_vars=dict(int_answer=3, reqd_answer="foo")), expect=202) # Integer bigger than min with no max defined - self.post(launch_url, dict(int_answer_no_max=3, reqd_answer="foo"), expect=202) + self.post(launch_url, dict(extra_vars=dict(int_answer_no_max=3, reqd_answer="foo")), expect=202) # Integer answer that's the wrong type - self.post(launch_url, dict(int_answer="test", reqd_answer="foo"), expect=400) + self.post(launch_url, dict(extra_vars=dict(int_answer="test", reqd_answer="foo")), expect=400) # Float that's too big - self.post(launch_url, dict(float_answer=10.5, reqd_answer="foo"), expect=400) + self.post(launch_url, dict(extra_vars=dict(float_answer=10.5, reqd_answer="foo")), expect=400) # Float that's too small - self.post(launch_url, dict(float_answer=1.995, reqd_answer="foo"), expect=400) + self.post(launch_url, dict(extra_vars=dict(float_answer=1.995, reqd_answer="foo")), expect=400) # float that's just riiiiight - self.post(launch_url, dict(float_answer=2.01, reqd_answer="foo"), expect=202) + self.post(launch_url, dict(extra_vars=dict(float_answer=2.01, reqd_answer="foo")), expect=202) # float answer that's the wrong type - self.post(launch_url, dict(float_answer="test", reqd_answer="foo"), expect=400) + self.post(launch_url, dict(extra_vars=dict(float_answer="test", reqd_answer="foo")), expect=400) # Wrong choice in single choice - self.post(launch_url, dict(reqd_answer="foo", single_choice="three"), expect=400) + self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo", single_choice="three")), expect=400) # Wrong choice in multi choice - self.post(launch_url, dict(reqd_answer="foo", multi_choice=["four"]), expect=400) + self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo", multi_choice=["four"])), expect=400) # Wrong type for multi choicen - self.post(launch_url, dict(reqd_answer="foo", multi_choice="two"), expect=400) + self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo", multi_choice="two")), expect=400) # Right choice in single choice - self.post(launch_url, dict(reqd_answer="foo", single_choice="two"), expect=202) + self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo", single_choice="two")), expect=202) # Right choices in multi choice - self.post(launch_url, dict(reqd_answer="foo", multi_choice=["one", "two"]), expect=202) + self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo", multi_choice=["one", "two"])), expect=202) # Nested json - self.post(launch_url, dict(json_answer=dict(test="val", num=1), reqd_answer="foo"), expect=202) + self.post(launch_url, dict(extra_vars=dict(json_answer=dict(test="val", num=1), reqd_answer="foo")), expect=202) def test_launch_job_template(self): url = reverse('api:job_template_list')