diff options
Diffstat (limited to 'tosca2heat/tosca-parser')
5 files changed, 211 insertions, 35 deletions
diff --git a/tosca2heat/tosca-parser/toscaparser/common/exception.py b/tosca2heat/tosca-parser/toscaparser/common/exception.py index f67a277..724844b 100644 --- a/tosca2heat/tosca-parser/toscaparser/common/exception.py +++ b/tosca2heat/tosca-parser/toscaparser/common/exception.py @@ -116,14 +116,19 @@ class UnknownInputError(TOSCAException): class MissingRequiredInputError(TOSCAException): msg_fmt = _('%(what)s is missing required input definition ' - ' with name: "%(input_name)s".') + 'of input "%(input_name)s".') class MissingRequiredParameterError(TOSCAException): - msg_fmt = _('%(what)s is missing required parameter for input: ' + msg_fmt = _('%(what)s is missing required parameter for input ' '"%(input_name)s".') +class MissingDefaultValueError(TOSCAException): + msg_fmt = _('%(what)s is missing required default value ' + 'of input "%(input_name)s".') + + class InvalidPropertyValueError(TOSCAException): msg_fmt = _('Value of property "%(what)s" is invalid.') diff --git a/tosca2heat/tosca-parser/toscaparser/substitution_mappings.py b/tosca2heat/tosca-parser/toscaparser/substitution_mappings.py index 35ffdf3..d4653c3 100644 --- a/tosca2heat/tosca-parser/toscaparser/substitution_mappings.py +++ b/tosca2heat/tosca-parser/toscaparser/substitution_mappings.py @@ -14,9 +14,9 @@ import logging from toscaparser.common.exception import ExceptionCollector from toscaparser.common.exception import InvalidNodeTypeError +from toscaparser.common.exception import MissingDefaultValueError from toscaparser.common.exception import MissingRequiredFieldError from toscaparser.common.exception import MissingRequiredInputError -from toscaparser.common.exception import MissingRequiredParameterError from toscaparser.common.exception import UnknownFieldError from toscaparser.elements.nodetype import NodeType from toscaparser.utils.gettextutils import _ @@ -27,7 +27,7 @@ log = logging.getLogger('tosca') class SubstitutionMappings(object): '''SubstitutionMappings class declaration - Substitution_mappings exports the topology template as an + SubstitutionMappings exports the topology template as an implementation of a Node type. ''' @@ -74,7 +74,7 @@ class SubstitutionMappings(object): return NodeType(self.node_type, self.custom_defs) def _validate(self): - # basic valiation + # Basic validation self._validate_keys() self._validate_type() @@ -104,48 +104,59 @@ class SubstitutionMappings(object): node_type_def = self.custom_defs.get(node_type) if not node_type_def: ExceptionCollector.appendException( - InvalidNodeTypeError(what=node_type_def)) + InvalidNodeTypeError(what=node_type)) def _validate_inputs(self): """validate the inputs of substitution mappings. - The inputs in service template which provides substutition mappings - must be in properties of node template which is mapped or provide - defualt value. Currently the input.name is not restrict to be the - same as property name in specification, but they should be equal - for current implementation. + The inputs defined by the topology template have to match the + properties of the node type or the substituted node. If there are + more inputs than the substituted node has properties, default values + must be defined for those inputs. """ - # Must provide parameters for required properties of node_type - # This checking is internal(inside SubstitutionMappings) - for propery in self.node_definition.get_properties_def_objects(): + all_inputs = set([input.name for input in self.inputs]) + required_properties = set([p.name for p in + self.node_definition. + get_properties_def_objects() + if p.required and p.default is None]) + # Must provide inputs for required properties of node type. + for property in required_properties: # Check property which is 'required' and has no 'default' value - if propery.required and propery.default is None and \ - propery.name not in [input.name for input in self.inputs]: + if property not in all_inputs: ExceptionCollector.appendException( MissingRequiredInputError( - what=_('SubstitutionMappings with node_type:') + what=_('SubstitutionMappings with node_type ') + self.node_type, - input_name=propery.name)) - - # Get property names from substituted node tempalte - property_names = list(self.sub_mapped_node_template - .get_properties().keys() - if self.sub_mapped_node_template else []) - # Sub_mapped_node_template is None(deploy standaolone), will check - # according to node_type - if 0 == len(property_names): - property_names = list(self.node_definition - .get_properties_def().keys()) - # Provide default value for parameter which is not property of - # node with the type node_type, this may not be mandatory for - # current implematation, but the specification express it mandatory. - # This checking is external(outside SubstitutionMappings) + input_name=property)) + + # If the optional properties of node type need to be customized by + # substituted node, it also is necessary to define inputs for them, + # otherwise they are not mandatory to be defined. + customized_parameters = set(self.sub_mapped_node_template + .get_properties().keys() + if self.sub_mapped_node_template else []) + all_properties = set([p.name for p in + self.node_definition. + get_properties_def_objects()]) + for parameter in customized_parameters - all_inputs: + if parameter in all_properties: + ExceptionCollector.appendException( + MissingRequiredInputError( + what=_('SubstitutionMappings with node_type ') + + self.node_type, + input_name=parameter)) + + # Additional inputs are not in the properties of node type must + # provide default values. Currently the scenario may not happen + # because of parameters validation in nodetemplate, here is a + # guarantee. for input in self.inputs: - if input.name not in property_names and input.default is None: + if input.name in all_inputs - all_properties \ + and input.default is None: ExceptionCollector.appendException( - MissingRequiredParameterError( - what=_('SubstitutionMappings with node_type:') + MissingDefaultValueError( + what=_('SubstitutionMappings with node_type ') + self.node_type, input_name=input.name)) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/validate/queuingsubsystem_invalid_input.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/validate/queuingsubsystem_invalid_input.yaml new file mode 100644 index 0000000..c54c12c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/validate/queuingsubsystem_invalid_input.yaml @@ -0,0 +1,79 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + This template is a test template which contains invalid input needed for substitution mappings. + The required properties without default value in substituted node template which be mapped must be + as inputs of nested service template which defines substutition mappings, and the inputs of nested + service template which are not in the properties of the substituted node template must provide + default values. + This template provides an additional input of server_port1/my_cpus/my_input which are not defined + in example.QueuingSubsystem, and the default value are 8080/2/123, all of these are right. But the + required property of server_port defined in example.QueuingSubsystem is not appeared in inputs + definiton, so will raise excepton of "MissingRequiredInputError". + +imports: + - ../definitions.yaml + +topology_template: + description: Template of a database including its hosting stack. + + inputs: + server_ip: + type: string + description: IP address of the message queuing server to receive messages from. + default: 127.0.0.1 + server_port1: + type: integer + description: Port to be used for receiving messages. + default: 8080 + my_cpus: + type: integer + description: Number of CPUs for the server. + default: 2 + constraints: + - valid_values: [ 1, 2, 4, 8 ] + my_input: + type: integer + description: test for input validation. + default: 123 + + substitution_mappings: + node_type: example.QueuingSubsystem + requirements: + receiver1: [ tran_app, receiver1 ] + receiver2: [ tran_app, receiver2 ] + + node_templates: + tran_app: + type: example.QueuingSubsystem + properties: + server_ip: { get_input: server_ip } + server_port: { get_input: server_port1 } + requirements: + - host: + node: server + + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: my_cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Ubuntu + version: 14.04 + + outputs: + receiver_ip: + description: private IP address of the message receiver application + value: { get_attribute: [ server, private_address ] } + + groups: + tran_server_group: + members: [ tran_app, server ] + type: tosca.groups.Root diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/validate/system_invalid_input.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/validate/system_invalid_input.yaml new file mode 100644 index 0000000..e3cdd71 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/validate/system_invalid_input.yaml @@ -0,0 +1,24 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +imports: + - queuingsubsystem_invalid_input.yaml + +topology_template: + description: Test template with invalid input. + + inputs: + mq_server_ip: + type: string + default: 127.0.0.1 + description: IP address of the message queuing server to receive messages from. + mq_server_port: + type: integer + default: 8080 + description: Port to be used for receiving messages. + + node_templates: + mq: + type: example.QueuingSubsystem + properties: + server_ip: { get_input: mq_server_ip } + server_port: { get_input: mq_server_port } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py b/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py index edb834b..6974d52 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py @@ -55,6 +55,18 @@ class TopologyTemplateTest(TestCase): custom_defs.update(self._get_custom_def('capability_types')) return custom_defs + def _get_custom_types(self): + custom_types = {} + def_file = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/topology_template/definitions.yaml") + custom_type = YAML_LOADER(def_file) + node_types = custom_type['node_types'] + for name in node_types: + defintion = node_types[name] + custom_types[name] = defintion + return custom_types + def test_description(self): expected_desc = 'Template of a database including its hosting stack.' self.assertEqual(expected_desc, self.topo.description) @@ -165,6 +177,7 @@ class TopologyTemplateTest(TestCase): self.assertEqual( len(system_tosca_template. nested_tosca_templates_with_topology), 4) + self.assertTrue(system_tosca_template.has_nested_templates()) def test_invalid_keyname(self): tpl_snippet = ''' @@ -205,3 +218,47 @@ class TopologyTemplateTest(TestCase): lambda: SubstitutionMappings(sub_mappings, None, None, None, None, None)) self.assertEqual(expected_message, err.__str__()) + + def test_invalid_nodetype(self): + tpl_snippet = ''' + substitution_mappings: + node_type: example.DatabaseSubsystem1 + capabilities: + database_endpoint: [ db_app, database_endpoint ] + requirements: + receiver1: [ tran_app, receiver1 ] + ''' + sub_mappings = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet))['substitution_mappings'] + custom_defs = self._get_custom_types() + expected_message = _('Node type "example.DatabaseSubsystem1" ' + 'is not a valid type.') + err = self.assertRaises( + exception.InvalidNodeTypeError, + lambda: SubstitutionMappings(sub_mappings, None, None, + None, None, custom_defs)) + self.assertEqual(expected_message, err.__str__()) + + def test_system_with_input_validation(self): + tpl_path0 = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/topology_template/validate/system_invalid_input.yaml") + tpl_path1 = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/topology_template/validate/" + "queuingsubsystem_invalid_input.yaml") + errormsg = _('SubstitutionMappings with node_type ' + 'example.QueuingSubsystem is missing ' + 'required input definition of input "server_port".') + + # It's invalid in nested template. + self.assertRaises(exception.ValidationError, + lambda: ToscaTemplate(tpl_path0)) + exception.ExceptionCollector.assertExceptionMessage( + exception.MissingRequiredInputError, errormsg) + + # Subtemplate deploy standaolone is also invalid. + self.assertRaises(exception.ValidationError, + lambda: ToscaTemplate(tpl_path1)) + exception.ExceptionCollector.assertExceptionMessage( + exception.MissingRequiredInputError, errormsg) |