scalar-unit data type support as template input parameters

Rounding scalr-unit.size to next GB if it value is not multiples
of GBs.

Added unittest cases
modified messages and exception string as per i18n standard

Change-Id: Ia3bb82d4d0b9596376fec80a8b8b803352d94975
Closes-Bug: #1474142
This commit is contained in:
srinivas_tadepalli
2015-07-26 09:58:32 +05:30
parent 51c3343e2d
commit 2b0f7b9716
15 changed files with 296 additions and 40 deletions

View File

@@ -10,16 +10,21 @@
# License for the specific language governing permissions and limitations
# under the License.
from collections import OrderedDict
from translator.common.utils import CompareUtils
from translator.hot.translate_inputs import TranslateInputs
from translator.toscalib.parameters import Input
from translator.toscalib.tests.base import TestCase
from translator.toscalib.utils.gettextutils import _
import translator.toscalib.utils.yamlparser
class ToscaTemplateInputValidationTest(TestCase):
def _translate_input_test(self, tpl_snippet, input_params,
expectedmessage=None):
expectedmessage=None,
expected_hot_params=None):
inputs_dict = (translator.toscalib.utils.yamlparser.
simple_parse(tpl_snippet)['inputs'])
inputs = []
@@ -29,12 +34,24 @@ class ToscaTemplateInputValidationTest(TestCase):
translateinput = TranslateInputs(inputs, input_params)
try:
translateinput.translate()
except ValueError:
pass
resulted_hot_params = translateinput.translate()
if expected_hot_params:
self._compare_hot_params(resulted_hot_params,
expected_hot_params)
except Exception as err:
self.assertEqual(expectedmessage, err.__str__())
def _compare_hot_params(self, resulted_hot_params,
expected_hot_params):
for expected_param in expected_hot_params:
for resulted_param_obj in resulted_hot_params:
resulted_param = resulted_param_obj.get_dict_output()
result = CompareUtils.compare_dicts(expected_param,
resulted_param)
if not result:
raise Exception(_("hot input and resulted input "
"params are not equal."))
def test_invalid_input_type(self):
tpl_snippet = '''
inputs:
@@ -45,8 +62,8 @@ class ToscaTemplateInputValidationTest(TestCase):
- valid_values: [ 1, 2, 4, 8 ]
'''
input_params = {'cpus': 'string'}
expectedmessage = ('could not convert string to float: string')
input_params = {'cpus': '0.3'}
expectedmessage = _("invalid literal for int() with base 10: '0.3'")
self._translate_input_test(tpl_snippet, input_params,
expectedmessage)
@@ -61,7 +78,7 @@ class ToscaTemplateInputValidationTest(TestCase):
'''
input_params = {'num_cpus': '0'}
expectedmessage = ('num_cpus: 0 is not equal to "1".')
expectedmessage = _('num_cpus: 0 is not equal to "1".')
self._translate_input_test(tpl_snippet, input_params, expectedmessage)
def test_invalid_input_constraints_for_greater_or_equal(self):
@@ -75,7 +92,7 @@ class ToscaTemplateInputValidationTest(TestCase):
'''
input_params = {'num_cpus': '0'}
expectedmessage = ('num_cpus: 0 must be greater or equal to "1".')
expectedmessage = _('num_cpus: 0 must be greater or equal to "1".')
self._translate_input_test(tpl_snippet, input_params, expectedmessage)
def test_invalid_input_constraints_for_greater_than(self):
@@ -89,7 +106,7 @@ class ToscaTemplateInputValidationTest(TestCase):
'''
input_params = {'num_cpus': '0'}
expectedmessage = ('num_cpus: 0 must be greater than "1".')
expectedmessage = _('num_cpus: 0 must be greater than "1".')
self._translate_input_test(tpl_snippet, input_params, expectedmessage)
def test_invalid_input_constraints_for_less_than(self):
@@ -103,7 +120,7 @@ class ToscaTemplateInputValidationTest(TestCase):
'''
input_params = {'num_cpus': '8'}
expectedmessage = ('num_cpus: 8 must be less than "8".')
expectedmessage = _('num_cpus: 8 must be less than "8".')
self._translate_input_test(tpl_snippet, input_params, expectedmessage)
def test_invalid_input_constraints_for_less_or_equal(self):
@@ -117,7 +134,7 @@ class ToscaTemplateInputValidationTest(TestCase):
'''
input_params = {'num_cpus': '9'}
expectedmessage = ('num_cpus: 9 must be less or equal to "8".')
expectedmessage = _('num_cpus: 9 must be less or equal to "8".')
self._translate_input_test(tpl_snippet, input_params, expectedmessage)
def test_invalid_input_constraints_for_valid_values(self):
@@ -131,7 +148,8 @@ class ToscaTemplateInputValidationTest(TestCase):
'''
input_params = {'num_cpus': '3'}
expectedmessage = ('num_cpus: 3 is not an valid value "[1, 2, 4, 8]".')
expectedmessage = _('num_cpus: 3 is not an valid value'
' "[1, 2, 4, 8]".')
self._translate_input_test(tpl_snippet, input_params, expectedmessage)
def test_invalid_input_constraints_for_in_range(self):
@@ -145,7 +163,7 @@ class ToscaTemplateInputValidationTest(TestCase):
'''
input_params = {'num_cpus': '10'}
expectedmessage = ('num_cpus: 10 is out of range (min:1, max:8).')
expectedmessage = _('num_cpus: 10 is out of range (min:1, max:8).')
self._translate_input_test(tpl_snippet, input_params, expectedmessage)
def test_invalid_input_constraints_for_min_length(self):
@@ -159,7 +177,7 @@ class ToscaTemplateInputValidationTest(TestCase):
'''
input_params = {'user_name': 'abcd'}
expectedmessage = ('length of user_name: abcd must be at least "8".')
expectedmessage = _('length of user_name: abcd must be at least "8".')
self._translate_input_test(tpl_snippet, input_params, expectedmessage)
def test_invalid_input_constraints_for_max_length(self):
@@ -173,8 +191,8 @@ class ToscaTemplateInputValidationTest(TestCase):
'''
input_params = {'user_name': 'abcdefg'}
expectedmessage = ('length of user_name: '
'abcdefg must be no greater than "6".')
expectedmessage = _('length of user_name: '
'abcdefg must be no greater than "6".')
self._translate_input_test(tpl_snippet, input_params, expectedmessage)
def test_invalid_input_constraints_for_pattern(self):
@@ -188,6 +206,90 @@ class ToscaTemplateInputValidationTest(TestCase):
'''
input_params = {'user_name': '1-abc'}
expectedmessage = ('user_name: "1-abc" does '
'not match pattern "^\\w+$".')
expectedmessage = _('user_name: "1-abc" does '
'not match pattern "^\\w+$".')
self._translate_input_test(tpl_snippet, input_params, expectedmessage)
def test_valid_input_storage_size(self):
tpl_snippet = '''
inputs:
storage_size:
type: scalar-unit.size
description: size of the storage volume.
'''
expectedmessage = _('both equal.')
input_params = {'storage_size': '2 GB'}
expected_hot_params = [{'storage_size':
OrderedDict([('type', 'number'),
('description',
'size of the storage volume.'),
('default', 2)])}]
self._translate_input_test(tpl_snippet, input_params,
expectedmessage, expected_hot_params)
""" TOSCA 2000 MB => 2 GB HOT conversion"""
input_params = {'storage_size': '2000 MB'}
expected_hot_params = [{'storage_size':
OrderedDict([('type', 'number'),
('description',
'size of the storage volume.'),
('default', 2)])}]
self._translate_input_test(tpl_snippet, input_params,
expectedmessage, expected_hot_params)
""" TOSCA 2048 MB => 2 GB HOT conversion"""
input_params = {'storage_size': '2048 MB'}
expected_hot_params = [{'storage_size':
OrderedDict([('type', 'number'),
('description',
'size of the storage volume.'),
('default', 2)])}]
self._translate_input_test(tpl_snippet, input_params,
expectedmessage, expected_hot_params)
""" TOSCA 2 MB => 1 GB HOT conversion"""
input_params = {'storage_size': '2 MB'}
expected_hot_params = [{'storage_size':
OrderedDict([('type', 'number'),
('description',
'size of the storage volume.'),
('default', 1)])}]
self._translate_input_test(tpl_snippet, input_params,
expectedmessage, expected_hot_params)
""" TOSCA 1024 MB => 1 GB HOT conversion"""
input_params = {'storage_size': '1024 MB'}
expected_hot_params = [{'storage_size':
OrderedDict([('type', 'number'),
('description',
'size of the storage volume.'),
('default', 1)])}]
self._translate_input_test(tpl_snippet, input_params,
expectedmessage, expected_hot_params)
""" TOSCA 1024 MiB => 1 GB HOT conversion"""
input_params = {'storage_size': '1024 MiB'}
expected_hot_params = [{'storage_size':
OrderedDict([('type', 'number'),
('description',
'size of the storage volume.'),
('default', 1)])}]
self._translate_input_test(tpl_snippet, input_params,
expectedmessage, expected_hot_params)
def test_invalid_input_storage_size(self):
tpl_snippet = '''
inputs:
storage_size:
type: scalar-unit.size
description: size of the storage volume.
'''
input_params = {'storage_size': '0 MB'}
expectedmsg = _("Unit value should be > 0.")
self._translate_input_test(tpl_snippet, input_params, expectedmsg)
input_params = {'storage_size': '-2 MB'}
expectedmsg = _('"-2 MB" is not a valid scalar-unit')
self._translate_input_test(tpl_snippet, input_params, expectedmsg)

View File

@@ -0,0 +1,84 @@
# 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 translator.hot.tosca.tosca_block_storage import ToscaBlockStorage
from translator.toscalib.common.exception import InvalidPropertyValueError
from translator.toscalib.nodetemplate import NodeTemplate
from translator.toscalib.tests.base import TestCase
from translator.toscalib.utils.gettextutils import _
import translator.toscalib.utils.yamlparser
class ToscaBlockStoreTest(TestCase):
def _tosca_blockstore_test(self, tpl_snippet, expectedprops):
nodetemplates = (translator.toscalib.utils.yamlparser.
simple_parse(tpl_snippet)['node_templates'])
name = list(nodetemplates.keys())[0]
try:
nodetemplate = NodeTemplate(name, nodetemplates)
tosca_block_store = ToscaBlockStorage(nodetemplate)
tosca_block_store.handle_properties()
if not self._compare_properties(tosca_block_store.properties,
expectedprops):
raise Exception(_("Hot Properties are not"
" same as expected properties"))
except Exception:
# for time being rethrowing. Will be handled future based
# on new development
raise
def _compare_properties(self, hotprops, expectedprops):
return all(item in hotprops.items() for item in expectedprops.items())
def test_node_blockstorage_with_properties(self):
tpl_snippet = '''
node_templates:
my_storage:
type: tosca.nodes.BlockStorage
properties:
size: 1024 MiB
snapshot_id: abc
'''
expectedprops = {'snapshot_id': 'abc',
'size': 1}
self._tosca_blockstore_test(
tpl_snippet,
expectedprops)
tpl_snippet = '''
node_templates:
my_storage:
type: tosca.nodes.BlockStorage
properties:
size: 124 MB
snapshot_id: abc
'''
expectedprops = {'snapshot_id': 'abc',
'size': 1}
self._tosca_blockstore_test(
tpl_snippet,
expectedprops)
def test_node_blockstorage_with_invalid_size_property(self):
tpl_snippet = '''
node_templates:
my_storage:
type: tosca.nodes.BlockStorage
properties:
size: 0 MB
snapshot_id: abc
'''
expectedprops = {}
self.assertRaises(InvalidPropertyValueError,
lambda: self._tosca_blockstore_test(tpl_snippet,
expectedprops))

View File

@@ -11,8 +11,14 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
from translator.hot.syntax.hot_resource import HotResource
from translator.toscalib.common.exception import InvalidPropertyValueError
from translator.toscalib.elements.scalarunit import ScalarUnit_Size
from translator.toscalib.functions import GetInput
from translator.toscalib.utils.gettextutils import _
log = logging.getLogger("tosca")
class ToscaBlockStorage(HotResource):
@@ -28,6 +34,24 @@ class ToscaBlockStorage(HotResource):
for prop in self.nodetemplate.get_properties_objects():
if isinstance(prop.value, GetInput):
tosca_props[prop.name] = {'get_param': prop.value.input_name}
else:
if prop.name == "size":
size_value = (ScalarUnit_Size(prop.value).
get_num_from_scalar_unit('GiB'))
if size_value == 0:
# OpenStack Heat expects size in GB
raise InvalidPropertyValueError(
what=_('Cinder Volume Size unit should be in GBs'))
elif int(size_value) < size_value:
size_value = int(size_value) + 1
log.warning(_("Cinder unit value should be in "
"multiples of GBs. so corrected "
" %(prop_val)s to %(size_value)s GB.")
% {'prop_val': prop.value,
'size_value': size_value})
tosca_props[prop.name] = int(size_value)
else:
tosca_props[prop.name] = prop.value
self.properties = tosca_props
def get_hot_attribute(self, attribute, args):

View File

@@ -11,8 +11,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
from translator.hot.syntax.hot_parameter import HotParameter
from translator.toscalib.dataentity import DataEntity
from translator.toscalib.elements.scalarunit import ScalarUnit_Size
from translator.toscalib.utils.gettextutils import _
@@ -46,9 +48,12 @@ TOSCA_TO_HOT_INPUT_TYPES = {'string': 'string',
'float': 'number',
'boolean': 'boolean',
'timestamp': 'string',
'scalar-unit.size': 'number',
'null': 'string',
'PortDef': 'number'}
log = logging.getLogger('tosca')
class TranslateInputs(object):
'''Translate TOSCA Inputs to Heat Parameters.'''
@@ -67,7 +72,10 @@ class TranslateInputs(object):
hot_input_type = TOSCA_TO_HOT_INPUT_TYPES[input.type]
if input.name in self.parsed_params:
DataEntity.validate_datatype(hot_input_type,
input_type = hot_input_type
if input.type == "scalar-unit.size":
input_type = input.type
DataEntity.validate_datatype(input_type,
self.parsed_params[input.name])
hot_default = self.parsed_params[input.name]
elif input.default is not None:
@@ -75,6 +83,24 @@ class TranslateInputs(object):
else:
raise Exception(_("Need to specify a value "
"for input {0}").format(input.name))
if input.type == "scalar-unit.size":
# Assumption here is to use this scalar-unit.size for size of
# cinder volume in heat templates and will be in GB.
# should add logic to support other types if needed.
input_value = hot_default
hot_default = (ScalarUnit_Size(hot_default).
get_num_from_scalar_unit('GiB'))
if hot_default == 0:
raise Exception(_(
'Unit value should be > 0.'))
elif int(hot_default) < hot_default:
hot_default = int(hot_default) + 1
log.warning(_("Cinder unit value should be in multiples"
" of GBs. So corrected %(input_value)s "
"to %(hot_default)s GB.")
% {'input_value': input_value,
'hot_default': hot_default})
hot_constraints = []
if input.constraints:
for constraint in input.constraints:

View File

@@ -102,7 +102,7 @@ class ToscaHotTranslationTest(TestCase):
'hot_blockstorage_with_attachment.yaml'
params = {'cpus': 1,
'storage_location': '/dev/vdc',
'storage_size': 1,
'storage_size': '2000 MB',
'storage_snapshot_id': 'ssid'}
diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file,
hot_file,
@@ -118,7 +118,7 @@ class ToscaHotTranslationTest(TestCase):
'hot_blockstorage_with_custom_relationship_type.yaml'
params = {'cpus': 1,
'storage_location': '/dev/vdc',
'storage_size': 1,
'storage_size': '1 GB',
'storage_snapshot_id': 'ssid'}
diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file,
hot_file,
@@ -134,7 +134,7 @@ class ToscaHotTranslationTest(TestCase):
'hot_blockstorage_with_relationship_template.yaml'
params = {'cpus': 1,
'storage_location': '/dev/vdc',
'storage_size': 1}
'storage_size': '1 GB'}
diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file,
hot_file,
params)
@@ -151,7 +151,7 @@ class ToscaHotTranslationTest(TestCase):
'hot_blockstorage_with_attachment_notation1_alt2.yaml'
params = {'cpus': 1,
'storage_location': 'some_folder',
'storage_size': 1,
'storage_size': '1 GB',
'storage_snapshot_id': 'ssid'}
diff1 = TranslationUtils.compare_tosca_translation_with_hot(tosca_file,
hot_file1,
@@ -177,7 +177,7 @@ class ToscaHotTranslationTest(TestCase):
'hot_blockstorage_with_attachment_notation2_alt2.yaml'
params = {'cpus': 1,
'storage_location': '/dev/vdc',
'storage_size': 1,
'storage_size': '1 GB',
'storage_snapshot_id': 'ssid'}
diff1 = TranslationUtils.compare_tosca_translation_with_hot(tosca_file,
hot_file1,
@@ -203,7 +203,7 @@ class ToscaHotTranslationTest(TestCase):
'hot_multiple_blockstorage_with_attachment_alt2.yaml'
params = {'cpus': 1,
'storage_location': '/dev/vdc',
'storage_size': 1,
'storage_size': '1 GB',
'storage_snapshot_id': 'ssid'}
diff1 = TranslationUtils.compare_tosca_translation_with_hot(tosca_file,
hot_file1,

View File

@@ -161,9 +161,9 @@ tosca.nodes.BlockStorage:
derived_from: tosca.nodes.Root
properties:
size:
type: integer
type: scalar-unit.size
constraints:
- greater_or_equal: 1
- greater_or_equal: 1 MB
volume_id:
type: string
required: false

View File

@@ -58,6 +58,17 @@ class ScalarUnit(object):
converted = int(converted)
return converted
def get_unit_from_scalar_unit(self, unit=None):
if unit:
if unit.upper() not in self.SCALAR_UNIT_DICT.keys():
raise ValueError(_('input unit "%s" is not a valid unit')
% unit)
return unit
else:
regex = re.compile('([0-9.]+)\s*(\w+)')
result = regex.match(str(self.value)).groups()
return result[1].upper()
class ScalarUnit_Size(ScalarUnit):

View File

@@ -22,7 +22,7 @@ parameters:
storage_size:
type: number
description: Size of the storage to be created.
default: 1
default: 2
storage_snapshot_id:
type: string
description: "Optional identifier for an existing snapshot to use when creating storage."

View File

@@ -12,7 +12,7 @@ topology_template:
constraints:
- valid_values: [ 1, 2, 4, 8 ]
storage_size:
type: integer
type: scalar-unit.size
description: Size of the storage to be created.
default: 1 GB
storage_snapshot_id:

View File

@@ -19,8 +19,8 @@ topology_template:
constraints:
- valid_values: [ 1, 2, 4, 8 ]
storage_size:
type: integer
default: 1
type: scalar-unit.size
default: 1 GB
description: Size of the storage to be created.
storage_snapshot_id:
type: string

View File

@@ -19,8 +19,8 @@ topology_template:
constraints:
- valid_values: [ 1, 2, 4, 8 ]
storage_size:
type: integer
default: 1
type: scalar-unit.size
default: 1 GB
description: Size of the storage to be created.
storage_snapshot_id:
type: string

View File

@@ -15,9 +15,9 @@ topology_template:
constraints:
- valid_values: [ 1, 2, 4, 8 ]
storage_size:
type: integer
type: scalar-unit.size
description: Size of the storage to be created.
default: 1
default: 1 GB
storage_snapshot_id:
type: string
description: >

View File

@@ -11,9 +11,9 @@ topology_template:
constraints:
- valid_values: [ 1, 2, 4, 8 ]
storage_size:
type: integer
type: scalar-unit.size
description: Size of the storage to be created.
default: 1
default: 1 GB
storage_location:
type: string
description: Block storage mount point (filesystem path).

View File

@@ -11,8 +11,8 @@ topology_template:
constraints:
- valid_values: [ 1, 2, 4, 8 ]
storage_size:
type: integer
default: 1
type: scalar-unit.size
default: 1 GB
description: Size of the storage to be created.
storage_snapshot_id:
type: string

View File

@@ -41,6 +41,15 @@ topology_template:
host:
properties:
num_cpus: 2
disk_size: 10 GB
mem_size: 4 MB
os:
properties:
# host Operating System image properties
architecture: x86_64
type: linux
distribution: rhel
version: 6.5
requirements:
- req1:
node: my_storage
@@ -48,7 +57,7 @@ topology_template:
my_storage:
type: tosca.nodes.BlockStorage
properties:
size: 1
size: 1 GiB
snapshot_id: id
relationship_templates: