add blockstorage attachment notation
Implement blockstorage attachment notations described in E1.4.2 and E1.4.3 of TOSCA wd02. Partially Implements: blueprint new-blockstorage-templates-tosca Change-Id: I7fe61b4458840bedeac88f58161cece12f2929bb
This commit is contained in:
parent
6aac081a24
commit
154b0e716a
@ -30,6 +30,8 @@ class ToscaBlockStorageAttachment(HotResource):
|
|||||||
for prop in self.nodetemplate.properties:
|
for prop in self.nodetemplate.properties:
|
||||||
if isinstance(prop.value, GetInput):
|
if isinstance(prop.value, GetInput):
|
||||||
tosca_props[prop.name] = {'get_param': prop.value.input_name}
|
tosca_props[prop.name] = {'get_param': prop.value.input_name}
|
||||||
|
else:
|
||||||
|
tosca_props[prop.name] = prop.value
|
||||||
self.properties = tosca_props
|
self.properties = tosca_props
|
||||||
#instance_uuid and volume_id for Cinder volume attachment
|
#instance_uuid and volume_id for Cinder volume attachment
|
||||||
self.properties['instance_uuid'] = self.instace_uuid
|
self.properties['instance_uuid'] = self.instace_uuid
|
||||||
|
@ -31,7 +31,7 @@ class TOSCATranslator(object):
|
|||||||
self._resolve_input()
|
self._resolve_input()
|
||||||
self.hot_template.description = self.tosca.description
|
self.hot_template.description = self.tosca.description
|
||||||
self.hot_template.parameters = self._translate_inputs()
|
self.hot_template.parameters = self._translate_inputs()
|
||||||
self.node_translator = TranslateNodeTemplates(self.tosca.nodetemplates,
|
self.node_translator = TranslateNodeTemplates(self.tosca,
|
||||||
self.hot_template)
|
self.hot_template)
|
||||||
self.hot_template.resources = self.node_translator.translate()
|
self.hot_template.resources = self.node_translator.translate()
|
||||||
self.hot_template.outputs = self._translate_outputs()
|
self.hot_template.outputs = self._translate_outputs()
|
||||||
|
@ -62,8 +62,9 @@ TOSCA_TO_HOT_PROPERTIES = {'properties': 'input'}
|
|||||||
class TranslateNodeTemplates():
|
class TranslateNodeTemplates():
|
||||||
'''Translate TOSCA NodeTemplates to Heat Resources.'''
|
'''Translate TOSCA NodeTemplates to Heat Resources.'''
|
||||||
|
|
||||||
def __init__(self, nodetemplates, hot_template):
|
def __init__(self, tosca, hot_template):
|
||||||
self.nodetemplates = nodetemplates
|
self.tosca = tosca
|
||||||
|
self.nodetemplates = self.tosca.nodetemplates
|
||||||
self.hot_template = hot_template
|
self.hot_template = hot_template
|
||||||
# list of all HOT resources generated
|
# list of all HOT resources generated
|
||||||
self.hot_resources = []
|
self.hot_resources = []
|
||||||
@ -165,11 +166,21 @@ class TranslateNodeTemplates():
|
|||||||
if value.type == 'tosca.nodes.BlockStorage':
|
if value.type == 'tosca.nodes.BlockStorage':
|
||||||
attach = True
|
attach = True
|
||||||
if attach:
|
if attach:
|
||||||
|
relationship_tpl = None
|
||||||
for req in node.requirements:
|
for req in node.requirements:
|
||||||
for rkey, rval in req.items():
|
for rkey, rval in req.items():
|
||||||
if rkey == 'type':
|
if rkey == 'type':
|
||||||
rval = rval + "_" + str(suffix)
|
relationship_tpl = req
|
||||||
att = RelationshipTemplate(req, rval, None)
|
elif rkey == 'template':
|
||||||
|
relationship_tpl = \
|
||||||
|
self.tosca._tpl_relationship_templates()[rval]
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
if relationship_tpl:
|
||||||
|
rval_new = rval + "_" + str(suffix)
|
||||||
|
att = RelationshipTemplate(
|
||||||
|
relationship_tpl, rval_new,
|
||||||
|
self.tosca._tpl_relationship_types())
|
||||||
hot_node = ToscaBlockStorageAttachment(att, ntpl,
|
hot_node = ToscaBlockStorageAttachment(att, ntpl,
|
||||||
node.name,
|
node.name,
|
||||||
volume_name
|
volume_name
|
||||||
|
@ -15,10 +15,12 @@ inputs:
|
|||||||
description: Size of the storage to be created.
|
description: Size of the storage to be created.
|
||||||
storage_snapshot_id:
|
storage_snapshot_id:
|
||||||
type: string
|
type: string
|
||||||
description: Some identifier that represents an existing snapshot that should be used when creating the block storage.
|
description: >
|
||||||
|
Some identifier that represents an existing snapshot that should be used when creating the block storage.
|
||||||
storage_location:
|
storage_location:
|
||||||
type: string
|
type: string
|
||||||
description: The relative location (e.g., path on the file system), which provides the root location to address an attached node.
|
description: >
|
||||||
|
The relative location (e.g., path on the file system), which provides the root location to address an attached node.
|
||||||
|
|
||||||
node_templates:
|
node_templates:
|
||||||
my_server:
|
my_server:
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
tosca_definitions_version: tosca_simple_1.0
|
||||||
|
|
||||||
|
description: >
|
||||||
|
TOSCA simple profile with server and attached block storage.
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
cpus:
|
||||||
|
type: integer
|
||||||
|
description: Number of CPUs for the server.
|
||||||
|
constraints:
|
||||||
|
- valid_values: [ 1, 2, 4, 8 ]
|
||||||
|
storage_size:
|
||||||
|
type: integer
|
||||||
|
default: 1 GB
|
||||||
|
description: Size of the storage to be created.
|
||||||
|
storage_snapshot_id:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Some identifier that represents an existing snapshot that should be used when creating the block storage.
|
||||||
|
storage_location:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
The relative location (e.g., path on the file system), which provides the root location to address an attached node.
|
||||||
|
|
||||||
|
node_templates:
|
||||||
|
my_web_app_tier_1:
|
||||||
|
type: tosca.nodes.Compute
|
||||||
|
properties:
|
||||||
|
# compute properties (flavor)
|
||||||
|
disk_size: 10
|
||||||
|
num_cpus: { get_input: cpus }
|
||||||
|
mem_size: 4096
|
||||||
|
# host image properties
|
||||||
|
os_arch: x86_64
|
||||||
|
os_type: Linux
|
||||||
|
os_distribution: Fedora
|
||||||
|
os_version: 18
|
||||||
|
requirements:
|
||||||
|
- attachment: my_storage
|
||||||
|
type: MyAttachTo
|
||||||
|
|
||||||
|
my_web_app_tier_2:
|
||||||
|
type: tosca.nodes.Compute
|
||||||
|
properties:
|
||||||
|
# compute properties (flavor)
|
||||||
|
disk_size: 10
|
||||||
|
num_cpus: { get_input: cpus }
|
||||||
|
mem_size: 4096
|
||||||
|
# host image properties
|
||||||
|
os_arch: x86_64
|
||||||
|
os_type: Linux
|
||||||
|
os_distribution: Fedora
|
||||||
|
os_version: 18
|
||||||
|
requirements:
|
||||||
|
- attachment: my_storage
|
||||||
|
type: MyAttachTo
|
||||||
|
properties:
|
||||||
|
location: /some_other_data_location
|
||||||
|
|
||||||
|
my_storage:
|
||||||
|
type: tosca.nodes.BlockStorage
|
||||||
|
properties:
|
||||||
|
size: { get_input: storage_size }
|
||||||
|
snapshot_id: { get_input: storage_snapshot_id }
|
||||||
|
|
||||||
|
relationship_types:
|
||||||
|
MyAttachTo:
|
||||||
|
derived_from: tosca.relationships.AttachTo
|
||||||
|
properties: # follows the syntax of property definitions
|
||||||
|
location:
|
||||||
|
type: string
|
||||||
|
default: /default_location
|
@ -0,0 +1,82 @@
|
|||||||
|
tosca_definitions_version: tosca_simple_1.0
|
||||||
|
|
||||||
|
description: >
|
||||||
|
TOSCA simple profile with server and attached block storage.
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
cpus:
|
||||||
|
type: integer
|
||||||
|
description: Number of CPUs for the server.
|
||||||
|
constraints:
|
||||||
|
- valid_values: [ 1, 2, 4, 8 ]
|
||||||
|
storage_size:
|
||||||
|
type: integer
|
||||||
|
default: 1 GB
|
||||||
|
description: Size of the storage to be created.
|
||||||
|
storage_snapshot_id:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Some identifier that represents an existing snapshot that should be used when creating the block storage.
|
||||||
|
storage_location:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
The relative location (e.g., path on the file system), which provides the root location to address an attached node.
|
||||||
|
|
||||||
|
node_templates:
|
||||||
|
my_web_app_tier_1:
|
||||||
|
type: tosca.nodes.Compute
|
||||||
|
properties:
|
||||||
|
# compute properties (flavor)
|
||||||
|
disk_size: 10
|
||||||
|
num_cpus: { get_input: cpus }
|
||||||
|
mem_size: 4096
|
||||||
|
# host image properties
|
||||||
|
os_arch: x86_64
|
||||||
|
os_type: Linux
|
||||||
|
os_distribution: Fedora
|
||||||
|
os_version: 18
|
||||||
|
requirements:
|
||||||
|
- attachment: my_storage
|
||||||
|
template: storage_attachto_1
|
||||||
|
|
||||||
|
my_web_app_tier_2:
|
||||||
|
type: tosca.nodes.Compute
|
||||||
|
properties:
|
||||||
|
# compute properties (flavor)
|
||||||
|
disk_size: 10
|
||||||
|
num_cpus: { get_input: cpus }
|
||||||
|
mem_size: 4096
|
||||||
|
# host image properties
|
||||||
|
os_arch: x86_64
|
||||||
|
os_type: Linux
|
||||||
|
os_distribution: Fedora
|
||||||
|
os_version: 18
|
||||||
|
requirements:
|
||||||
|
- attachment: my_storage
|
||||||
|
template: storage_attachto_2
|
||||||
|
|
||||||
|
my_storage:
|
||||||
|
type: tosca.nodes.BlockStorage
|
||||||
|
properties:
|
||||||
|
size: { get_input: storage_size }
|
||||||
|
snapshot_id: { get_input: storage_snapshot_id }
|
||||||
|
|
||||||
|
relationship_templates:
|
||||||
|
storage_attachto_1:
|
||||||
|
type: MyAttachTo
|
||||||
|
properties:
|
||||||
|
location: /my_data_location
|
||||||
|
|
||||||
|
storage_attachto_2:
|
||||||
|
type: MyAttachTo
|
||||||
|
properties:
|
||||||
|
location: /some_other_data_location
|
||||||
|
|
||||||
|
relationship_types:
|
||||||
|
MyAttachTo:
|
||||||
|
derived_from: tosca.relationships.AttachTo
|
||||||
|
properties: # follows the syntax of property definitions
|
||||||
|
location:
|
||||||
|
type: string
|
||||||
|
default: /default_location
|
||||||
|
|
@ -59,6 +59,71 @@ class ToscaBlockStorageTest(TestCase):
|
|||||||
self.assertEqual({'get_resource': 'my_storage'},
|
self.assertEqual({'get_resource': 'my_storage'},
|
||||||
outputs['volume_id']['value'])
|
outputs['volume_id']['value'])
|
||||||
|
|
||||||
|
def test_translate_storage_notation1(self):
|
||||||
|
'''TOSCA template with single BlockStorage and Attachment.'''
|
||||||
|
tosca_tpl = os.path.join(
|
||||||
|
os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
"data/tosca_blockstorage_with_attachment_notation1.yaml")
|
||||||
|
|
||||||
|
tosca = ToscaTemplate(tosca_tpl)
|
||||||
|
translate = TOSCATranslator(tosca, self.parsed_params)
|
||||||
|
output = translate.translate()
|
||||||
|
|
||||||
|
expected_resource_1 = {'type': 'OS::Cinder::VolumeAttachment',
|
||||||
|
'properties':
|
||||||
|
{'instance_uuid': 'my_web_app_tier_1',
|
||||||
|
'location': '/default_location',
|
||||||
|
'volume_id': 'my_storage'}}
|
||||||
|
expected_resource_2 = {'type': 'OS::Cinder::VolumeAttachment',
|
||||||
|
'properties':
|
||||||
|
{'instance_uuid': 'my_web_app_tier_2',
|
||||||
|
'location': '/some_other_data_location',
|
||||||
|
'volume_id': 'my_storage'}}
|
||||||
|
|
||||||
|
output_dict = translator.toscalib.utils.yamlparser.simple_parse(output)
|
||||||
|
|
||||||
|
resources = output_dict.get('resources')
|
||||||
|
self.assertIn('myattachto_1', resources.keys())
|
||||||
|
self.assertIn('myattachto_2', resources.keys())
|
||||||
|
self.assertIn(expected_resource_1, resources.values())
|
||||||
|
self.assertIn(expected_resource_2, resources.values())
|
||||||
|
|
||||||
|
def test_translate_storage_notation2(self):
|
||||||
|
'''TOSCA template with single BlockStorage and Attachment.'''
|
||||||
|
tosca_tpl = os.path.join(
|
||||||
|
os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
"data/tosca_blockstorage_with_attachment_notation2.yaml")
|
||||||
|
|
||||||
|
tosca = ToscaTemplate(tosca_tpl)
|
||||||
|
translate = TOSCATranslator(tosca, self.parsed_params)
|
||||||
|
output = translate.translate()
|
||||||
|
|
||||||
|
expected_resource_1 = {'type': 'OS::Cinder::VolumeAttachment',
|
||||||
|
'properties':
|
||||||
|
{'instance_uuid': 'my_web_app_tier_1',
|
||||||
|
'location': '/my_data_location',
|
||||||
|
'volume_id': 'my_storage'}}
|
||||||
|
expected_resource_2 = {'type': 'OS::Cinder::VolumeAttachment',
|
||||||
|
'properties':
|
||||||
|
{'instance_uuid': 'my_web_app_tier_2',
|
||||||
|
'location': '/some_other_data_location',
|
||||||
|
'volume_id': 'my_storage'}}
|
||||||
|
|
||||||
|
output_dict = translator.toscalib.utils.yamlparser.simple_parse(output)
|
||||||
|
|
||||||
|
resources = output_dict.get('resources')
|
||||||
|
# Resource name suffix depends on nodetemplates order in dict, which is
|
||||||
|
# not certain. So we have two possibilities of resources name.
|
||||||
|
if resources.get('storage_attachto_1_1'):
|
||||||
|
self.assertIn('storage_attachto_1_1', resources.keys())
|
||||||
|
self.assertIn('storage_attachto_2_2', resources.keys())
|
||||||
|
else:
|
||||||
|
self.assertIn('storage_attachto_1_2', resources.keys())
|
||||||
|
self.assertIn('storage_attachto_2_1', resources.keys())
|
||||||
|
|
||||||
|
self.assertIn(expected_resource_1, resources.values())
|
||||||
|
self.assertIn(expected_resource_2, resources.values())
|
||||||
|
|
||||||
def test_translate_multi_storage(self):
|
def test_translate_multi_storage(self):
|
||||||
'''TOSCA template with multiple BlockStorage and Attachment.'''
|
'''TOSCA template with multiple BlockStorage and Attachment.'''
|
||||||
tosca_tpl = os.path.join(
|
tosca_tpl = os.path.join(
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from translator.toscalib.common.exception import InvalidSchemaError
|
||||||
|
|
||||||
|
|
||||||
class PropertyDef(object):
|
class PropertyDef(object):
|
||||||
'''TOSCA built-in Property type.'''
|
'''TOSCA built-in Property type.'''
|
||||||
@ -19,6 +21,13 @@ class PropertyDef(object):
|
|||||||
self.value = value
|
self.value = value
|
||||||
self.schema = schema
|
self.schema = schema
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.schema['type']
|
||||||
|
except KeyError:
|
||||||
|
msg = (_("Property definition of %(pname)s must have type.") %
|
||||||
|
dict(pname=self.name))
|
||||||
|
raise InvalidSchemaError(message=msg)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def required(self):
|
def required(self):
|
||||||
if self.schema:
|
if self.schema:
|
||||||
|
@ -21,6 +21,13 @@ class RelationshipType(StatefulEntityType):
|
|||||||
self.capability_name = capability_name
|
self.capability_name = capability_name
|
||||||
self.custom_def = custom_def
|
self.custom_def = custom_def
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent_type(self):
|
||||||
|
'''Return a relationship this reletionship is derived from.'''
|
||||||
|
prel = self.derived_from(self.defs)
|
||||||
|
if prel:
|
||||||
|
return RelationshipType(prel)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def valid_targets(self):
|
def valid_targets(self):
|
||||||
return self.entity_value(self.defs, 'valid_targets')
|
return self.entity_value(self.defs, 'valid_targets')
|
||||||
|
@ -29,10 +29,12 @@ class StatefulEntityType(EntityType):
|
|||||||
'remove_target']
|
'remove_target']
|
||||||
|
|
||||||
def __init__(self, entitytype, prefix, custom_def=None):
|
def __init__(self, entitytype, prefix, custom_def=None):
|
||||||
|
entire_entitytype = entitytype
|
||||||
if not entitytype.startswith(self.TOSCA):
|
if not entitytype.startswith(self.TOSCA):
|
||||||
entitytype = prefix + entitytype
|
entire_entitytype = prefix + entitytype
|
||||||
if entitytype in list(self.TOSCA_DEF.keys()):
|
if entire_entitytype in list(self.TOSCA_DEF.keys()):
|
||||||
self.defs = self.TOSCA_DEF[entitytype]
|
self.defs = self.TOSCA_DEF[entire_entitytype]
|
||||||
|
entitytype = entire_entitytype
|
||||||
elif custom_def and entitytype in list(custom_def.keys()):
|
elif custom_def and entitytype in list(custom_def.keys()):
|
||||||
self.defs = custom_def[entitytype]
|
self.defs = custom_def[entitytype]
|
||||||
else:
|
else:
|
||||||
|
@ -38,7 +38,7 @@ class EntityTemplate(object):
|
|||||||
custom_def)
|
custom_def)
|
||||||
if entity_name == 'relationship_type':
|
if entity_name == 'relationship_type':
|
||||||
self.type_definition = RelationshipType(self.entity_tpl['type'],
|
self.type_definition = RelationshipType(self.entity_tpl['type'],
|
||||||
custom_def)
|
None, custom_def)
|
||||||
self._properties = None
|
self._properties = None
|
||||||
self._interfaces = None
|
self._interfaces = None
|
||||||
self._requirements = None
|
self._requirements = None
|
||||||
|
@ -133,7 +133,7 @@ class NodeTemplate(EntityTemplate):
|
|||||||
|
|
||||||
def _validate_requirements(self):
|
def _validate_requirements(self):
|
||||||
type_requires = self.type_definition.get_all_requirements()
|
type_requires = self.type_definition.get_all_requirements()
|
||||||
allowed_reqs = []
|
allowed_reqs = ["template"]
|
||||||
if type_requires:
|
if type_requires:
|
||||||
for treq in type_requires:
|
for treq in type_requires:
|
||||||
for key in treq:
|
for key in treq:
|
||||||
|
@ -29,7 +29,7 @@ class RelationshipTemplate(EntityTemplate):
|
|||||||
super(RelationshipTemplate, self).__init__(name,
|
super(RelationshipTemplate, self).__init__(name,
|
||||||
relationship_template,
|
relationship_template,
|
||||||
'relationship_type',
|
'relationship_type',
|
||||||
custom_def=None)
|
custom_def)
|
||||||
self.name = name.lower()
|
self.name = name.lower()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
@ -30,8 +30,7 @@ class PropertyTest(TestCase):
|
|||||||
test_property_schema)
|
test_property_schema)
|
||||||
error = self.assertRaises(InvalidTypeError,
|
error = self.assertRaises(InvalidTypeError,
|
||||||
propertyInstance.validate)
|
propertyInstance.validate)
|
||||||
self.assertEqual('Type "tosca.datatypes.network.Fish" '
|
self.assertEqual('Type "Fish" is not a valid type.', str(error))
|
||||||
'is not a valid type.', str(error))
|
|
||||||
|
|
||||||
def test_list(self):
|
def test_list(self):
|
||||||
test_property_schema = {'type': 'list'}
|
test_property_schema = {'type': 'list'}
|
||||||
|
@ -220,7 +220,7 @@ class ToscaTemplateValidationTest(TestCase):
|
|||||||
interfaces:
|
interfaces:
|
||||||
tosca.interfaces.node.Lifecycle:
|
tosca.interfaces.node.Lifecycle:
|
||||||
create: webserver_install.sh
|
create: webserver_install.sh
|
||||||
start: webserver_start.sh
|
start: d.sh
|
||||||
'''
|
'''
|
||||||
expectedmessage = ('Requirements of template webserver '
|
expectedmessage = ('Requirements of template webserver '
|
||||||
'must be of type: "list".')
|
'must be of type: "list".')
|
||||||
|
@ -98,6 +98,7 @@ class ToscaTemplate(object):
|
|||||||
|
|
||||||
def _relationship_templates(self):
|
def _relationship_templates(self):
|
||||||
custom_types = {}
|
custom_types = {}
|
||||||
|
# Handle custom relationships defined in outer template file
|
||||||
imports = self._tpl_imports()
|
imports = self._tpl_imports()
|
||||||
if imports:
|
if imports:
|
||||||
for definition in imports:
|
for definition in imports:
|
||||||
@ -109,8 +110,15 @@ class ToscaTemplate(object):
|
|||||||
custom_type = YAML_LOADER(def_file)
|
custom_type = YAML_LOADER(def_file)
|
||||||
rel_types = custom_type.get('relationship_types') or {}
|
rel_types = custom_type.get('relationship_types') or {}
|
||||||
for name in rel_types:
|
for name in rel_types:
|
||||||
defintion = rel_types[name]
|
definition = rel_types[name]
|
||||||
custom_types[name] = defintion
|
custom_types[name] = definition
|
||||||
|
|
||||||
|
# Handle custom relationships defined in current template file
|
||||||
|
rel_types = self._tpl_relationship_types()
|
||||||
|
for name in rel_types:
|
||||||
|
definition = rel_types[name]
|
||||||
|
custom_types[name] = definition
|
||||||
|
|
||||||
rel_templates = []
|
rel_templates = []
|
||||||
tpls = self._tpl_relationship_templates()
|
tpls = self._tpl_relationship_templates()
|
||||||
for name in tpls:
|
for name in tpls:
|
||||||
@ -145,6 +153,9 @@ class ToscaTemplate(object):
|
|||||||
def _tpl_relationship_templates(self):
|
def _tpl_relationship_templates(self):
|
||||||
return self.tpl.get(RELATIONSHIP_TEMPLATES) or {}
|
return self.tpl.get(RELATIONSHIP_TEMPLATES) or {}
|
||||||
|
|
||||||
|
def _tpl_relationship_types(self):
|
||||||
|
return self.tpl.get(RELATIONSHIP_TYPES) or {}
|
||||||
|
|
||||||
def _tpl_outputs(self):
|
def _tpl_outputs(self):
|
||||||
return self.tpl.get(OUTPUTS) or {}
|
return self.tpl.get(OUTPUTS) or {}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user