diff options
Diffstat (limited to 'tosca2heat/tosca-parser')
43 files changed, 1183 insertions, 137 deletions
diff --git a/tosca2heat/tosca-parser/.gitignore b/tosca2heat/tosca-parser/.gitignore new file mode 100644 index 0000000..c172104 --- /dev/null +++ b/tosca2heat/tosca-parser/.gitignore @@ -0,0 +1,56 @@ +*.py[cod] + +# C extensions +*.so + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox +nosetests.xml +.testrepository + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Complexity +output/*.html +output/*/index.html + +# Sphinx +doc/build + +# pbr generates these +AUTHORS +ChangeLog + +# Editors +*~ +.*.swp +.idea +*.iml + +# OSX +.DS_Store
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/.gitreview b/tosca2heat/tosca-parser/.gitreview new file mode 100644 index 0000000..9105f23 --- /dev/null +++ b/tosca2heat/tosca-parser/.gitreview @@ -0,0 +1,4 @@ +[gerrit] +host=review.openstack.org +port=29418 +project=openstack/tosca-parser.git diff --git a/tosca2heat/tosca-parser/.mailmap b/tosca2heat/tosca-parser/.mailmap new file mode 100644 index 0000000..cc92f17 --- /dev/null +++ b/tosca2heat/tosca-parser/.mailmap @@ -0,0 +1,3 @@ +# Format is: +# <preferred e-mail> <other e-mail 1> +# <preferred e-mail> <other e-mail 2>
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/requirements.txt b/tosca2heat/tosca-parser/requirements.txt index dd27a53..b30cc51 100644 --- a/tosca2heat/tosca-parser/requirements.txt +++ b/tosca2heat/tosca-parser/requirements.txt @@ -2,8 +2,9 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr>=1.6 # Apache-2.0 -Babel>=1.3 # BSD +Babel>=2.3.4 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 PyYAML>=3.1.0 # MIT python-dateutil>=2.4.2 # BSD six>=1.9.0 # MIT +requests>=2.10.0 # Apache-2.0 diff --git a/tosca2heat/tosca-parser/test-requirements.txt b/tosca2heat/tosca-parser/test-requirements.txt index 2b9cae7..70a261b 100644 --- a/tosca2heat/tosca-parser/test-requirements.txt +++ b/tosca2heat/tosca-parser/test-requirements.txt @@ -4,11 +4,11 @@ hacking<0.11,>=0.10.0 coverage>=3.6 # Apache-2.0 discover # BSD -fixtures>=1.3.1 # Apache-2.0/BSD +fixtures>=3.0.0 # Apache-2.0/BSD oslotest>=1.10.0 # Apache-2.0 oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 python-subunit>=0.0.18 # Apache-2.0/BSD -sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD +sphinx!=1.3b1,<1.3,>=1.2.1 # BSD testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=1.4.0 # MIT diff --git a/tosca2heat/tosca-parser/toscaparser/common/exception.py b/tosca2heat/tosca-parser/toscaparser/common/exception.py index 4f99dda..bd0ed9c 100644 --- a/tosca2heat/tosca-parser/toscaparser/common/exception.py +++ b/tosca2heat/tosca-parser/toscaparser/common/exception.py @@ -88,6 +88,15 @@ class InvalidTypeError(TOSCAException): msg_fmt = _('Type "%(what)s" is not a valid type.') +class InvalidTypeAdditionalRequirementsError(TOSCAException): + msg_fmt = _('Additional requirements for type "%(type)s" not met.') + + +class RangeValueError(TOSCAException): + msg_fmt = _('The value "%(pvalue)s" of property "%(pname)s" is out of ' + 'range "(min:%(vmin)s, max:%(vmax)s)".') + + class InvalidSchemaError(TOSCAException): msg_fmt = _('%(message)s') diff --git a/tosca2heat/tosca-parser/toscaparser/dataentity.py b/tosca2heat/tosca-parser/toscaparser/dataentity.py index 6e7d59e..507c899 100644 --- a/tosca2heat/tosca-parser/toscaparser/dataentity.py +++ b/tosca2heat/tosca-parser/toscaparser/dataentity.py @@ -16,10 +16,10 @@ from toscaparser.common.exception import TypeMismatchError from toscaparser.common.exception import UnknownFieldError from toscaparser.elements.constraints import Schema from toscaparser.elements.datatype import DataType +from toscaparser.elements.portspectype import PortSpec from toscaparser.elements.scalarunit import ScalarUnit_Frequency from toscaparser.elements.scalarunit import ScalarUnit_Size from toscaparser.elements.scalarunit import ScalarUnit_Time - from toscaparser.utils.gettextutils import _ from toscaparser.utils import validateutils @@ -27,11 +27,13 @@ from toscaparser.utils import validateutils class DataEntity(object): '''A complex data value entity.''' - def __init__(self, datatypename, value_dict, custom_def=None): + def __init__(self, datatypename, value_dict, custom_def=None, + prop_name=None): self.custom_def = custom_def self.datatype = DataType(datatypename, custom_def) self.schema = self.datatype.get_all_properties() self.value = value_dict + self.property_name = prop_name def validate(self): '''Validate the value by the definition of the datatype.''' @@ -43,7 +45,7 @@ class DataEntity(object): self.value, None, self.custom_def) - schema = Schema(None, self.datatype.defs) + schema = Schema(self.property_name, self.datatype.defs) for constraint in schema.constraints: constraint.validate(self.value) # If the datatype has 'properties' definition @@ -89,7 +91,10 @@ class DataEntity(object): # check every field for name, value in list(self.value.items()): - prop_schema = Schema(name, self._find_schema(name)) + schema_name = self._find_schema(name) + if not schema_name: + continue + prop_schema = Schema(name, schema_name) # check if field value meets type defined DataEntity.validate_datatype(prop_schema.type, value, prop_schema.entry_schema, @@ -110,12 +115,16 @@ class DataEntity(object): return self.schema[name].schema @staticmethod - def validate_datatype(type, value, entry_schema=None, custom_def=None): + def validate_datatype(type, value, entry_schema=None, custom_def=None, + prop_name=None): '''Validate value with given type. If type is list or map, validate its entry by entry_schema(if defined) If type is a user-defined complex datatype, custom_def is required. ''' + from toscaparser.functions import is_function + if is_function(value): + return value if type == Schema.STRING: return validateutils.validate_string(value) elif type == Schema.INTEGER: @@ -123,7 +132,7 @@ class DataEntity(object): elif type == Schema.FLOAT: return validateutils.validate_float(value) elif type == Schema.NUMBER: - return validateutils.validate_number(value) + return validateutils.validate_numeric(value) elif type == Schema.BOOLEAN: return validateutils.validate_boolean(value) elif type == Schema.RANGE: @@ -149,6 +158,10 @@ class DataEntity(object): if entry_schema: DataEntity.validate_entry(value, entry_schema, custom_def) return value + elif type == Schema.PORTSPEC: + # TODO(TBD) bug 1567063, validate source & target as PortDef type + # as complex types not just as integers + PortSpec.validate_additional_req(value, prop_name, custom_def) else: data = DataEntity(type, value, custom_def) return data.validate() diff --git a/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml b/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml index c1c1cd4..ede5fa5 100644 --- a/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml +++ b/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml @@ -390,7 +390,7 @@ tosca.nodes.Container.Application: requirements: - host: capability: tosca.capabilities.Container - node: tosca.nodes.Container + node: tosca.nodes.Container.Runtime relationship: tosca.relationships.HostedOn tosca.nodes.Container.Runtime: @@ -544,7 +544,7 @@ tosca.capabilities.Endpoint: tosca.capabilities.Endpoint.Admin: derived_from: tosca.capabilities.Endpoint properties: - secure: + secure: type: boolean default: true constraints: @@ -815,14 +815,19 @@ tosca.datatypes.Credential: properties: protocol: type: string + required: false token_type: type: string + default: password + required: true token: type: string + required: true keys: type: map entry_schema: type: string + required: false user: type: string required: false diff --git a/tosca2heat/tosca-parser/toscaparser/elements/capabilitytype.py b/tosca2heat/tosca-parser/toscaparser/elements/capabilitytype.py index 0413443..865690e 100644 --- a/tosca2heat/tosca-parser/toscaparser/elements/capabilitytype.py +++ b/tosca2heat/tosca-parser/toscaparser/elements/capabilitytype.py @@ -16,6 +16,7 @@ from toscaparser.elements.statefulentitytype import StatefulEntityType class CapabilityTypeDef(StatefulEntityType): '''TOSCA built-in capabilities type.''' + TOSCA_TYPEURI_CAPABILITY_ROOT = 'tosca.capabilities.Root' def __init__(self, name, ctype, ntype, custom_def=None): self.name = name @@ -23,6 +24,7 @@ class CapabilityTypeDef(StatefulEntityType): custom_def) self.nodetype = ntype self.properties = None + self.custom_def = custom_def if self.PROPERTIES in self.defs: self.properties = self.defs[self.PROPERTIES] self.parent_capabilities = self._get_parent_capabilities(custom_def) @@ -61,7 +63,8 @@ class CapabilityTypeDef(StatefulEntityType): capabilities = {} parent_cap = self.parent_type if parent_cap: - while parent_cap != 'tosca.capabilities.Root': + parent_cap = parent_cap.type + while parent_cap != self.TOSCA_TYPEURI_CAPABILITY_ROOT: if parent_cap in self.TOSCA_DEF.keys(): capabilities[parent_cap] = self.TOSCA_DEF[parent_cap] elif custom_def and parent_cap in custom_def.keys(): @@ -72,4 +75,9 @@ class CapabilityTypeDef(StatefulEntityType): @property def parent_type(self): '''Return a capability this capability is derived from.''' - return self.derived_from(self.defs) + if not hasattr(self, 'defs'): + return None + pnode = self.derived_from(self.defs) + if pnode: + return CapabilityTypeDef(self.name, pnode, + self.nodetype, self.custom_def) diff --git a/tosca2heat/tosca-parser/toscaparser/elements/constraints.py b/tosca2heat/tosca-parser/toscaparser/elements/constraints.py index 9883da3..8594b85 100644 --- a/tosca2heat/tosca-parser/toscaparser/elements/constraints.py +++ b/tosca2heat/tosca-parser/toscaparser/elements/constraints.py @@ -18,6 +18,7 @@ import toscaparser from toscaparser.common.exception import ExceptionCollector from toscaparser.common.exception import InvalidSchemaError from toscaparser.common.exception import ValidationError +from toscaparser.elements.portspectype import PortSpec from toscaparser.elements import scalarunit from toscaparser.utils.gettextutils import _ @@ -36,12 +37,12 @@ class Schema(collections.Mapping): INTEGER, STRING, BOOLEAN, FLOAT, RANGE, NUMBER, TIMESTAMP, LIST, MAP, SCALAR_UNIT_SIZE, SCALAR_UNIT_FREQUENCY, SCALAR_UNIT_TIME, - PORTDEF, VERSION + VERSION, PORTDEF, PORTSPEC ) = ( 'integer', 'string', 'boolean', 'float', 'range', 'number', 'timestamp', 'list', 'map', 'scalar-unit.size', 'scalar-unit.frequency', 'scalar-unit.time', - 'PortDef', 'version' + 'version', 'PortDef', PortSpec.SHORTNAME ) SCALAR_UNIT_SIZE_DEFAULT = 'B' @@ -127,8 +128,6 @@ class Constraint(object): 'less_or_equal', 'in_range', 'valid_values', 'length', 'min_length', 'max_length', 'pattern') - UNBOUNDED = 'UNBOUNDED' - def __new__(cls, property_name, property_type, constraint): if cls is not Constraint: return super(Constraint, cls).__new__(cls) @@ -370,6 +369,7 @@ class InRange(Constraint): Constrains a property or parameter to a value in range of (inclusive) the two values declared. """ + UNBOUNDED = 'UNBOUNDED' constraint_key = Constraint.IN_RANGE diff --git a/tosca2heat/tosca-parser/toscaparser/elements/datatype.py b/tosca2heat/tosca-parser/toscaparser/elements/datatype.py index 7e05a69..93d1b3a 100644 --- a/tosca2heat/tosca-parser/toscaparser/elements/datatype.py +++ b/tosca2heat/tosca-parser/toscaparser/elements/datatype.py @@ -18,7 +18,8 @@ class DataType(StatefulEntityType): '''TOSCA built-in and user defined complex data type.''' def __init__(self, datatypename, custom_def=None): - super(DataType, self).__init__(datatypename, self.DATATYPE_PREFIX, + super(DataType, self).__init__(datatypename, + self.DATATYPE_NETWORK_PREFIX, custom_def) self.custom_def = custom_def diff --git a/tosca2heat/tosca-parser/toscaparser/elements/entity_type.py b/tosca2heat/tosca-parser/toscaparser/elements/entity_type.py index 72e7e3f..5947b1c 100644 --- a/tosca2heat/tosca-parser/toscaparser/elements/entity_type.py +++ b/tosca2heat/tosca-parser/toscaparser/elements/entity_type.py @@ -56,7 +56,8 @@ class EntityType(object): GROUP_PREFIX = 'tosca.groups.' # currently the data types are defined only for network # but may have changes in the future. - DATATYPE_PREFIX = 'tosca.datatypes.network.' + DATATYPE_PREFIX = 'tosca.datatypes.' + DATATYPE_NETWORK_PREFIX = DATATYPE_PREFIX + 'network.' TOSCA = 'tosca' def derived_from(self, defs): diff --git a/tosca2heat/tosca-parser/toscaparser/elements/portspectype.py b/tosca2heat/tosca-parser/toscaparser/elements/portspectype.py new file mode 100644 index 0000000..d32e97e --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/portspectype.py @@ -0,0 +1,86 @@ +# 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. + +import logging + +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import InvalidTypeAdditionalRequirementsError +from toscaparser.utils.gettextutils import _ +import toscaparser.utils.validateutils as validateutils + +log = logging.getLogger('tosca') + + +class PortSpec(object): + '''Parent class for tosca.datatypes.network.PortSpec type.''' + + SHORTNAME = 'PortSpec' + TYPE_URI = 'tosca.datatypes.network.' + SHORTNAME + + PROPERTY_NAMES = ( + PROTOCOL, SOURCE, SOURCE_RANGE, + TARGET, TARGET_RANGE + ) = ( + 'protocol', 'source', 'source_range', + 'target', 'target_range' + ) + + # TODO(TBD) May want to make this a subclass of DataType + # and change init method to set PortSpec's properties + def __init__(self): + pass + + # The following additional requirements MUST be tested: + # 1) A valid PortSpec MUST have at least one of the following properties: + # target, target_range, source or source_range. + # 2) A valid PortSpec MUST have a value for the source property that + # is within the numeric range specified by the property source_range + # when source_range is specified. + # 3) A valid PortSpec MUST have a value for the target property that is + # within the numeric range specified by the property target_range + # when target_range is specified. + @staticmethod + def validate_additional_req(properties, prop_name, custom_def=None, ): + try: + source = properties.get(PortSpec.SOURCE) + source_range = properties.get(PortSpec.SOURCE_RANGE) + target = properties.get(PortSpec.TARGET) + target_range = properties.get(PortSpec.TARGET_RANGE) + + # verify one of the specified values is set + if source is None and source_range is None and \ + target is None and target_range is None: + ExceptionCollector.appendException( + InvalidTypeAdditionalRequirementsError( + type=PortSpec.TYPE_URI)) + # Validate source value is in specified range + if source and source_range: + validateutils.validate_value_in_range(source, source_range, + PortSpec.SOURCE) + else: + from toscaparser.dataentity import DataEntity + portdef = DataEntity('PortDef', source, None, PortSpec.SOURCE) + portdef.validate() + # Validate target value is in specified range + if target and target_range: + validateutils.validate_value_in_range(target, target_range, + PortSpec.TARGET) + else: + from toscaparser.dataentity import DataEntity + portdef = DataEntity('PortDef', source, None, PortSpec.TARGET) + portdef.validate() + except Exception: + msg = _('"%(value)s" do not meet requirements ' + 'for type "%(type)s".') \ + % {'value': properties, 'type': PortSpec.SHORTNAME} + ExceptionCollector.appendException( + ValueError(msg)) diff --git a/tosca2heat/tosca-parser/toscaparser/entity_template.py b/tosca2heat/tosca-parser/toscaparser/entity_template.py index f416c99..7488c33 100644 --- a/tosca2heat/tosca-parser/toscaparser/entity_template.py +++ b/tosca2heat/tosca-parser/toscaparser/entity_template.py @@ -81,15 +81,11 @@ class EntityTemplate(object): def type(self): if self.type_definition: return self.type_definition.type - else: - return None @property def parent_type(self): if self.type_definition: return self.type_definition.parent_type - else: - return None @property def requirements(self): @@ -189,7 +185,10 @@ class EntityTemplate(object): def _validate_capabilities_properties(self, capabilities): for cap, props in capabilities.items(): - capabilitydef = self.get_capability(cap).definition + capability = self.get_capability(cap) + if not capability: + continue + capabilitydef = capability.definition self._common_validate_properties(capabilitydef, props[self.PROPERTIES]) diff --git a/tosca2heat/tosca-parser/toscaparser/extensions/exttools.py b/tosca2heat/tosca-parser/toscaparser/extensions/exttools.py index 963b958..5310422 100644 --- a/tosca2heat/tosca-parser/toscaparser/extensions/exttools.py +++ b/tosca2heat/tosca-parser/toscaparser/extensions/exttools.py @@ -36,7 +36,7 @@ class ExtTools(object): extdirs = [e for e in os.listdir(abs_path) if not e.startswith('tests') and - not e.endswith('.pyc') and not e.endswith('.py')] + os.path.isdir(os.path.join(abs_path, e))] for e in extdirs: log.info(e) diff --git a/tosca2heat/tosca-parser/toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml index 660cdc0..dc986e5 100644 --- a/tosca2heat/tosca-parser/toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml +++ b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml @@ -235,16 +235,3 @@ tosca.groups.nfv.VNFFG: type: string required: true description: Reference to a list of VNFD used in this VNF Forwarding Graph - - targets: - type: list - entry_schema: - type: string - required: false - description: list of Network Forwarding Path within the VNFFG - - requirements: - - forwarder: - capability: tosca.capabilities.nfv.Forwarder - relationship: tosca.relationships.nfv.ForwardsTo - diff --git a/tosca2heat/tosca-parser/toscaparser/functions.py b/tosca2heat/tosca-parser/toscaparser/functions.py index 011efde..1b64416 100644 --- a/tosca2heat/tosca-parser/toscaparser/functions.py +++ b/tosca2heat/tosca-parser/toscaparser/functions.py @@ -18,6 +18,8 @@ import six from toscaparser.common.exception import ExceptionCollector from toscaparser.common.exception import UnknownInputError from toscaparser.dataentity import DataEntity +from toscaparser.elements.constraints import Schema +from toscaparser.elements.datatype import DataType from toscaparser.elements.entity_type import EntityType from toscaparser.elements.relationshiptype import RelationshipType from toscaparser.utils.gettextutils import _ @@ -27,6 +29,7 @@ GET_PROPERTY = 'get_property' GET_ATTRIBUTE = 'get_attribute' GET_INPUT = 'get_input' CONCAT = 'concat' +TOKEN = 'token' SELF = 'SELF' HOST = 'HOST' @@ -125,30 +128,67 @@ class GetAttribute(Function): * { get_attribute: [ server, private_address ] } * { get_attribute: [ HOST, private_address ] } * { get_attribute: [ HOST, private_address, 0 ] } + * { get_attribute: [ HOST, private_address, 0, some_prop] } """ def validate(self): - if len(self.args) != 2 and len(self.args) != 3: + if len(self.args) < 2: ExceptionCollector.appendException( ValueError(_('Illegal arguments for function "{0}". Expected ' - 'arguments: "node-template-name", ' - '"attribute-name"').format(GET_ATTRIBUTE))) - node_tpl = self._find_node_template_containing_attribute() - if len(self.args) > 2: - # Currently we only check the first level - attrs_def = node_tpl.type_definition.get_attributes_def() - attr_def = attrs_def[self.attribute_name] - if attr_def.schema['type'] == "list": - if not isinstance(self.args[2], int): - ExceptionCollector.appendException( - ValueError(_('Illegal arguments for function "{0}". ' - 'Third argument must be a positive' - ' integer') .format(GET_ATTRIBUTE))) - elif attr_def.schema['type'] != "map": - ExceptionCollector.appendException( - ValueError(_('Illegal arguments for function "{0}". ' - 'Expected arguments: "node-template-name", ' - '"attribute-name"').format(GET_ATTRIBUTE))) + 'arguments: "node-template-name", "req-or-cap"' + '(optional), "property name"' + ).format(GET_ATTRIBUTE))) + return + elif len(self.args) == 2: + self._find_node_template_containing_attribute() + else: + node_tpl = self._find_node_template(self.args[0]) + index = 2 + attrs = node_tpl.type_definition.get_attributes_def() + found = [attrs[self.args[1]]] if self.args[1] in attrs else [] + if found: + attr = found[0] + else: + index = 3 + # then check the req or caps + attr = self._find_req_or_cap_attribute(self.args[1], + self.args[2]) + + value_type = attr.schema['type'] + if len(self.args) > index: + for elem in self.args[index:]: + if value_type == "list": + if not isinstance(elem, int): + ExceptionCollector.appendException( + ValueError(_('Illegal arguments for function' + ' "{0}". "{1}" Expected positive' + ' integer argument' + ).format(GET_ATTRIBUTE, elem))) + value_type = attr.schema['entry_schema']['type'] + elif value_type == "map": + value_type = attr.schema['entry_schema']['type'] + elif value_type in Schema.PROPERTY_TYPES: + ExceptionCollector.appendException( + ValueError(_('Illegal arguments for function' + ' "{0}". Unexpected attribute/' + 'index value "{1}"' + ).format(GET_ATTRIBUTE, elem))) + return + else: # It is a complex type + data_type = DataType(value_type) + props = data_type.get_all_properties() + found = [props[elem]] if elem in props else [] + if found: + prop = found[0] + value_type = prop.schema['type'] + else: + ExceptionCollector.appendException( + KeyError(_('Illegal arguments for function' + ' "{0}". Attribute name "{1}" not' + ' found in "{2}"' + ).format(GET_ATTRIBUTE, + elem, + value_type))) def result(self): return self @@ -163,25 +203,7 @@ class GetAttribute(Function): return self._find_node_template_containing_attribute() def _find_node_template_containing_attribute(self): - if self.node_template_name == HOST: - # Currently this is the only way to tell whether the function - # is used within the outputs section of the TOSCA template. - if isinstance(self.context, list): - ExceptionCollector.appendException( - ValueError(_( - '"get_attribute: [ HOST, ... ]" is not allowed in ' - '"outputs" section of the TOSCA template.'))) - return - node_tpl = self._find_host_containing_attribute() - if not node_tpl: - ExceptionCollector.appendException( - ValueError(_( - '"get_attribute: [ HOST, ... ]" was used in node ' - 'template "{0}" but "{1}" was not found in ' - 'the relationship chain.').format(self.context.name, - HOSTED_ON))) - else: - node_tpl = self._find_node_template(self.args[0]) + node_tpl = self._find_node_template(self.args[0]) if node_tpl and \ not self._attribute_exists_in_type(node_tpl.type_definition): ExceptionCollector.appendException( @@ -214,6 +236,25 @@ class GetAttribute(Function): target_name) def _find_node_template(self, node_template_name): + if node_template_name == HOST: + # Currently this is the only way to tell whether the function + # is used within the outputs section of the TOSCA template. + if isinstance(self.context, list): + ExceptionCollector.appendException( + ValueError(_( + '"get_attribute: [ HOST, ... ]" is not allowed in ' + '"outputs" section of the TOSCA template.'))) + return + node_tpl = self._find_host_containing_attribute() + if not node_tpl: + ExceptionCollector.appendException( + ValueError(_( + '"get_attribute: [ HOST, ... ]" was used in node ' + 'template "{0}" but "{1}" was not found in ' + 'the relationship chain.').format(self.context.name, + HOSTED_ON))) + return + return node_tpl if node_template_name == TARGET: if not isinstance(self.context.type_definition, RelationshipType): ExceptionCollector.appendException( @@ -240,6 +281,51 @@ class GetAttribute(Function): 'Node template "{0}" was not found.' ).format(node_template_name))) + def _find_req_or_cap_attribute(self, req_or_cap, attr_name): + node_tpl = self._find_node_template(self.args[0]) + # Find attribute in node template's requirements + for r in node_tpl.requirements: + for req, node_name in r.items(): + if req == req_or_cap: + node_template = self._find_node_template(node_name) + return self._get_capability_attribute( + node_template, + req, + attr_name) + # If requirement was not found, look in node template's capabilities + return self._get_capability_attribute(node_tpl, + req_or_cap, + attr_name) + + def _get_capability_attribute(self, + node_template, + capability_name, + attr_name): + """Gets a node template capability attribute.""" + caps = node_template.get_capabilities() + if caps and capability_name in caps.keys(): + cap = caps[capability_name] + attribute = None + attrs = cap.definition.get_attributes_def() + if attrs and attr_name in attrs.keys(): + attribute = attrs[attr_name] + if not attribute: + ExceptionCollector.appendException( + KeyError(_('Attribute "%(attr)s" was not found in ' + 'capability "%(cap)s" of node template ' + '"%(ntpl1)s" referenced from node template ' + '"%(ntpl2)s".') % {'attr': attr_name, + 'cap': capability_name, + 'ntpl1': node_template.name, + 'ntpl2': self.context.name})) + return attribute + msg = _('Requirement/Capability "{0}" referenced from node template ' + '"{1}" was not found in node template "{2}".').format( + capability_name, + self.context.name, + node_template.name) + ExceptionCollector.appendException(KeyError(msg)) + @property def node_template_name(self): return self.args[0] @@ -551,11 +637,58 @@ class Concat(Function): def result(self): return self + +class Token(Function): + """Validate the function and provide an instance of the function + + The token function is used within a TOSCA service template on a string to + parse out (tokenize) substrings separated by one or more token characters + within a larger string. + + + Arguments: + + * The composite string that contains one or more substrings separated by + token characters. + * The string that contains one or more token characters that separate + substrings within the composite string. + * The integer indicates the index of the substring to return from the + composite string. Note that the first substring is denoted by using + the '0' (zero) integer value. + + Example: + + [ get_attribute: [ my_server, data_endpoint, ip_address ], ':', 1 ] + + """ + + def validate(self): + if len(self.args) < 3: + ExceptionCollector.appendException( + ValueError(_('Invalid arguments for function "{0}". Expected ' + 'at least three arguments.').format(TOKEN))) + else: + if not isinstance(self.args[1], str) or len(self.args[1]) != 1: + ExceptionCollector.appendException( + ValueError(_('Invalid arguments for function "{0}". ' + 'Expected single char value as second ' + 'argument.').format(TOKEN))) + + if not isinstance(self.args[2], int): + ExceptionCollector.appendException( + ValueError(_('Invalid arguments for function "{0}". ' + 'Expected integer value as third ' + 'argument.').format(TOKEN))) + + def result(self): + return self + function_mappings = { GET_PROPERTY: GetProperty, GET_INPUT: GetInput, GET_ATTRIBUTE: GetAttribute, - CONCAT: Concat + CONCAT: Concat, + TOKEN: Token } diff --git a/tosca2heat/tosca-parser/toscaparser/groups.py b/tosca2heat/tosca-parser/toscaparser/groups.py index 5fd5dec..6a3e5c7 100644 --- a/tosca2heat/tosca-parser/toscaparser/groups.py +++ b/tosca2heat/tosca-parser/toscaparser/groups.py @@ -15,7 +15,7 @@ from toscaparser.common.exception import UnknownFieldError from toscaparser.entity_template import EntityTemplate from toscaparser.utils import validateutils -SECTIONS = (TYPE, METADATA, DESCRIPTION, PROPERTIES, TARGETS, INTERFACES) = \ +SECTIONS = (TYPE, METADATA, DESCRIPTION, PROPERTIES, MEMBERS, INTERFACES) = \ ('type', 'metadata', 'description', 'properties', 'members', 'interfaces') diff --git a/tosca2heat/tosca-parser/toscaparser/imports.py b/tosca2heat/tosca-parser/toscaparser/imports.py index 62748bd..58c9300 100644 --- a/tosca2heat/tosca-parser/toscaparser/imports.py +++ b/tosca2heat/tosca-parser/toscaparser/imports.py @@ -14,6 +14,7 @@ import logging import os from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import InvalidPropertyValueError from toscaparser.common.exception import MissingRequiredFieldError from toscaparser.common.exception import UnknownFieldError from toscaparser.common.exception import ValidationError @@ -161,12 +162,17 @@ class ImportsLoader(object): | URL | URL | OK | +----------+--------+------------------------------+ """ - short_import_notation = False if isinstance(import_uri_def, dict): self._validate_import_keys(import_name, import_uri_def) file_name = import_uri_def.get(self.FILE) repository = import_uri_def.get(self.REPOSITORY) + repos = self.repositories.keys() + if repository is not None: + if repository not in repos: + ExceptionCollector.appendException( + InvalidPropertyValueError( + what=_('Repository is not found in "%s"') % repos)) else: file_name = import_uri_def repository = None diff --git a/tosca2heat/tosca-parser/toscaparser/parameters.py b/tosca2heat/tosca-parser/toscaparser/parameters.py index 983aee3..1d2cb29 100644 --- a/tosca2heat/tosca-parser/toscaparser/parameters.py +++ b/tosca2heat/tosca-parser/toscaparser/parameters.py @@ -27,8 +27,9 @@ log = logging.getLogger('tosca') class Input(object): - INPUTFIELD = (TYPE, DESCRIPTION, DEFAULT, CONSTRAINTS) = \ - ('type', 'description', 'default', 'constraints') + INPUTFIELD = (TYPE, DESCRIPTION, DEFAULT, CONSTRAINTS, REQUIRED, + STATUS) = ('type', 'description', 'default', + 'constraints', 'required', 'status') def __init__(self, name, schema_dict): self.name = name @@ -53,7 +54,7 @@ class Input(object): def validate(self, value=None): self._validate_field() self.validate_type(self.type) - if value: + if value is not None: self._validate_value(value) def _validate_field(self): @@ -68,13 +69,16 @@ class Input(object): ExceptionCollector.appendException( ValueError(_('Invalid type "%s".') % type)) + # TODO(anyone) Need to test for any built-in datatype not just network + # that is, tosca.datatypes.* and not assume tosca.datatypes.network.* + # TODO(anyone) Add support for tosca.datatypes.Credential def _validate_value(self, value): tosca = EntityType.TOSCA_DEF datatype = None if self.type in tosca: datatype = tosca[self.type] - elif EntityType.DATATYPE_PREFIX + self.type in tosca: - datatype = tosca[EntityType.DATATYPE_PREFIX + self.type] + elif EntityType.DATATYPE_NETWORK_PREFIX + self.type in tosca: + datatype = tosca[EntityType.DATATYPE_NETWORK_PREFIX + self.type] DataEntity.validate_datatype(self.type, value, None, datatype) diff --git a/tosca2heat/tosca-parser/toscaparser/properties.py b/tosca2heat/tosca-parser/toscaparser/properties.py index 23c1db2..c69b151 100644 --- a/tosca2heat/tosca-parser/toscaparser/properties.py +++ b/tosca2heat/tosca-parser/toscaparser/properties.py @@ -67,7 +67,8 @@ class Property(object): self.value = str(self.value) self.value = DataEntity.validate_datatype(self.type, self.value, self.entry_schema, - self.custom_def) + self.custom_def, + self.name) self._validate_constraints() def _validate_constraints(self): diff --git a/tosca2heat/tosca-parser/toscaparser/repositories.py b/tosca2heat/tosca-parser/toscaparser/repositories.py new file mode 100644 index 0000000..184eba4 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/repositories.py @@ -0,0 +1,52 @@ +# 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 MissingRequiredFieldError +from toscaparser.common.exception import UnknownFieldError +from toscaparser.common.exception import URLException +from toscaparser.utils.gettextutils import _ +import toscaparser.utils.urlutils + +SECTIONS = (DESCRIPTION, URL, CREDENTIAL) = \ + ('description', 'url', 'credential') + + +class Repository(object): + def __init__(self, repositories, values): + self.name = repositories + self.reposit = values + if isinstance(self.reposit, dict): + if 'url' not in self.reposit.keys(): + ExceptionCollector.appendException( + MissingRequiredFieldError(what=_('Repository "%s"') + % self.name, required='url')) + self.url = self.reposit['url'] + self.load_and_validate(self.name, self.reposit) + + def load_and_validate(self, val, reposit_def): + self.keyname = val + if isinstance(reposit_def, dict): + for key in reposit_def.keys(): + if key not in SECTIONS: + ExceptionCollector.appendException( + UnknownFieldError(what=_('repositories "%s"') + % self.keyname, field=key)) + + if URL in reposit_def.keys(): + reposit_url = reposit_def.get(URL) + url_val = toscaparser.utils.urlutils.UrlUtils.\ + validate_url(reposit_url) + if url_val is not True: + ExceptionCollector.appendException( + URLException(what=_('repsositories "%s" Invalid Url') + % self.keyname)) diff --git a/tosca2heat/tosca-parser/toscaparser/shell.py b/tosca2heat/tosca-parser/toscaparser/shell.py index 848726f..b41c024 100644 --- a/tosca2heat/tosca-parser/toscaparser/shell.py +++ b/tosca2heat/tosca-parser/toscaparser/shell.py @@ -67,33 +67,33 @@ class ParserShell(object): version = tosca.version if tosca.version: - print ("\nversion: " + version) + print("\nversion: " + version) if hasattr(tosca, 'description'): description = tosca.description if description: - print ("\ndescription: " + description) + print("\ndescription: " + description) if hasattr(tosca, 'inputs'): inputs = tosca.inputs if inputs: - print ("\ninputs:") + print("\ninputs:") for input in inputs: - print ("\t" + input.name) + print("\t" + input.name) if hasattr(tosca, 'nodetemplates'): nodetemplates = tosca.nodetemplates if nodetemplates: - print ("\nnodetemplates:") + print("\nnodetemplates:") for node in nodetemplates: - print ("\t" + node.name) + print("\t" + node.name) if hasattr(tosca, 'outputs'): outputs = tosca.outputs if outputs: - print ("\noutputs:") + print("\noutputs:") for output in outputs: - print ("\t" + output.name) + print("\t" + output.name) def main(args=None): diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/compute_with_nested_atributes.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/compute_with_nested_atributes.yaml new file mode 100644 index 0000000..f23a8a1 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/compute_with_nested_atributes.yaml @@ -0,0 +1,22 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Compute node type with capability with an atribute of type list + +capability_types: + + tosca.capabilities.indigo.Endpoint: + derived_from: tosca.capabilities.Endpoint + attributes: + credential: + type: list + entry_schema: + type: tosca.datatypes.Credential + +node_types: + + tosca.nodes.ComputeWithCapWithAttr: + derived_from: tosca.nodes.Compute + capabilities: + endpoint: + type: tosca.capabilities.indigo.Endpoint + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/node_with_cap.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/node_with_cap.yaml index 11e1b51..b17513f 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/node_with_cap.yaml +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/node_with_cap.yaml @@ -3,7 +3,8 @@ tosca_definitions_version: tosca_simple_yaml_1_0 description: > Node type that has a requirement of a capability with a defined value -node_types: +capability_types: + tosca.capabilities.SomeCap: derived_from: tosca.capabilities.Root properties: @@ -14,6 +15,8 @@ node_types: constraints: - equal: someval +node_types: + tosca.nodes.SomeNode: derived_from: tosca.nodes.Root requirements: diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_datatype_portspec_add_req.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_datatype_portspec_add_req.yaml new file mode 100644 index 0000000..f944927 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_datatype_portspec_add_req.yaml @@ -0,0 +1,41 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: TOSCA test PortSpec Additional Requirement clauses + +node_types: + + MyNodeType: + derived_from: Root + properties: + test_port: + type: PortSpec + +topology_template: + + node_templates: + + # Test invalid source value below (default) specified range constraint + test_node2: + type: MyNodeType + properties: + test_port: + protocol: tcp + source: 0 + + # Test invalid source value over specified range + test_node3: + type: MyNodeType + properties: + test_port: + protocol: tcp + source: 65535 + source_range: [ 2, 65534 ] + + # Test invalid source value under specified range + test_node4: + type: MyNodeType + properties: + test_port: + protocol: tcp + source: 1 + source_range: [ 2, 65534 ] diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_with_index_error.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_with_index_error.yaml index 88a2721..7511999 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_with_index_error.yaml +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_with_index_error.yaml @@ -1,7 +1,7 @@ tosca_definitions_version: tosca_simple_yaml_1_0 description: > - TOSCA template for testing get_attribute with a list attribute and an index + TOSCA template for testing get_attribute with an incorrect index imports: - ../custom_types/compute_with_attribute_list.yaml diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_with_nested_params.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_with_nested_params.yaml new file mode 100644 index 0000000..79e632c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_with_nested_params.yaml @@ -0,0 +1,23 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA template for testing get_attribute with nested attributes + +imports: + - ../custom_types/compute_with_nested_atributes.yaml + +topology_template: + node_templates: + server: + type: tosca.nodes.ComputeWithCapWithAttr + capabilities: + endpoint: + properties: + port: 80 + interfaces: + Standard: + configure: + implementation: configure.sh + inputs: + ip_address: { get_attribute: [ SELF, endpoint, credential, 0, token ] } + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_token.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_token.yaml new file mode 100644 index 0000000..495a930 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_token.yaml @@ -0,0 +1,15 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Template for deploying a single server with token function. + +topology_template: + node_templates: + server: + type: tosca.nodes.Compute + + outputs: + url: + description: Get the first part of the ip + value: { token: [ get_attribute: [ server, public_address ], + '.' , + 0 ] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_token_invalid.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_token_invalid.yaml new file mode 100644 index 0000000..35ae2ff --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_token_invalid.yaml @@ -0,0 +1,17 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Template for deploying a single server with invalid token function. + +topology_template: + outputs: + invalid_token_syntax_1: + description: test token with only two paremeters. + value: { token: ["some_string", "_"]} + + invalid_token_syntax_2: + description: test token with invalid string as third argument. + value: { token: ["some_string", "_", "1"]} + + invalid_token_syntax_3: + description: test token with invalid string as second argument. + value: { token: ["some_string", "aa", "1"]} diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_containers.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_containers.yaml new file mode 100644 index 0000000..ba1cc16 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_containers.yaml @@ -0,0 +1,44 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with mysql docker container. + +# Repositories to retrieve code artifacts from +repositories: + docker_hub: https://registry.hub.docker.com/ + +topology_template: + + inputs: + mysql_root_pwd: + type: string + description: Root password for MySQL. + + node_templates: + # The MYSQL container based on official MySQL image in Docker hub + mysql_container: + type: tosca.nodes.Container.Application + requirements: + - host: mysql_runtime + artifacts: + my_image: + file: mysql + type: tosca.artifacts.Deployment.Image.Container.Docker + repository: docker_hub + interfaces: + Standard: + create: + implementation: my_image + inputs: + MYSQL_ROOT_PASSWORD: { get_input: mysql_root_pwd } + + # The properties of the runtime to host the container + mysql_runtime: + type: tosca.nodes.Container.Runtime + capabilities: + host: + properties: + num_cpus: 1 + disk_size: 10 GB + mem_size: 2 MB + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_credential_datatype.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_credential_datatype.yaml new file mode 100644 index 0000000..583ec82 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_credential_datatype.yaml @@ -0,0 +1,77 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with to demonstrate the usage of the + TOSCA Credential Data Type. + +imports: + - custom_types/wordpress.yaml + +relationship_types: + my.types.WordpressDbConnection: + derived_from: tosca.relationships.ConnectsTo + properties: + credential: + user: db_user + token: db_pwd + +topology_template: + node_templates: + wordpress: + type: tosca.nodes.WebApplication.WordPress + requirements: + - host: webserver + - database_endpoint: + node: mysql_database + relationship: my.types.WordpressDbConnection + + mysql_database: + type: tosca.nodes.Database + properties: + name: db_name + user: db_user + password: db_pwd + capabilities: + database_endpoint: + properties: + port: 3306 + requirements: + - host: + node: mysql_dbms + + mysql_dbms: + type: tosca.nodes.DBMS + properties: + root_password: db_root_pwd + port: 3306 + requirements: + - host: server + + webserver: + type: tosca.nodes.WebServer + properties: + admin_credential: + user: username + token: some_pass + requirements: + - host: server + + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: 1 + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Ubuntu + version: 14.04 + + outputs: + website_url: + description: URL for Wordpress wiki. + value: { get_attribute: [server, private_address] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_invalid_input_defaults.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_invalid_input_defaults.yaml new file mode 100644 index 0000000..f8f4ae7 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_invalid_input_defaults.yaml @@ -0,0 +1,12 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Test template with default not matching required type. + +topology_template: + inputs: + invalid_default: + type: integer + default: two + valid_default: + type: integer + default: 2 diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_repositories_definition.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_repositories_definition.yaml index 2145d8f..c2856c8 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/data/test_repositories_definition.yaml +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_repositories_definition.yaml @@ -4,9 +4,9 @@ repositories: some_repository: description: Some repo url: https://raw.githubusercontent.com/openstack/tosca-parser/master/toscaparser/tests/data/custom_types/ - namespace_uri: http://docs.oasis-open.org/tosca/ns/simple/yaml/1.0a - namespace_prefix: oasis_tosca - + credential: #type: Credential + token_type: basic_auth + token: myusername:mypassword imports: - some_import: file: compute_with_prop.yaml diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/subsystem.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/subsystem.yaml index 99d645b..b27e698 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/subsystem.yaml +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/subsystem.yaml @@ -12,15 +12,12 @@ topology_template: inputs: mq_server_ip: type: string - required: true description: IP address of the message queuing server to receive messages from. receiver_port: type: string - required: true description: Port to be used for receiving messages. my_cpus: type: integer - default: 2 description: Number of CPUs for the server. constraints: - valid_values: [ 1, 2, 4, 8 ] diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_repositories_test_definition.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_repositories_test_definition.yaml new file mode 100644 index 0000000..0001d06 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_repositories_test_definition.yaml @@ -0,0 +1,26 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: TOSCA simple profile with repositories validation and imports. + +repositories: + repo_code0: https://raw.githubusercontent.com/nandinivemula/intern + repo_code1: + description: My project's code Repository in github usercontent. + url: https://raw.githubusercontent.com/nandinivemula/intern/master + credential: #type: Credential + token_type: basic_auth + token: myusername:mypassword + + repo_code2: + description: My Project's code Repository in github. + url: https://github.com/nandinivemula/intern/master + credential: #type: Credential + token_type: basic_auth + token: myusername:mypassword + +imports: + - sample_import: + file: tosca_repository_import.yaml + repository: repo_code1 + namespace_uri: https://github.com/nandinivemula/intern + namespace_prefix: intern diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_datatypes.py b/tosca2heat/tosca-parser/toscaparser/tests/test_datatypes.py index 0e613b2..0a6cfe0 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/test_datatypes.py +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_datatypes.py @@ -66,16 +66,21 @@ class DataTypeTest(TestCase): tosca.my.datatypes.TestLab: properties: - temperature: + humidity: + type: range + required: false + constraints: + - in_range: [-256, INFINITY] + temperature1: type: range required: false constraints: - in_range: [-256, UNBOUNDED] - humidity: + temperature2: type: range required: false constraints: - - in_range: [-256, INFINITY] + - in_range: [UNBOUNDED, 256] ''' custom_type_def = yamlparser.simple_parse(custom_type_schema) @@ -84,15 +89,6 @@ class DataTypeTest(TestCase): value = yamlparser.simple_parse(value_snippet) self.assertEqual(value, {}) - # TODO(Matt) - opened as bug 1555300 - # Need a test for PortSpec normative data type - # that tests the spec. requirement: "A valid PortSpec - # must have at least one of the following properties: - # target, target_range, source or source_range." - # TODO(Matt) - opened as bug 1555310 - # test PortSpec value for source and target - # against the source_range and target_range - # when specified. def test_built_in_datatype(self): value_snippet = ''' private_network: @@ -140,6 +136,31 @@ class DataTypeTest(TestCase): data = DataEntity('PortInfo', value.get('ethernet_port')) self.assertIsNotNone(data.validate()) + # Test normative PortSpec datatype's additional requirements + # TODO(Matt) - opened as bug 1555300 + # Need a test for PortSpec normative data type + # that tests the spec. requirement: "A valid PortSpec + # must have at least one of the following properties: + # target, target_range, source or source_range." + # TODO(Matt) - opened as bug 1555310 + # test PortSpec value for source and target + # against the source_range and target_range + # when specified. + def test_port_spec_addl_reqs(self): + value_snippet = ''' + test_port: + protocol: tcp + target: 65535 + target_range: [ 1, 65535 ] + source: 1 + source_range: [ 1, 65535 ] + + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.datatypes.network.PortSpec', + value.get('test_port')) + self.assertIsNotNone(data.validate()) + def test_built_in_datatype_without_properties(self): value_snippet = ''' 2 @@ -365,6 +386,7 @@ class DataTypeTest(TestCase): value_snippet = ''' user_port: protocol: tcp + target: 1 target_range: [20000] ''' value = yamlparser.simple_parse(value_snippet) @@ -377,6 +399,7 @@ class DataTypeTest(TestCase): value_snippet = ''' user_port: protocol: tcp + target: 1 target_range: [20000, 3000] ''' value = yamlparser.simple_parse(value_snippet) @@ -400,9 +423,95 @@ class DataTypeTest(TestCase): def test_range_unbounded(self): value_snippet = ''' - temperature: [-100, 999999] + humidity: [-100, 100] + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.TestLab', + value, DataTypeTest.custom_type_def) + err = self.assertRaises(exception.InvalidSchemaError, + lambda: data.validate()) + self.assertEqual(_('The property "in_range" expects comparable values.' + ), + err.__str__()) + + def test_invalid_ranges_against_constraints(self): + # The TestLab range type has min=-256, max=UNBOUNDED + value_snippet = ''' + temperature1: [-257, 999999] + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.TestLab', value, + DataTypeTest.custom_type_def) + err = self.assertRaises(exception.ValidationError, data.validate) + self.assertEqual(_('The value "-257" of property "temperature1" is ' + 'out of range "(min:-256, max:UNBOUNDED)".'), + err.__str__()) + + value_snippet = ''' + temperature2: [-999999, 257] + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.TestLab', value, + DataTypeTest.custom_type_def) + err = self.assertRaises(exception.ValidationError, data.validate) + self.assertEqual(_('The value "257" of property "temperature2" is ' + 'out of range "(min:UNBOUNDED, max:256)".'), + err.__str__()) + + def test_valid_ranges_against_constraints(self): + + # The TestLab range type has max=UNBOUNDED + value_snippet = ''' + temperature1: [-255, 999999] ''' value = yamlparser.simple_parse(value_snippet) data = DataEntity('tosca.my.datatypes.TestLab', value, DataTypeTest.custom_type_def) self.assertIsNotNone(data.validate()) + + # The TestLab range type has min=UNBOUNDED + value_snippet = ''' + temperature2: [-999999, 255] + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.TestLab', value, + DataTypeTest.custom_type_def) + self.assertIsNotNone(data.validate()) + + def test_incorrect_field_in_datatype(self): + tpl_snippet = ''' + tosca_definitions_version: tosca_simple_yaml_1_0 + topology_template: + node_templates: + server: + type: tosca.nodes.Compute + + webserver: + type: tosca.nodes.WebServer + properties: + admin_credential: + user: username + token: some_pass + some_field: value + requirements: + - host: server + ''' + tpl = yamlparser.simple_parse(tpl_snippet) + err = self.assertRaises(exception.ValidationError, ToscaTemplate, + None, None, None, tpl) + self.assertIn(_('The pre-parsed input failed validation with the ' + 'following error(s): \n\n\tUnknownFieldError: Data ' + 'value of type "tosca.datatypes.Credential" contains' + ' unknown field "some_field". Refer to the definition' + ' to verify valid values'), err.__str__()) + + def test_functions_datatype(self): + value_snippet = ''' + admin_credential: + user: username + token: { get_input: password } + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.datatypes.Credential', + value.get('admin_credential')) + self.assertIsNotNone(data.validate()) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_functions.py b/tosca2heat/tosca-parser/toscaparser/tests/test_functions.py index 2a6225d..4d063e5 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/test_functions.py +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_functions.py @@ -201,8 +201,9 @@ class GetAttributeTest(TestCase): website_url_output.value.attribute_name) def test_get_attribute_invalid_args(self): - expected_msg = _('Expected arguments: "node-template-name", ' - '"attribute-name"') + expected_msg = _('Illegal arguments for function "get_attribute".' + ' Expected arguments: "node-template-name", ' + '"req-or-cap"(optional), "property name"') err = self.assertRaises(ValueError, functions.get_function, None, None, {'get_attribute': []}) @@ -211,10 +212,6 @@ class GetAttributeTest(TestCase): functions.get_function, None, None, {'get_attribute': ['x']}) self.assertIn(expected_msg, six.text_type(err)) - err = self.assertRaises(ValueError, - functions.get_function, None, None, - {'get_attribute': ['x', 'y', 'z', 'k']}) - self.assertIn(expected_msg, six.text_type(err)) def test_get_attribute_unknown_node_template_name(self): self.assertRaises( @@ -280,7 +277,7 @@ class GetAttributeTest(TestCase): exception.ExceptionCollector.assertExceptionMessage( ValueError, _('Illegal arguments for function "get_attribute". ' - 'Expected arguments: "node-template-name", "attribute-name"')) + 'Unexpected attribute/index value "0"')) def test_get_attribute_source_target_keywords(self): tosca_tpl = os.path.join( @@ -300,6 +297,10 @@ class GetAttributeTest(TestCase): source_port = operation.inputs['source_port'] self.assertTrue(isinstance(source_port, functions.GetAttribute)) + def test_get_attribute_with_nested_params(self): + self._load_template( + 'functions/test_get_attribute_with_nested_params.yaml') + class ConcatTest(TestCase): @@ -322,3 +323,34 @@ class ConcatTest(TestCase): ValueError, _('Invalid arguments for function "concat". Expected at least ' 'one arguments.')) + + +class TokenTest(TestCase): + + def _load_template(self, filename): + return ToscaTemplate(os.path.join( + os.path.dirname(os.path.abspath(__file__)), + filename)) + + def test_validate_token(self): + tosca = self._load_template("data/functions/test_token.yaml") + server_url_output = [ + output for output in tosca.outputs if output.name == 'url'][0] + func = functions.get_function(self, tosca.outputs, + server_url_output.value) + self.assertIsInstance(func, functions.Token) + + self.assertRaises(exception.ValidationError, self._load_template, + 'data/functions/test_token_invalid.yaml') + exception.ExceptionCollector.assertExceptionMessage( + ValueError, + _('Invalid arguments for function "token". Expected at least ' + 'three arguments.')) + exception.ExceptionCollector.assertExceptionMessage( + ValueError, + _('Invalid arguments for function "token". Expected ' + 'integer value as third argument.')) + exception.ExceptionCollector.assertExceptionMessage( + ValueError, + _('Invalid arguments for function "token". Expected ' + 'single char value as second argument.')) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_toscatpl.py b/tosca2heat/tosca-parser/toscaparser/tests/test_toscatpl.py index ac55059..2488b65 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/test_toscatpl.py +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_toscatpl.py @@ -12,10 +12,10 @@ import os import six - from toscaparser.common import exception import toscaparser.elements.interfaces as ifaces from toscaparser.elements.nodetype import NodeType +from toscaparser.elements.portspectype import PortSpec from toscaparser.functions import GetInput from toscaparser.functions import GetProperty from toscaparser.nodetemplate import NodeTemplate @@ -26,16 +26,17 @@ import toscaparser.utils.yamlparser class ToscaTemplateTest(TestCase): - '''TOSCA template.''' tosca_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/tosca_single_instance_wordpress.yaml") tosca = ToscaTemplate(tosca_tpl) - tosca_elk_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/tosca_elk.yaml") + tosca_repo_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_repositories_test_definition.yaml") def test_version(self): self.assertEqual(self.tosca.version, "tosca_simple_yaml_1_0") @@ -251,6 +252,18 @@ class ToscaTemplateTest(TestCase): expected_hosts, sorted([v.type for v in node_tpl.relationships.values()])) + def test_repositories(self): + template = ToscaTemplate(self.tosca_repo_tpl) + self.assertEqual( + ['repo_code0', 'repo_code1', 'repo_code2'], + sorted([input.name for input in template.repositories])) + + input_name = "repo_code2" + expected_url = "https://github.com/nandinivemula/intern/master" + for input in template.repositories: + if input.name == input_name: + self.assertEqual(input.url, expected_url) + def test_template_macro(self): template = ToscaTemplate(self.tosca_elk_tpl) for node_tpl in template.nodetemplates: @@ -747,3 +760,46 @@ class ToscaTemplateTest(TestCase): self.assertTrue(rel.is_derived_from("tosca.relationships.Root")) self.assertEqual(len(rel.interfaces), 1) self.assertEqual(rel.interfaces[0].type, "Configure") + + def test_various_portspec_errors(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/datatypes/test_datatype_portspec_add_req.yaml") + self.assertRaises(exception.ValidationError, ToscaTemplate, tosca_tpl, + None) + + # TODO(TBD) find way to reuse error messages from constraints.py + msg = (_('The value "%(pvalue)s" of property "%(pname)s" is out of ' + 'range "(min:%(vmin)s, max:%(vmax)s)".') % + dict(pname=PortSpec.SOURCE, + pvalue='0', + vmin='1', + vmax='65535')) + exception.ExceptionCollector.assertExceptionMessage( + exception.ValidationError, msg) + + # Test value below range min. + msg = (_('The value "%(pvalue)s" of property "%(pname)s" is out of ' + 'range "(min:%(vmin)s, max:%(vmax)s)".') % + dict(pname=PortSpec.SOURCE, + pvalue='1', + vmin='2', + vmax='65534')) + exception.ExceptionCollector.assertExceptionMessage( + exception.RangeValueError, msg) + + # Test value above range max. + msg = (_('The value "%(pvalue)s" of property "%(pname)s" is out of ' + 'range "(min:%(vmin)s, max:%(vmax)s)".') % + dict(pname=PortSpec.SOURCE, + pvalue='65535', + vmin='2', + vmax='65534')) + exception.ExceptionCollector.assertExceptionMessage( + exception.RangeValueError, msg) + + def test_containers(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_containers.yaml") + ToscaTemplate(tosca_tpl) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py b/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py index 81a1a6c..986d9e4 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py @@ -20,12 +20,12 @@ from toscaparser.parameters import Input from toscaparser.parameters import Output from toscaparser.policy import Policy from toscaparser.relationship_template import RelationshipTemplate +from toscaparser.repositories import Repository from toscaparser.tests.base import TestCase from toscaparser.topology_template import TopologyTemplate from toscaparser.tosca_template import ToscaTemplate from toscaparser.triggers import Triggers from toscaparser.utils.gettextutils import _ - import toscaparser.utils.yamlparser @@ -105,7 +105,9 @@ class ToscaTemplateValidationTest(TestCase): type: integer description: Number of CPUs for the server. constraint: - - valid_values: [ 1, 2, 4, 8 ] + - valid_values: [ 1, 2, 4 ] + required: yes + status: supported ''' inputs = (toscaparser.utils.yamlparser. simple_parse(tpl_snippet)['inputs']) @@ -335,6 +337,113 @@ heat-translator/master/translator/tests/data/custom_types/wordpress.yaml 'to verify valid values.'), err.__str__()) + def _repo_content(self, path): + repositories = path['repositories'] + reposit = [] + for name, val in repositories.items(): + reposits = Repository(name, val) + reposit.append(reposits) + return reposit + + def test_repositories(self): + tpl_snippet = ''' + repositories: + repo_code0: https://raw.githubusercontent.com/nandinivemula/intern + repo_code1: + description: My project's code Repository in github usercontent. + url: https://github.com/nandinivemula/intern + credential: + user: nandini + password: tcs@12345 + repo_code2: + description: My Project's code Repository in github. + url: https://github.com/nandinivemula/intern + credential: + user: xyzw + password: xyz@123 + ''' + tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet)) + repoobject = self._repo_content(tpl) + actualrepo_names = [] + for repo in repoobject: + repos = repo.name + actualrepo_names.append(repos) + reposname = list(tpl.values()) + reposnames = reposname[0] + expected_reponames = list(reposnames.keys()) + self.assertEqual(expected_reponames, actualrepo_names) + + def test_repositories_with_missing_required_field(self): + tpl_snippet = ''' + repositories: + repo_code0: https://raw.githubusercontent.com/nandinivemula/intern + repo_code1: + description: My project's code Repository in github usercontent. + credential: + user: nandini + password: tcs@12345 + repo_code2: + description: My Project's code Repository in github. + url: https://github.com/nandinivemula/intern + credential: + user: xyzw + password: xyz@123 + ''' + tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet)) + err = self.assertRaises(exception.MissingRequiredFieldError, + self._repo_content, tpl) + expectedmessage = _('Repository "repo_code1" is missing ' + 'required field "url".') + self.assertEqual(expectedmessage, err.__str__()) + + def test_repositories_with_unknown_field(self): + tpl_snippet = ''' + repositories: + repo_code0: https://raw.githubusercontent.com/nandinivemula/intern + repo_code1: + description: My project's code Repository in github usercontent. + url: https://github.com/nandinivemula/intern + credential: + user: nandini + password: tcs@12345 + repo_code2: + descripton: My Project's code Repository in github. + url: https://github.com/nandinivemula/intern + credential: + user: xyzw + password: xyz@123 + ''' + tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet)) + err = self.assertRaises(exception.UnknownFieldError, + self._repo_content, tpl) + expectedmessage = _('repositories "repo_code2" contains unknown field' + ' "descripton". Refer to the definition to verify' + ' valid values.') + self.assertEqual(expectedmessage, err.__str__()) + + def test_repositories_with_invalid_url(self): + tpl_snippet = ''' + repositories: + repo_code0: https://raw.githubusercontent.com/nandinivemula/intern + repo_code1: + description: My project's code Repository in github usercontent. + url: h + credential: + user: nandini + password: tcs@12345 + repo_code2: + description: My Project's code Repository in github. + url: https://github.com/nandinivemula/intern + credential: + user: xyzw + password: xyz@123 + ''' + tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet)) + err = self.assertRaises(exception.URLException, + self._repo_content, tpl) + expectedmessage = _('repsositories "repo_code1" Invalid Url') + self.assertEqual(expectedmessage, err.__str__()) + def test_groups(self): tpl_snippet = ''' node_templates: @@ -1379,3 +1488,35 @@ heat-translator/master/translator/tests/data/custom_types/wordpress.yaml exception.MissingRequiredFieldError, lambda: Policy(name, policies[name], None, None)) self.assertEqual(expectedmessage, err.__str__()) + + def test_credential_datatype(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_credential_datatype.yaml") + self.assertIsNotNone(ToscaTemplate(tosca_tpl)) + + def test_invalid_default_value(self): + tpl_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_invalid_input_defaults.yaml") + self.assertRaises(exception.ValidationError, ToscaTemplate, tpl_path) + exception.ExceptionCollector.assertExceptionMessage( + ValueError, _('"two" is not an integer.')) + + def test_invalid_capability(self): + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + oss: + properties: + architecture: x86_64 + ''' + tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet)) + err = self.assertRaises(exception.UnknownFieldError, + TopologyTemplate, tpl, None) + expectedmessage = _('"capabilities" of template "server" contains ' + 'unknown field "oss". Refer to the definition ' + 'to verify valid values.') + self.assertEqual(expectedmessage, err.__str__()) diff --git a/tosca2heat/tosca-parser/toscaparser/topology_template.py b/tosca2heat/tosca-parser/toscaparser/topology_template.py index d51512a..6cf4f31 100644 --- a/tosca2heat/tosca-parser/toscaparser/topology_template.py +++ b/tosca2heat/tosca-parser/toscaparser/topology_template.py @@ -66,6 +66,10 @@ class TopologyTemplate(object): input = Input(name, attrs) if self.parsed_params and name in self.parsed_params: input.validate(self.parsed_params[name]) + else: + default = input.default + if default: + input.validate(default) inputs.append(input) return inputs diff --git a/tosca2heat/tosca-parser/toscaparser/tosca_template.py b/tosca2heat/tosca-parser/toscaparser/tosca_template.py index 2ab2581..01e6c73 100644 --- a/tosca2heat/tosca-parser/toscaparser/tosca_template.py +++ b/tosca2heat/tosca-parser/toscaparser/tosca_template.py @@ -23,6 +23,7 @@ from toscaparser.elements.entity_type import update_definitions from toscaparser.extensions.exttools import ExtTools import toscaparser.imports from toscaparser.prereq.csar import CSAR +from toscaparser.repositories import Repository from toscaparser.topology_template import TopologyTemplate from toscaparser.tpl_relationship_graph import ToscaGraph from toscaparser.utils.gettextutils import _ @@ -95,6 +96,7 @@ class ToscaTemplate(object): self.relationship_types = self._tpl_relationship_types() self.description = self._tpl_description() self.topology_template = self._topology_template() + self.repositories = self._tpl_repositories() if self.topology_template.tpl: self.inputs = self._inputs() self.relationship_templates = self._relationship_templates() @@ -134,6 +136,15 @@ class ToscaTemplate(object): def _tpl_imports(self): return self.tpl.get(IMPORTS) + def _tpl_repositories(self): + repositories = self.tpl.get(REPOSITORIES) + reposit = [] + if repositories: + for name, val in repositories.items(): + reposits = Repository(name, val) + reposit.append(reposits) + return reposit + def _tpl_relationship_types(self): return self._get_custom_types(RELATIONSHIP_TYPES) diff --git a/tosca2heat/tosca-parser/toscaparser/utils/validateutils.py b/tosca2heat/tosca-parser/toscaparser/utils/validateutils.py index f9b9fc5..43e14d6 100644 --- a/tosca2heat/tosca-parser/toscaparser/utils/validateutils.py +++ b/tosca2heat/tosca-parser/toscaparser/utils/validateutils.py @@ -17,14 +17,20 @@ import numbers import re import six +# from toscaparser.elements import constraints from toscaparser.common.exception import ExceptionCollector from toscaparser.common.exception import InvalidTOSCAVersionPropertyException +from toscaparser.common.exception import RangeValueError from toscaparser.utils.gettextutils import _ + log = logging.getLogger('tosca') +RANGE_UNBOUNDED = 'UNBOUNDED' + def str_to_num(value): '''Convert a string representation of a number into a numeric type.''' + # TODO(TBD) we should not allow numeric values in, input should be str if isinstance(value, numbers.Number): return value try: @@ -33,8 +39,11 @@ def str_to_num(value): return float(value) -def validate_number(value): - return str_to_num(value) +def validate_numeric(value): + if not isinstance(value, numbers.Number): + ExceptionCollector.appendException( + ValueError(_('"%s" is not a numeric.') % value)) + return value def validate_integer(value): @@ -51,7 +60,7 @@ def validate_float(value): if not isinstance(value, float): ExceptionCollector.appendException( ValueError(_('"%s" is not a float.') % value)) - return validate_number(value) + return value def validate_string(value): @@ -68,15 +77,53 @@ def validate_list(value): return value -def validate_range(value): - validate_list(value) - if isinstance(value, list): - if len(value) != 2 or not (value[0] <= value[1]): +def validate_range(range): + # list class check + validate_list(range) + # validate range list has a min and max + if len(range) != 2: + ExceptionCollector.appendException( + ValueError(_('"%s" is not a valid range.') % range)) + # validate min and max are numerics or the keyword UNBOUNDED + min_test = max_test = False + if not range[0] == RANGE_UNBOUNDED: + min = validate_numeric(range[0]) + else: + min_test = True + if not range[1] == RANGE_UNBOUNDED: + max = validate_numeric(range[1]) + else: + max_test = True + # validate the max > min (account for UNBOUNDED) + if not min_test and not max_test: + # Note: min == max is allowed + if min > max: + ExceptionCollector.appendException( + ValueError(_('"%s" is not a valid range.') % range)) + + return range + + +def validate_value_in_range(value, range, prop_name): + validate_numeric(value) + validate_range(range) + + # Note: value is valid if equal to min + if range[0] != RANGE_UNBOUNDED: + if value < range[0]: + ExceptionCollector.appendException( + RangeValueError(pname=prop_name, + pvalue=value, + vmin=range[0], + vmax=range[1])) + # Note: value is valid if equal to max + if range[1] != RANGE_UNBOUNDED: + if value > range[1]: ExceptionCollector.appendException( - ValueError(_('"%s" is not a valid range.') % value)) - validate_integer(value[0]) - if not value[1] == "UNBOUNDED": - validate_integer(value[1]) + RangeValueError(pname=prop_name, + pvalue=value, + vmin=range[0], + vmax=range[1])) return value |