From 0617239136c04f5eb773e10aecb04a7b0ddc6562 Mon Sep 17 00:00:00 2001 From: shangxdy Date: Mon, 15 Aug 2016 20:54:12 +0800 Subject: [PATCH] Add validation for parameters Add validation for all parameters and modification related test cases: 1. Add property of required in Input class; 2. For all required inputs which have not default value, must provide parameters when instantiated a template; 3. For all required inputs which have default value, may not provide parameters when instantiated a template; 4. For all not required inputs, must process the logical of no parameters in template design(may provide default value, const value, or process case for none input). bp: https://blueprints.launchpad.net/tosca-parser/+spec/tosca-input-validation Change-Id: I5a4a4343f978f387f1b47d36b2210fbcfe7f1b28 Signed-off-by: shangxdy --- toscaparser/common/exception.py | 5 +++ toscaparser/elements/constraints.py | 8 +++- toscaparser/parameters.py | 13 +++++- ...est_tosca_normative_type_by_shortname.yaml | 1 + .../data/topology_template/definitions.yaml | 4 ++ .../tests/data/topology_template/system.yaml | 2 +- toscaparser/tests/test_functions.py | 29 +++++++++---- toscaparser/tests/test_toscatpl.py | 42 ++++++++++++++----- toscaparser/tests/test_toscatplvalidation.py | 41 +++++++++++++----- toscaparser/topology_template.py | 8 ++++ 10 files changed, 119 insertions(+), 34 deletions(-) diff --git a/toscaparser/common/exception.py b/toscaparser/common/exception.py index 98d23e42..29702b85 100644 --- a/toscaparser/common/exception.py +++ b/toscaparser/common/exception.py @@ -114,6 +114,11 @@ class UnknownInputError(TOSCAException): msg_fmt = _('Unknown input "%(input_name)s".') +class MissingRequiredParameterError(TOSCAException): + msg_fmt = _('%(what)s is missing required parameter for input: ' + '"%(input_name)s".') + + class InvalidPropertyValueError(TOSCAException): msg_fmt = _('Value of property "%(what)s" is invalid.') diff --git a/toscaparser/elements/constraints.py b/toscaparser/elements/constraints.py index 8594b851..70863bc2 100644 --- a/toscaparser/elements/constraints.py +++ b/toscaparser/elements/constraints.py @@ -27,10 +27,10 @@ class Schema(collections.Mapping): KEYS = ( TYPE, REQUIRED, DESCRIPTION, - DEFAULT, CONSTRAINTS, ENTRYSCHEMA + DEFAULT, CONSTRAINTS, ENTRYSCHEMA, STATUS ) = ( 'type', 'required', 'description', - 'default', 'constraints', 'entry_schema' + 'default', 'constraints', 'entry_schema', 'status' ) PROPERTY_TYPES = ( @@ -85,6 +85,10 @@ class Schema(collections.Mapping): def default(self): return self.schema.get(self.DEFAULT) + @property + def status(self): + return self.schema.get(self.STATUS, '') + @property def constraints(self): if not self.constraints_list: diff --git a/toscaparser/parameters.py b/toscaparser/parameters.py index 1d2cb29f..cac167f2 100644 --- a/toscaparser/parameters.py +++ b/toscaparser/parameters.py @@ -35,10 +35,17 @@ class Input(object): self.name = name self.schema = Schema(name, schema_dict) + self._validate_field() + self.validate_type(self.type) + @property def type(self): return self.schema.type + @property + def required(self): + return self.schema.required + @property def description(self): return self.schema.description @@ -51,9 +58,11 @@ class Input(object): def constraints(self): return self.schema.constraints + @property + def status(self): + return self.schema.status + def validate(self, value=None): - self._validate_field() - self.validate_type(self.type) if value is not None: self._validate_value(value) diff --git a/toscaparser/tests/data/test_tosca_normative_type_by_shortname.yaml b/toscaparser/tests/data/test_tosca_normative_type_by_shortname.yaml index 8a702fb7..c0653e7b 100644 --- a/toscaparser/tests/data/test_tosca_normative_type_by_shortname.yaml +++ b/toscaparser/tests/data/test_tosca_normative_type_by_shortname.yaml @@ -10,6 +10,7 @@ topology_template: description: Number of CPUs for the server. constraints: - valid_values: [ 1, 2, 4, 8 ] + default: 2 node_templates: server: diff --git a/toscaparser/tests/data/topology_template/definitions.yaml b/toscaparser/tests/data/topology_template/definitions.yaml index 77829c6f..ba5eac10 100644 --- a/toscaparser/tests/data/topology_template/definitions.yaml +++ b/toscaparser/tests/data/topology_template/definitions.yaml @@ -27,8 +27,10 @@ node_types: properties: mq_server_ip: type: string + required: False receiver_port: type: integer + required: False attributes: receiver_ip: type: string @@ -51,8 +53,10 @@ node_types: properties: admin_user: type: string + required: False pool_size: type: integer + required: False capabilities: message_receiver: type: example.capabilities.Receiver diff --git a/toscaparser/tests/data/topology_template/system.yaml b/toscaparser/tests/data/topology_template/system.yaml index 3c946f3b..00fb4860 100644 --- a/toscaparser/tests/data/topology_template/system.yaml +++ b/toscaparser/tests/data/topology_template/system.yaml @@ -15,7 +15,7 @@ topology_template: description: IP address of the message queuing server to receive messages from. mq_server_port: type: integer - default1: 8080 + default: 8080 description: Port to be used for receiving messages. node_templates: diff --git a/toscaparser/tests/test_functions.py b/toscaparser/tests/test_functions.py index 4d063e58..7f3782c2 100644 --- a/toscaparser/tests/test_functions.py +++ b/toscaparser/tests/test_functions.py @@ -24,7 +24,8 @@ class IntrinsicFunctionsTest(TestCase): tosca_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/tosca_single_instance_wordpress.yaml") - params = {'db_name': 'my_wordpress', 'db_user': 'my_db_user'} + params = {'db_name': 'my_wordpress', 'db_user': 'my_db_user', + 'db_root_pwd': '12345678'} tosca = ToscaTemplate(tosca_tpl, parsed_params=params) def _get_node(self, node_name, tosca=None): @@ -116,14 +117,17 @@ class IntrinsicFunctionsTest(TestCase): self.assertEqual(3306, dbms_port.result()) dbms_root_password = self._get_property(mysql_dbms, 'root_password') - self.assertIsNone(dbms_root_password.result()) + self.assertEqual(dbms_root_password.result(), '12345678') def test_get_property_with_host(self): tosca_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/functions/test_get_property_with_host.yaml") mysql_database = self._get_node('mysql_database', - ToscaTemplate(tosca_tpl)) + ToscaTemplate(tosca_tpl, + parsed_params={ + 'db_root_pwd': '123' + })) operation = self._get_operation(mysql_database.interfaces, 'configure') db_port = operation.inputs['db_port'] self.assertTrue(isinstance(db_port, functions.GetProperty)) @@ -138,7 +142,10 @@ class IntrinsicFunctionsTest(TestCase): tosca_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/functions/tosca_nested_property_names_indexes.yaml") - webserver = self._get_node('wordpress', ToscaTemplate(tosca_tpl)) + webserver = self._get_node('wordpress', + ToscaTemplate(tosca_tpl, + parsed_params={ + 'db_root_pwd': '1234'})) operation = self._get_operation(webserver.interfaces, 'configure') wp_endpoint_prot = operation.inputs['wp_endpoint_protocol'] self.assertTrue(isinstance(wp_endpoint_prot, functions.GetProperty)) @@ -151,7 +158,10 @@ class IntrinsicFunctionsTest(TestCase): tosca_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/functions/test_capabilties_inheritance.yaml") - some_node = self._get_node('some_node', ToscaTemplate(tosca_tpl)) + some_node = self._get_node('some_node', + ToscaTemplate(tosca_tpl, + parsed_params={ + 'db_root_pwd': '1234'})) operation = self._get_operation(some_node.interfaces, 'configure') some_input = operation.inputs['some_input'] self.assertTrue(isinstance(some_input, functions.GetProperty)) @@ -161,7 +171,8 @@ class IntrinsicFunctionsTest(TestCase): tosca_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/functions/test_get_property_source_target_keywords.yaml") - tosca = ToscaTemplate(tosca_tpl) + tosca = ToscaTemplate(tosca_tpl, + parsed_params={'db_root_pwd': '1234'}) for node in tosca.nodetemplates: for relationship, trgt in node.relationships.items(): @@ -184,7 +195,8 @@ class GetAttributeTest(TestCase): return ToscaTemplate(os.path.join( os.path.dirname(os.path.abspath(__file__)), 'data', - filename)) + filename), + parsed_params={'db_root_pwd': '1234'}) def _get_operation(self, interfaces, operation): return [ @@ -283,7 +295,8 @@ class GetAttributeTest(TestCase): tosca_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/functions/test_get_attribute_source_target_keywords.yaml") - tosca = ToscaTemplate(tosca_tpl) + tosca = ToscaTemplate(tosca_tpl, + parsed_params={'db_root_pwd': '12345678'}) for node in tosca.nodetemplates: for relationship, trgt in node.relationships.items(): diff --git a/toscaparser/tests/test_toscatpl.py b/toscaparser/tests/test_toscatpl.py index 3fd5e39b..84d746b6 100644 --- a/toscaparser/tests/test_toscatpl.py +++ b/toscaparser/tests/test_toscatpl.py @@ -30,7 +30,9 @@ class ToscaTemplateTest(TestCase): tosca_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/tosca_single_instance_wordpress.yaml") - tosca = ToscaTemplate(tosca_tpl) + params = {'db_name': 'my_wordpress', 'db_user': 'my_db_user', + 'db_root_pwd': '12345678'} + tosca = ToscaTemplate(tosca_tpl, parsed_params=params) tosca_elk_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/tosca_elk.yaml") @@ -437,21 +439,30 @@ class ToscaTemplateTest(TestCase): tosca_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/tosca_single_instance_wordpress.yaml") - tosca = ToscaTemplate(tosca_tpl) + params = {'db_name': 'my_wordpress', 'db_user': 'my_db_user', + 'db_root_pwd': '12345678'} + tosca = ToscaTemplate(tosca_tpl, parsed_params=params) self.assertTrue(tosca.topology_template.custom_defs) def test_local_template_with_url_import(self): tosca_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/tosca_single_instance_wordpress_with_url_import.yaml") - tosca = ToscaTemplate(tosca_tpl) + tosca = ToscaTemplate(tosca_tpl, + parsed_params={'db_root_pwd': '123456'}) self.assertTrue(tosca.topology_template.custom_defs) def test_url_template_with_local_relpath_import(self): tosca_tpl = ('https://raw.githubusercontent.com/openstack/' 'tosca-parser/master/toscaparser/tests/data/' 'tosca_single_instance_wordpress.yaml') - tosca = ToscaTemplate(tosca_tpl, None, False) + tosca = ToscaTemplate(tosca_tpl, a_file=False, + parsed_params={"db_name": "mysql", + "db_user": "mysql", + "db_root_pwd": "1234", + "db_pwd": "5678", + "db_port": 3306, + "cpus": 4}) self.assertTrue(tosca.topology_template.custom_defs) def test_url_template_with_local_abspath_import(self): @@ -472,19 +483,27 @@ class ToscaTemplateTest(TestCase): tosca_tpl = ('https://raw.githubusercontent.com/openstack/' 'tosca-parser/master/toscaparser/tests/data/' 'tosca_single_instance_wordpress_with_url_import.yaml') - tosca = ToscaTemplate(tosca_tpl, None, False) + tosca = ToscaTemplate(tosca_tpl, a_file=False, + parsed_params={"db_root_pwd": "1234"}) self.assertTrue(tosca.topology_template.custom_defs) def test_csar_parsing_wordpress(self): csar_archive = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'data/CSAR/csar_wordpress.zip') - self.assertTrue(ToscaTemplate(csar_archive)) + self.assertTrue(ToscaTemplate(csar_archive, + parsed_params={"db_name": "mysql", + "db_user": "mysql", + "db_root_pwd": "1234", + "db_pwd": "5678", + "db_port": 3306, + "cpus": 4})) def test_csar_parsing_elk_url_based(self): csar_archive = ('https://github.com/openstack/tosca-parser/raw/master/' 'toscaparser/tests/data/CSAR/csar_elk.zip') - self.assertTrue(ToscaTemplate(csar_archive, None, False)) + self.assertTrue(ToscaTemplate(csar_archive, a_file=False, + parsed_params={"my_cpus": 4})) def test_nested_imports_in_templates(self): tosca_tpl = os.path.join( @@ -592,7 +611,7 @@ class ToscaTemplateTest(TestCase): tosca_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/CSAR/csar_elk.csar") - tosca = ToscaTemplate(tosca_tpl) + tosca = ToscaTemplate(tosca_tpl, parsed_params={"my_cpus": 2}) self.assertTrue(tosca.topology_template.custom_defs) def test_available_rel_tpls(self): @@ -658,9 +677,10 @@ class ToscaTemplateTest(TestCase): "data/tosca_single_instance_wordpress.yaml") yaml_dict_tpl = toscaparser.utils.yamlparser.load_yaml(test_tpl) - + params = {'db_name': 'my_wordpress', 'db_user': 'my_db_user', + 'db_root_pwd': '12345678'} self.assertRaises(exception.ValidationError, ToscaTemplate, None, - None, False, yaml_dict_tpl) + params, False, yaml_dict_tpl) err_msg = (_('Relative file name "custom_types/wordpress.yaml" ' 'cannot be used in a pre-parsed input template.')) exception.ExceptionCollector.assertExceptionMessage(ImportError, @@ -805,7 +825,7 @@ class ToscaTemplateTest(TestCase): tosca_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/test_containers.yaml") - ToscaTemplate(tosca_tpl) + ToscaTemplate(tosca_tpl, parsed_params={"mysql_root_pwd": "12345678"}) def test_endpoint_on_compute(self): tosca_tpl = os.path.join( diff --git a/toscaparser/tests/test_toscatplvalidation.py b/toscaparser/tests/test_toscatplvalidation.py index 15fe007f..4675c609 100644 --- a/toscaparser/tests/test_toscatplvalidation.py +++ b/toscaparser/tests/test_toscatplvalidation.py @@ -35,7 +35,9 @@ class ToscaTemplateValidationTest(TestCase): tpl_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/tosca_single_instance_wordpress.yaml") - self.assertIsNotNone(ToscaTemplate(tpl_path)) + params = {'db_name': 'my_wordpress', 'db_user': 'my_db_user', + 'db_root_pwd': '12345678'} + self.assertIsNotNone(ToscaTemplate(tpl_path, params)) def test_first_level_sections(self): tpl_path = os.path.join( @@ -120,7 +122,7 @@ class ToscaTemplateValidationTest(TestCase): self.assertEqual(expectedmessage, err.__str__()) def test_inputs(self): - tpl_snippet = ''' + tpl_snippet1 = ''' inputs: cpus: type: integer @@ -130,14 +132,33 @@ class ToscaTemplateValidationTest(TestCase): required: yes status: supported ''' - inputs = (toscaparser.utils.yamlparser. - simple_parse(tpl_snippet)['inputs']) - name, attrs = list(inputs.items())[0] - input = Input(name, attrs) - err = self.assertRaises(exception.UnknownFieldError, input.validate) - self.assertEqual(_('Input "cpus" contains unknown field "constraint". ' - 'Refer to the definition to verify valid values.'), - err.__str__()) + tpl_snippet2 = ''' + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4 ] + required: yes + status: supported + ''' + inputs1 = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet1)['inputs']) + name1, attrs1 = list(inputs1.items())[0] + inputs2 = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet2)['inputs']) + name2, attrs2 = list(inputs2.items())[0] + try: + Input(name1, attrs1) + except Exception as err: + # err=self.assertRaises(exception.UnknownFieldError, + # input1.validate) + self.assertEqual(_('Input "cpus" contains unknown field ' + '"constraint". Refer to the definition to ' + 'verify valid values.'), + err.__str__()) + input2 = Input(name2, attrs2) + self.assertTrue(input2.required) def _imports_content_test(self, tpl_snippet, path, custom_type_def): imports = (toscaparser.utils.yamlparser. diff --git a/toscaparser/topology_template.py b/toscaparser/topology_template.py index d7bd53b9..7fdd5ad5 100644 --- a/toscaparser/topology_template.py +++ b/toscaparser/topology_template.py @@ -73,6 +73,14 @@ class TopologyTemplate(object): default = input.default if default: input.validate(default) + if (self.parsed_params and input.name not in self.parsed_params + or self.parsed_params is None) and input.required \ + and input.default is None: + exception.ExceptionCollector.appendException( + exception.MissingRequiredParameterError( + what='Template', + input_name=input.name)) + inputs.append(input) return inputs