From 8e2c7bde6224a5d2076d2e962dbc0a04e8c076c6 Mon Sep 17 00:00:00 2001 From: madhavi Date: Tue, 5 Jan 2016 15:31:48 +0530 Subject: [PATCH] Validation of imported templates Validated First level sections of imported templates,First level sub-sections of node_types and policy_types and added testcases for the same Change-Id: I51f4e2496fea567fb0e5672f77899334c7556909 Partially Implements: #1516177 --- toscaparser/elements/entity_type.py | 13 +++- toscaparser/elements/nodetype.py | 26 +++++++- toscaparser/elements/policytype.py | 2 +- toscaparser/elements/statefulentitytype.py | 1 + toscaparser/elements/tosca_type_validation.py | 59 +++++++++++++++++++ toscaparser/imports.py | 4 ++ toscaparser/nodetemplate.py | 16 +++-- .../data/custom_types/imported_sample.yaml | 34 +++++++++++ .../tests/data/tosca_imports_validation.yaml | 39 ++++++++++++ toscaparser/tests/test_toscatplvalidation.py | 42 +++++++++++++ 10 files changed, 225 insertions(+), 11 deletions(-) create mode 100644 toscaparser/elements/tosca_type_validation.py create mode 100644 toscaparser/tests/data/custom_types/imported_sample.yaml create mode 100644 toscaparser/tests/data/tosca_imports_validation.yaml diff --git a/toscaparser/elements/entity_type.py b/toscaparser/elements/entity_type.py index 8e3a4d0..a8a4df0 100644 --- a/toscaparser/elements/entity_type.py +++ b/toscaparser/elements/entity_type.py @@ -12,6 +12,8 @@ import logging import os +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import ValidationError from toscaparser.extensions.exttools import ExtTools import toscaparser.utils.yamlparser @@ -82,7 +84,7 @@ class EntityType(object): value = None if defs is None: if not hasattr(self, 'defs'): - return + return None defs = self.defs if ndtype in defs: value = defs[ndtype] @@ -111,8 +113,13 @@ class EntityType(object): def get_definition(self, ndtype): value = None - defs = self.defs - if ndtype in defs: + if not hasattr(self, 'defs'): + defs = None + ExceptionCollector.appendException( + ValidationError(message="defs is " + str(defs))) + else: + defs = self.defs + if defs is not None and ndtype in defs: value = defs[ndtype] p = self.parent_type if p: diff --git a/toscaparser/elements/nodetype.py b/toscaparser/elements/nodetype.py index 2d545c7..05a9f06 100644 --- a/toscaparser/elements/nodetype.py +++ b/toscaparser/elements/nodetype.py @@ -10,6 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import UnknownFieldError +from toscaparser.common.exception import ValidationError from toscaparser.elements.capabilitytype import CapabilityTypeDef import toscaparser.elements.interfaces as ifaces from toscaparser.elements.interfaces import InterfacesDef @@ -19,16 +22,22 @@ from toscaparser.elements.statefulentitytype import StatefulEntityType class NodeType(StatefulEntityType): '''TOSCA built-in node type.''' + SECTIONS = (DERIVED_FROM, METADATA, PROPERTIES, VERSION, DESCRIPTION, ATTRIBUTES, REQUIREMENTS, CAPABILITIES, INTERFACES, ARTIFACTS) = \ + ('derived_from', 'metadata', 'properties', 'version', + 'description', 'attributes', 'requirements', 'capabilities', + 'interfaces', 'artifacts') def __init__(self, ntype, custom_def=None): super(NodeType, self).__init__(ntype, self.NODE_PREFIX, custom_def) + self.ntype = ntype self.custom_def = custom_def + self._validate_keys() @property def parent_type(self): '''Return a node this node is derived from.''' if not hasattr(self, 'defs'): - return + return None pnode = self.derived_from(self.defs) if pnode: return NodeType(pnode, self.custom_def) @@ -153,7 +162,12 @@ class NodeType(StatefulEntityType): parent_node = self.parent_type if requires is None: requires = self.get_value(self.REQUIREMENTS, None, True) - parent_node = parent_node.parent_type + if parent_node is None: + ExceptionCollector.appendException( + ValidationError(message="parent_node is " + + str(parent_node))) + else: + parent_node = parent_node.parent_type if parent_node: while parent_node.type != 'tosca.nodes.Root': req = parent_node.get_value(self.REQUIREMENTS, None, True) @@ -200,3 +214,11 @@ class NodeType(StatefulEntityType): captype = self.get_capability(name) if captype and name in captype.keys(): return captype[name].value + + def _validate_keys(self): + if self.defs: + for key in self.defs.keys(): + if key not in self.SECTIONS: + ExceptionCollector.appendException( + UnknownFieldError(what='Nodetype"%s"' % self.ntype, + field=key)) diff --git a/toscaparser/elements/policytype.py b/toscaparser/elements/policytype.py index bb1c074..04cbab5 100644 --- a/toscaparser/elements/policytype.py +++ b/toscaparser/elements/policytype.py @@ -89,7 +89,7 @@ class PolicyType(StatefulEntityType): for key in self.defs.keys(): if key not in self.SECTIONS: ExceptionCollector.appendException( - UnknownFieldError(what='Policy "%s"' % self.name, + UnknownFieldError(what='Policy "%s"' % self.type, field=key)) def _validate_targets(self, targets_list, custom_def): diff --git a/toscaparser/elements/statefulentitytype.py b/toscaparser/elements/statefulentitytype.py index 9c8f4e2..1f1ba03 100644 --- a/toscaparser/elements/statefulentitytype.py +++ b/toscaparser/elements/statefulentitytype.py @@ -39,6 +39,7 @@ class StatefulEntityType(EntityType): elif custom_def and entitytype in list(custom_def.keys()): self.defs = custom_def[entitytype] else: + self.defs = None ExceptionCollector.appendException( InvalidTypeError(what=entitytype)) self.type = entitytype diff --git a/toscaparser/elements/tosca_type_validation.py b/toscaparser/elements/tosca_type_validation.py new file mode 100644 index 0000000..899c718 --- /dev/null +++ b/toscaparser/elements/tosca_type_validation.py @@ -0,0 +1,59 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import InvalidTemplateVersion +from toscaparser.common.exception import UnknownFieldError +from toscaparser.extensions.exttools import ExtTools + + +class TypeValidation(object): + + ALLOWED_TYPE_SECTIONS = (DEFINITION_VERSION, DESCRIPTION, IMPORTS, + DSL_DEFINITIONS, NODE_TYPES, REPOSITORIES, + DATA_TYPES, ARTIFACT_TYPES, GROUP_TYPES, + RELATIONSHIP_TYPES, CAPABILITY_TYPES, + INTERFCAE_TYPES, POLICY_TYPES, DATATYPE_DEFINITIONS) = \ + ('tosca_definitions_version', 'description', + 'imports', 'dsl_definitions', 'node_types', + 'repositories', 'data_types', 'group_types', + 'artifact_types', 'relationship_types', + 'capability_types', 'interface_types', + 'policy_types', 'datatype_definitions') + VALID_TEMPLATE_VERSIONS = ['tosca_simple_yaml_1_0'] + exttools = ExtTools() + VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions()) + + def __init__(self, custom_types, import_def): + self.import_def = import_def + self._validate_type_keys(custom_types) + + def _validate_type_keys(self, custom_type): + version = custom_type[self.DEFINITION_VERSION] \ + if self.DEFINITION_VERSION in custom_type \ + else None + if version: + self._validate_type_version(version) + self.version = version + + for name in custom_type: + if name not in self.ALLOWED_TYPE_SECTIONS: + ExceptionCollector.appendException( + UnknownFieldError(what='Template ' + (self.import_def), + field=name)) + + def _validate_type_version(self, version): + if version not in self.VALID_TEMPLATE_VERSIONS: + ExceptionCollector.appendException( + InvalidTemplateVersion( + what=version + ' in ' + self.import_def, + valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS))) diff --git a/toscaparser/imports.py b/toscaparser/imports.py index 4107970..c342d19 100644 --- a/toscaparser/imports.py +++ b/toscaparser/imports.py @@ -17,6 +17,7 @@ from toscaparser.common.exception import ExceptionCollector from toscaparser.common.exception import MissingRequiredFieldError from toscaparser.common.exception import UnknownFieldError from toscaparser.common.exception import ValidationError +from toscaparser.elements.tosca_type_validation import TypeValidation from toscaparser.utils.gettextutils import _ import toscaparser.utils.urlutils import toscaparser.utils.yamlparser @@ -79,11 +80,14 @@ class ImportsLoader(object): namespace_prefix = import_uri.get( self.NAMESPACE_PREFIX) if custom_type: + TypeValidation(custom_type, import_def) self._update_custom_def(custom_type, namespace_prefix) else: # old style of imports custom_type = self._load_import_template(None, import_def) if custom_type: + TypeValidation( + custom_type, import_def) self._update_custom_def(custom_type, None) def _update_custom_def(self, custom_type, namespace_prefix): diff --git a/toscaparser/nodetemplate.py b/toscaparser/nodetemplate.py index 3991f05..517ec14 100644 --- a/toscaparser/nodetemplate.py +++ b/toscaparser/nodetemplate.py @@ -18,6 +18,7 @@ from toscaparser.common.exception import InvalidPropertyValueError from toscaparser.common.exception import MissingRequiredFieldError from toscaparser.common.exception import TypeMismatchError from toscaparser.common.exception import UnknownFieldError +from toscaparser.common.exception import ValidationError from toscaparser.dataentity import DataEntity from toscaparser.elements.interfaces import CONFIGURE from toscaparser.elements.interfaces import CONFIGURE_SHORTNAME @@ -93,11 +94,16 @@ class NodeTemplate(EntityTemplate): # check if it's type has relationship defined if not relationship: parent_reqs = self.type_definition.get_all_requirements() - for key in req.keys(): - for req_dict in parent_reqs: - if key in req_dict.keys(): - relationship = (req_dict.get(key). - get('relationship')) + if parent_reqs is None: + ExceptionCollector.appendException( + ValidationError(message='parent_req is ' + + str(parent_reqs))) + else: + for key in req.keys(): + for req_dict in parent_reqs: + if key in req_dict.keys(): + relationship = (req_dict.get(key). + get('relationship')) break if relationship: found_relationship_tpl = False diff --git a/toscaparser/tests/data/custom_types/imported_sample.yaml b/toscaparser/tests/data/custom_types/imported_sample.yaml new file mode 100644 index 0000000..70d0b0f --- /dev/null +++ b/toscaparser/tests/data/custom_types/imported_sample.yaml @@ -0,0 +1,34 @@ +tosca1_definitions_version: tosca_simple_yaml_1_0 +tosca_definitions_version: tosca_simple_yaml_1_10 + +descriptions: > + Pizza store app that allows you to explore the features provided by PayPal's REST APIs. + More detail can be found at https://github.com/paypal/rest-api-sample-app-nodejs/ + +node_typess: +node_types: + tosca.nodes.SoftwareComponent.Logstash: + derived_from: tosca.nodes.SoftwareComponent + requirements: + - search_endpoint: + capability: tosca.capabilities.Endpoint + node: tosca.nodes.SoftwareComponent.Elasticsearch + relationship: + type: tosca.relationships.ConnectsTo + interfaces: + Configure: + pre_configure_source: + inputs: + elasticsearch_ip: + type: string + capabilities1: + log_endpoint: + type: tosca.capabilities.Endpoint +policy_types1: +policy_types: + mycompany.mytypes.myScalingPolicy: + derived1_from: tosca.policies.Scaling + metadata: + type: map + entry_schema: + type: string diff --git a/toscaparser/tests/data/tosca_imports_validation.yaml b/toscaparser/tests/data/tosca_imports_validation.yaml new file mode 100644 index 0000000..9c3fef4 --- /dev/null +++ b/toscaparser/tests/data/tosca_imports_validation.yaml @@ -0,0 +1,39 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Template to test invalid imports. + +imports: + - custom_types/imported_sample.yaml + +topology_template: + node_templates: + logstash: + type: tosca.nodes.SoftwareComponent.Logstash + requirements: + - search_endpoint: + capability: search_endpoint + relationship: + type: tosca.relationships.ConnectsTo + interfaces: + Configure: + pre_configure_source: + implementation: logstash/configure_elasticsearch.py + inputs: + elasticsearch_ip: { get_attribute: [elasticsearch_server, private_address] } + interfaces: + Standard: + create: logstash/create.sh + start: logstash/start.sh + policies: + - my_compute_placement_policy: + type: tosca.policies.Placement + description: Apply placement policy to servers + metadata: { user1: 1001, user2: 1002 } + targets: [ my_server_1, my_server_2 ] + - my_groups_placement: + type: mycompany.mytypes.myScalingPolicy + targets: [ webserver_group ] + description: my company scaling policy + metadata: + user1: 1001 + user2: 1003 diff --git a/toscaparser/tests/test_toscatplvalidation.py b/toscaparser/tests/test_toscatplvalidation.py index 9b480d9..74ddf30 100644 --- a/toscaparser/tests/test_toscatplvalidation.py +++ b/toscaparser/tests/test_toscatplvalidation.py @@ -54,6 +54,48 @@ class ToscaTemplateValidationTest(TestCase): _('Template contains unknown field "node_template". Refer to the ' 'definition to verify valid values.')) + def test_template_with_imports_validation(self): + tpl_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_imports_validation.yaml") + self.assertRaises(exception.ValidationError, ToscaTemplate, tpl_path) + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, + _('Template custom_types/imported_sample.yaml contains unknown ' + 'field "descriptions". Refer to the definition' + ' to verify valid values.')) + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, + _('Template custom_types/imported_sample.yaml contains unknown ' + 'field "node_typess". Refer to the definition to ' + 'verify valid values.')) + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, + _('Template custom_types/imported_sample.yaml contains unknown ' + 'field "tosca1_definitions_version". Refer to the definition' + ' to verify valid values.')) + exception.ExceptionCollector.assertExceptionMessage( + exception.InvalidTemplateVersion, + _('The template version "tosca_simple_yaml_1_10 in ' + 'custom_types/imported_sample.yaml" is invalid. ' + 'Valid versions are "tosca_simple_yaml_1_0, ' + 'tosca_simple_profile_for_nfv_1_0_0".')) + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, + _('Template custom_types/imported_sample.yaml contains unknown ' + 'field "policy_types1". Refer to the definition to ' + 'verify valid values.')) + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, + _('Nodetype"tosca.nodes.SoftwareComponent.Logstash" contains ' + 'unknown field "capabilities1". Refer to the definition ' + 'to verify valid values.')) + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, + _('Policy "mycompany.mytypes.myScalingPolicy" contains unknown ' + 'field "derived1_from". Refer to the definition to ' + 'verify valid values.')) + def test_inputs(self): tpl_snippet = ''' inputs: