Implement PortSpec datatype and all spec. requirements
Support the built-in PortSpec datatype beyond basic schema by checking for all sub properties and additional requirements during validation. Change-Id: Idb7ecbe7a8587badee59eae96c8f2b487e9dc0e6
This commit is contained in:
parent
ceba80e85e
commit
0a91862a05
@ -88,6 +88,15 @@ class InvalidTypeError(TOSCAException):
|
|||||||
msg_fmt = _('Type "%(what)s" is not a valid type.')
|
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):
|
class InvalidSchemaError(TOSCAException):
|
||||||
msg_fmt = _('%(message)s')
|
msg_fmt = _('%(message)s')
|
||||||
|
|
||||||
|
@ -16,10 +16,10 @@ from toscaparser.common.exception import TypeMismatchError
|
|||||||
from toscaparser.common.exception import UnknownFieldError
|
from toscaparser.common.exception import UnknownFieldError
|
||||||
from toscaparser.elements.constraints import Schema
|
from toscaparser.elements.constraints import Schema
|
||||||
from toscaparser.elements.datatype import DataType
|
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_Frequency
|
||||||
from toscaparser.elements.scalarunit import ScalarUnit_Size
|
from toscaparser.elements.scalarunit import ScalarUnit_Size
|
||||||
from toscaparser.elements.scalarunit import ScalarUnit_Time
|
from toscaparser.elements.scalarunit import ScalarUnit_Time
|
||||||
|
|
||||||
from toscaparser.utils.gettextutils import _
|
from toscaparser.utils.gettextutils import _
|
||||||
from toscaparser.utils import validateutils
|
from toscaparser.utils import validateutils
|
||||||
|
|
||||||
@ -27,11 +27,13 @@ from toscaparser.utils import validateutils
|
|||||||
class DataEntity(object):
|
class DataEntity(object):
|
||||||
'''A complex data value entity.'''
|
'''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.custom_def = custom_def
|
||||||
self.datatype = DataType(datatypename, custom_def)
|
self.datatype = DataType(datatypename, custom_def)
|
||||||
self.schema = self.datatype.get_all_properties()
|
self.schema = self.datatype.get_all_properties()
|
||||||
self.value = value_dict
|
self.value = value_dict
|
||||||
|
self.property_name = prop_name
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
'''Validate the value by the definition of the datatype.'''
|
'''Validate the value by the definition of the datatype.'''
|
||||||
@ -43,7 +45,7 @@ class DataEntity(object):
|
|||||||
self.value,
|
self.value,
|
||||||
None,
|
None,
|
||||||
self.custom_def)
|
self.custom_def)
|
||||||
schema = Schema(None, self.datatype.defs)
|
schema = Schema(self.property_name, self.datatype.defs)
|
||||||
for constraint in schema.constraints:
|
for constraint in schema.constraints:
|
||||||
constraint.validate(self.value)
|
constraint.validate(self.value)
|
||||||
# If the datatype has 'properties' definition
|
# If the datatype has 'properties' definition
|
||||||
@ -110,7 +112,8 @@ class DataEntity(object):
|
|||||||
return self.schema[name].schema
|
return self.schema[name].schema
|
||||||
|
|
||||||
@staticmethod
|
@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.
|
'''Validate value with given type.
|
||||||
|
|
||||||
If type is list or map, validate its entry by entry_schema(if defined)
|
If type is list or map, validate its entry by entry_schema(if defined)
|
||||||
@ -123,7 +126,7 @@ class DataEntity(object):
|
|||||||
elif type == Schema.FLOAT:
|
elif type == Schema.FLOAT:
|
||||||
return validateutils.validate_float(value)
|
return validateutils.validate_float(value)
|
||||||
elif type == Schema.NUMBER:
|
elif type == Schema.NUMBER:
|
||||||
return validateutils.validate_number(value)
|
return validateutils.validate_numeric(value)
|
||||||
elif type == Schema.BOOLEAN:
|
elif type == Schema.BOOLEAN:
|
||||||
return validateutils.validate_boolean(value)
|
return validateutils.validate_boolean(value)
|
||||||
elif type == Schema.RANGE:
|
elif type == Schema.RANGE:
|
||||||
@ -149,6 +152,10 @@ class DataEntity(object):
|
|||||||
if entry_schema:
|
if entry_schema:
|
||||||
DataEntity.validate_entry(value, entry_schema, custom_def)
|
DataEntity.validate_entry(value, entry_schema, custom_def)
|
||||||
return value
|
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:
|
else:
|
||||||
data = DataEntity(type, value, custom_def)
|
data = DataEntity(type, value, custom_def)
|
||||||
return data.validate()
|
return data.validate()
|
||||||
|
@ -16,6 +16,7 @@ from toscaparser.elements.statefulentitytype import StatefulEntityType
|
|||||||
|
|
||||||
class CapabilityTypeDef(StatefulEntityType):
|
class CapabilityTypeDef(StatefulEntityType):
|
||||||
'''TOSCA built-in capabilities type.'''
|
'''TOSCA built-in capabilities type.'''
|
||||||
|
TOSCA_TYPEURI_CAPABILITY_ROOT = 'tosca.capabilities.Root'
|
||||||
|
|
||||||
def __init__(self, name, ctype, ntype, custom_def=None):
|
def __init__(self, name, ctype, ntype, custom_def=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -61,7 +62,7 @@ class CapabilityTypeDef(StatefulEntityType):
|
|||||||
capabilities = {}
|
capabilities = {}
|
||||||
parent_cap = self.parent_type
|
parent_cap = self.parent_type
|
||||||
if parent_cap:
|
if parent_cap:
|
||||||
while parent_cap != 'tosca.capabilities.Root':
|
while parent_cap != self.TOSCA_TYPEURI_CAPABILITY_ROOT:
|
||||||
if parent_cap in self.TOSCA_DEF.keys():
|
if parent_cap in self.TOSCA_DEF.keys():
|
||||||
capabilities[parent_cap] = self.TOSCA_DEF[parent_cap]
|
capabilities[parent_cap] = self.TOSCA_DEF[parent_cap]
|
||||||
elif custom_def and parent_cap in custom_def.keys():
|
elif custom_def and parent_cap in custom_def.keys():
|
||||||
|
@ -18,6 +18,7 @@ import toscaparser
|
|||||||
from toscaparser.common.exception import ExceptionCollector
|
from toscaparser.common.exception import ExceptionCollector
|
||||||
from toscaparser.common.exception import InvalidSchemaError
|
from toscaparser.common.exception import InvalidSchemaError
|
||||||
from toscaparser.common.exception import ValidationError
|
from toscaparser.common.exception import ValidationError
|
||||||
|
from toscaparser.elements.portspectype import PortSpec
|
||||||
from toscaparser.elements import scalarunit
|
from toscaparser.elements import scalarunit
|
||||||
from toscaparser.utils.gettextutils import _
|
from toscaparser.utils.gettextutils import _
|
||||||
|
|
||||||
@ -36,12 +37,12 @@ class Schema(collections.Mapping):
|
|||||||
INTEGER, STRING, BOOLEAN, FLOAT, RANGE,
|
INTEGER, STRING, BOOLEAN, FLOAT, RANGE,
|
||||||
NUMBER, TIMESTAMP, LIST, MAP,
|
NUMBER, TIMESTAMP, LIST, MAP,
|
||||||
SCALAR_UNIT_SIZE, SCALAR_UNIT_FREQUENCY, SCALAR_UNIT_TIME,
|
SCALAR_UNIT_SIZE, SCALAR_UNIT_FREQUENCY, SCALAR_UNIT_TIME,
|
||||||
PORTDEF, VERSION
|
VERSION, PORTDEF, PORTSPEC
|
||||||
) = (
|
) = (
|
||||||
'integer', 'string', 'boolean', 'float', 'range',
|
'integer', 'string', 'boolean', 'float', 'range',
|
||||||
'number', 'timestamp', 'list', 'map',
|
'number', 'timestamp', 'list', 'map',
|
||||||
'scalar-unit.size', 'scalar-unit.frequency', 'scalar-unit.time',
|
'scalar-unit.size', 'scalar-unit.frequency', 'scalar-unit.time',
|
||||||
'PortDef', 'version'
|
'version', 'PortDef', PortSpec.SHORTNAME
|
||||||
)
|
)
|
||||||
|
|
||||||
SCALAR_UNIT_SIZE_DEFAULT = 'B'
|
SCALAR_UNIT_SIZE_DEFAULT = 'B'
|
||||||
@ -127,8 +128,6 @@ class Constraint(object):
|
|||||||
'less_or_equal', 'in_range', 'valid_values', 'length',
|
'less_or_equal', 'in_range', 'valid_values', 'length',
|
||||||
'min_length', 'max_length', 'pattern')
|
'min_length', 'max_length', 'pattern')
|
||||||
|
|
||||||
UNBOUNDED = 'UNBOUNDED'
|
|
||||||
|
|
||||||
def __new__(cls, property_name, property_type, constraint):
|
def __new__(cls, property_name, property_type, constraint):
|
||||||
if cls is not Constraint:
|
if cls is not Constraint:
|
||||||
return super(Constraint, cls).__new__(cls)
|
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)
|
Constrains a property or parameter to a value in range of (inclusive)
|
||||||
the two values declared.
|
the two values declared.
|
||||||
"""
|
"""
|
||||||
|
UNBOUNDED = 'UNBOUNDED'
|
||||||
|
|
||||||
constraint_key = Constraint.IN_RANGE
|
constraint_key = Constraint.IN_RANGE
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@ class DataType(StatefulEntityType):
|
|||||||
'''TOSCA built-in and user defined complex data type.'''
|
'''TOSCA built-in and user defined complex data type.'''
|
||||||
|
|
||||||
def __init__(self, datatypename, custom_def=None):
|
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)
|
custom_def)
|
||||||
self.custom_def = custom_def
|
self.custom_def = custom_def
|
||||||
|
|
||||||
|
@ -56,7 +56,8 @@ class EntityType(object):
|
|||||||
GROUP_PREFIX = 'tosca.groups.'
|
GROUP_PREFIX = 'tosca.groups.'
|
||||||
# currently the data types are defined only for network
|
# currently the data types are defined only for network
|
||||||
# but may have changes in the future.
|
# but may have changes in the future.
|
||||||
DATATYPE_PREFIX = 'tosca.datatypes.network.'
|
DATATYPE_PREFIX = 'tosca.datatypes.'
|
||||||
|
DATATYPE_NETWORK_PREFIX = DATATYPE_PREFIX + 'network.'
|
||||||
TOSCA = 'tosca'
|
TOSCA = 'tosca'
|
||||||
|
|
||||||
def derived_from(self, defs):
|
def derived_from(self, defs):
|
||||||
|
86
toscaparser/elements/portspectype.py
Normal file
86
toscaparser/elements/portspectype.py
Normal file
@ -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))
|
@ -68,13 +68,16 @@ class Input(object):
|
|||||||
ExceptionCollector.appendException(
|
ExceptionCollector.appendException(
|
||||||
ValueError(_('Invalid type "%s".') % type))
|
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):
|
def _validate_value(self, value):
|
||||||
tosca = EntityType.TOSCA_DEF
|
tosca = EntityType.TOSCA_DEF
|
||||||
datatype = None
|
datatype = None
|
||||||
if self.type in tosca:
|
if self.type in tosca:
|
||||||
datatype = tosca[self.type]
|
datatype = tosca[self.type]
|
||||||
elif EntityType.DATATYPE_PREFIX + self.type in tosca:
|
elif EntityType.DATATYPE_NETWORK_PREFIX + self.type in tosca:
|
||||||
datatype = tosca[EntityType.DATATYPE_PREFIX + self.type]
|
datatype = tosca[EntityType.DATATYPE_NETWORK_PREFIX + self.type]
|
||||||
|
|
||||||
DataEntity.validate_datatype(self.type, value, None, datatype)
|
DataEntity.validate_datatype(self.type, value, None, datatype)
|
||||||
|
|
||||||
|
@ -67,7 +67,8 @@ class Property(object):
|
|||||||
self.value = str(self.value)
|
self.value = str(self.value)
|
||||||
self.value = DataEntity.validate_datatype(self.type, self.value,
|
self.value = DataEntity.validate_datatype(self.type, self.value,
|
||||||
self.entry_schema,
|
self.entry_schema,
|
||||||
self.custom_def)
|
self.custom_def,
|
||||||
|
self.name)
|
||||||
self._validate_constraints()
|
self._validate_constraints()
|
||||||
|
|
||||||
def _validate_constraints(self):
|
def _validate_constraints(self):
|
||||||
|
@ -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 ]
|
@ -66,16 +66,21 @@ class DataTypeTest(TestCase):
|
|||||||
|
|
||||||
tosca.my.datatypes.TestLab:
|
tosca.my.datatypes.TestLab:
|
||||||
properties:
|
properties:
|
||||||
temperature:
|
|
||||||
type: range
|
|
||||||
required: false
|
|
||||||
constraints:
|
|
||||||
- in_range: [-256, UNBOUNDED]
|
|
||||||
humidity:
|
humidity:
|
||||||
type: range
|
type: range
|
||||||
required: false
|
required: false
|
||||||
constraints:
|
constraints:
|
||||||
- in_range: [-256, INFINITY]
|
- in_range: [-256, INFINITY]
|
||||||
|
temperature1:
|
||||||
|
type: range
|
||||||
|
required: false
|
||||||
|
constraints:
|
||||||
|
- in_range: [-256, UNBOUNDED]
|
||||||
|
temperature2:
|
||||||
|
type: range
|
||||||
|
required: false
|
||||||
|
constraints:
|
||||||
|
- in_range: [UNBOUNDED, 256]
|
||||||
'''
|
'''
|
||||||
custom_type_def = yamlparser.simple_parse(custom_type_schema)
|
custom_type_def = yamlparser.simple_parse(custom_type_schema)
|
||||||
|
|
||||||
@ -84,15 +89,6 @@ class DataTypeTest(TestCase):
|
|||||||
value = yamlparser.simple_parse(value_snippet)
|
value = yamlparser.simple_parse(value_snippet)
|
||||||
self.assertEqual(value, {})
|
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):
|
def test_built_in_datatype(self):
|
||||||
value_snippet = '''
|
value_snippet = '''
|
||||||
private_network:
|
private_network:
|
||||||
@ -140,6 +136,31 @@ class DataTypeTest(TestCase):
|
|||||||
data = DataEntity('PortInfo', value.get('ethernet_port'))
|
data = DataEntity('PortInfo', value.get('ethernet_port'))
|
||||||
self.assertIsNotNone(data.validate())
|
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):
|
def test_built_in_datatype_without_properties(self):
|
||||||
value_snippet = '''
|
value_snippet = '''
|
||||||
2
|
2
|
||||||
@ -365,6 +386,7 @@ class DataTypeTest(TestCase):
|
|||||||
value_snippet = '''
|
value_snippet = '''
|
||||||
user_port:
|
user_port:
|
||||||
protocol: tcp
|
protocol: tcp
|
||||||
|
target: 1
|
||||||
target_range: [20000]
|
target_range: [20000]
|
||||||
'''
|
'''
|
||||||
value = yamlparser.simple_parse(value_snippet)
|
value = yamlparser.simple_parse(value_snippet)
|
||||||
@ -377,6 +399,7 @@ class DataTypeTest(TestCase):
|
|||||||
value_snippet = '''
|
value_snippet = '''
|
||||||
user_port:
|
user_port:
|
||||||
protocol: tcp
|
protocol: tcp
|
||||||
|
target: 1
|
||||||
target_range: [20000, 3000]
|
target_range: [20000, 3000]
|
||||||
'''
|
'''
|
||||||
value = yamlparser.simple_parse(value_snippet)
|
value = yamlparser.simple_parse(value_snippet)
|
||||||
@ -400,7 +423,55 @@ class DataTypeTest(TestCase):
|
|||||||
|
|
||||||
def test_range_unbounded(self):
|
def test_range_unbounded(self):
|
||||||
value_snippet = '''
|
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)
|
value = yamlparser.simple_parse(value_snippet)
|
||||||
data = DataEntity('tosca.my.datatypes.TestLab', value,
|
data = DataEntity('tosca.my.datatypes.TestLab', value,
|
||||||
|
@ -16,6 +16,7 @@ import six
|
|||||||
from toscaparser.common import exception
|
from toscaparser.common import exception
|
||||||
import toscaparser.elements.interfaces as ifaces
|
import toscaparser.elements.interfaces as ifaces
|
||||||
from toscaparser.elements.nodetype import NodeType
|
from toscaparser.elements.nodetype import NodeType
|
||||||
|
from toscaparser.elements.portspectype import PortSpec
|
||||||
from toscaparser.functions import GetInput
|
from toscaparser.functions import GetInput
|
||||||
from toscaparser.functions import GetProperty
|
from toscaparser.functions import GetProperty
|
||||||
from toscaparser.nodetemplate import NodeTemplate
|
from toscaparser.nodetemplate import NodeTemplate
|
||||||
@ -730,3 +731,40 @@ class ToscaTemplateTest(TestCase):
|
|||||||
rel = tosca.relationship_templates[0]
|
rel = tosca.relationship_templates[0]
|
||||||
self.assertEqual(len(rel.interfaces), 1)
|
self.assertEqual(len(rel.interfaces), 1)
|
||||||
self.assertEqual(rel.interfaces[0].type, "Configure")
|
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)
|
||||||
|
@ -17,14 +17,20 @@ import numbers
|
|||||||
import re
|
import re
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
# from toscaparser.elements import constraints
|
||||||
from toscaparser.common.exception import ExceptionCollector
|
from toscaparser.common.exception import ExceptionCollector
|
||||||
from toscaparser.common.exception import InvalidTOSCAVersionPropertyException
|
from toscaparser.common.exception import InvalidTOSCAVersionPropertyException
|
||||||
|
from toscaparser.common.exception import RangeValueError
|
||||||
from toscaparser.utils.gettextutils import _
|
from toscaparser.utils.gettextutils import _
|
||||||
|
|
||||||
log = logging.getLogger('tosca')
|
log = logging.getLogger('tosca')
|
||||||
|
|
||||||
|
RANGE_UNBOUNDED = 'UNBOUNDED'
|
||||||
|
|
||||||
|
|
||||||
def str_to_num(value):
|
def str_to_num(value):
|
||||||
'''Convert a string representation of a number into a numeric type.'''
|
'''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):
|
if isinstance(value, numbers.Number):
|
||||||
return value
|
return value
|
||||||
try:
|
try:
|
||||||
@ -33,8 +39,11 @@ def str_to_num(value):
|
|||||||
return float(value)
|
return float(value)
|
||||||
|
|
||||||
|
|
||||||
def validate_number(value):
|
def validate_numeric(value):
|
||||||
return str_to_num(value)
|
if not isinstance(value, numbers.Number):
|
||||||
|
ExceptionCollector.appendException(
|
||||||
|
ValueError(_('"%s" is not a numeric.') % value))
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def validate_integer(value):
|
def validate_integer(value):
|
||||||
@ -51,7 +60,7 @@ def validate_float(value):
|
|||||||
if not isinstance(value, float):
|
if not isinstance(value, float):
|
||||||
ExceptionCollector.appendException(
|
ExceptionCollector.appendException(
|
||||||
ValueError(_('"%s" is not a float.') % value))
|
ValueError(_('"%s" is not a float.') % value))
|
||||||
return validate_number(value)
|
return value
|
||||||
|
|
||||||
|
|
||||||
def validate_string(value):
|
def validate_string(value):
|
||||||
@ -68,15 +77,53 @@ def validate_list(value):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def validate_range(value):
|
def validate_range(range):
|
||||||
validate_list(value)
|
# list class check
|
||||||
if isinstance(value, list):
|
validate_list(range)
|
||||||
if len(value) != 2 or not (value[0] <= value[1]):
|
# 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(
|
ExceptionCollector.appendException(
|
||||||
ValueError(_('"%s" is not a valid range.') % value))
|
ValueError(_('"%s" is not a valid range.') % range))
|
||||||
validate_integer(value[0])
|
|
||||||
if not value[1] == "UNBOUNDED":
|
return range
|
||||||
validate_integer(value[1])
|
|
||||||
|
|
||||||
|
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(
|
||||||
|
RangeValueError(pname=prop_name,
|
||||||
|
pvalue=value,
|
||||||
|
vmin=range[0],
|
||||||
|
vmax=range[1]))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user