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.')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
@ -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
|
||||
@ -110,7 +112,8 @@ 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)
|
||||
@ -123,7 +126,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 +152,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()
|
||||
|
@ -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
|
||||
@ -61,7 +62,7 @@ class CapabilityTypeDef(StatefulEntityType):
|
||||
capabilities = {}
|
||||
parent_cap = self.parent_type
|
||||
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():
|
||||
capabilities[parent_cap] = self.TOSCA_DEF[parent_cap]
|
||||
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 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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
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(
|
||||
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)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
properties:
|
||||
temperature:
|
||||
type: range
|
||||
required: false
|
||||
constraints:
|
||||
- in_range: [-256, UNBOUNDED]
|
||||
humidity:
|
||||
type: range
|
||||
required: false
|
||||
constraints:
|
||||
- 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)
|
||||
|
||||
@ -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,7 +423,55 @@ 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,
|
||||
|
@ -16,6 +16,7 @@ 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
|
||||
@ -730,3 +731,40 @@ class ToscaTemplateTest(TestCase):
|
||||
rel = tosca.relationship_templates[0]
|
||||
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)
|
||||
|
@ -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.') % value))
|
||||
validate_integer(value[0])
|
||||
if not value[1] == "UNBOUNDED":
|
||||
validate_integer(value[1])
|
||||
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(
|
||||
RangeValueError(pname=prop_name,
|
||||
pvalue=value,
|
||||
vmin=range[0],
|
||||
vmax=range[1]))
|
||||
return value
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user