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:
Yaoguo Jiang 2014-10-22 11:02:51 +00:00
parent 6aac081a24
commit 154b0e716a
16 changed files with 280 additions and 18 deletions

View File

@ -30,6 +30,8 @@ class ToscaBlockStorageAttachment(HotResource):
for prop in self.nodetemplate.properties:
if isinstance(prop.value, GetInput):
tosca_props[prop.name] = {'get_param': prop.value.input_name}
else:
tosca_props[prop.name] = prop.value
self.properties = tosca_props
#instance_uuid and volume_id for Cinder volume attachment
self.properties['instance_uuid'] = self.instace_uuid

View File

@ -31,7 +31,7 @@ class TOSCATranslator(object):
self._resolve_input()
self.hot_template.description = self.tosca.description
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.resources = self.node_translator.translate()
self.hot_template.outputs = self._translate_outputs()

View File

@ -62,8 +62,9 @@ TOSCA_TO_HOT_PROPERTIES = {'properties': 'input'}
class TranslateNodeTemplates():
'''Translate TOSCA NodeTemplates to Heat Resources.'''
def __init__(self, nodetemplates, hot_template):
self.nodetemplates = nodetemplates
def __init__(self, tosca, hot_template):
self.tosca = tosca
self.nodetemplates = self.tosca.nodetemplates
self.hot_template = hot_template
# list of all HOT resources generated
self.hot_resources = []
@ -165,11 +166,21 @@ class TranslateNodeTemplates():
if value.type == 'tosca.nodes.BlockStorage':
attach = True
if attach:
relationship_tpl = None
for req in node.requirements:
for rkey, rval in req.items():
if rkey == 'type':
rval = rval + "_" + str(suffix)
att = RelationshipTemplate(req, rval, None)
relationship_tpl = req
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,
node.name,
volume_name

View File

@ -15,10 +15,12 @@ inputs:
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.
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.
description: >
The relative location (e.g., path on the file system), which provides the root location to address an attached node.
node_templates:
my_server:

View File

@ -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

View File

@ -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

View File

@ -59,6 +59,71 @@ class ToscaBlockStorageTest(TestCase):
self.assertEqual({'get_resource': 'my_storage'},
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):
'''TOSCA template with multiple BlockStorage and Attachment.'''
tosca_tpl = os.path.join(

View File

@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from translator.toscalib.common.exception import InvalidSchemaError
class PropertyDef(object):
'''TOSCA built-in Property type.'''
@ -19,6 +21,13 @@ class PropertyDef(object):
self.value = value
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
def required(self):
if self.schema:

View File

@ -21,6 +21,13 @@ class RelationshipType(StatefulEntityType):
self.capability_name = capability_name
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
def valid_targets(self):
return self.entity_value(self.defs, 'valid_targets')

View File

@ -29,10 +29,12 @@ class StatefulEntityType(EntityType):
'remove_target']
def __init__(self, entitytype, prefix, custom_def=None):
entire_entitytype = entitytype
if not entitytype.startswith(self.TOSCA):
entitytype = prefix + entitytype
if entitytype in list(self.TOSCA_DEF.keys()):
self.defs = self.TOSCA_DEF[entitytype]
entire_entitytype = prefix + entitytype
if entire_entitytype in list(self.TOSCA_DEF.keys()):
self.defs = self.TOSCA_DEF[entire_entitytype]
entitytype = entire_entitytype
elif custom_def and entitytype in list(custom_def.keys()):
self.defs = custom_def[entitytype]
else:

View File

@ -38,7 +38,7 @@ class EntityTemplate(object):
custom_def)
if entity_name == 'relationship_type':
self.type_definition = RelationshipType(self.entity_tpl['type'],
custom_def)
None, custom_def)
self._properties = None
self._interfaces = None
self._requirements = None

View File

@ -133,7 +133,7 @@ class NodeTemplate(EntityTemplate):
def _validate_requirements(self):
type_requires = self.type_definition.get_all_requirements()
allowed_reqs = []
allowed_reqs = ["template"]
if type_requires:
for treq in type_requires:
for key in treq:

View File

@ -29,7 +29,7 @@ class RelationshipTemplate(EntityTemplate):
super(RelationshipTemplate, self).__init__(name,
relationship_template,
'relationship_type',
custom_def=None)
custom_def)
self.name = name.lower()
def validate(self):

View File

@ -30,8 +30,7 @@ class PropertyTest(TestCase):
test_property_schema)
error = self.assertRaises(InvalidTypeError,
propertyInstance.validate)
self.assertEqual('Type "tosca.datatypes.network.Fish" '
'is not a valid type.', str(error))
self.assertEqual('Type "Fish" is not a valid type.', str(error))
def test_list(self):
test_property_schema = {'type': 'list'}

View File

@ -220,7 +220,7 @@ class ToscaTemplateValidationTest(TestCase):
interfaces:
tosca.interfaces.node.Lifecycle:
create: webserver_install.sh
start: webserver_start.sh
start: d.sh
'''
expectedmessage = ('Requirements of template webserver '
'must be of type: "list".')

View File

@ -98,6 +98,7 @@ class ToscaTemplate(object):
def _relationship_templates(self):
custom_types = {}
# Handle custom relationships defined in outer template file
imports = self._tpl_imports()
if imports:
for definition in imports:
@ -109,8 +110,15 @@ class ToscaTemplate(object):
custom_type = YAML_LOADER(def_file)
rel_types = custom_type.get('relationship_types') or {}
for name in rel_types:
defintion = rel_types[name]
custom_types[name] = defintion
definition = rel_types[name]
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 = []
tpls = self._tpl_relationship_templates()
for name in tpls:
@ -145,6 +153,9 @@ class ToscaTemplate(object):
def _tpl_relationship_templates(self):
return self.tpl.get(RELATIONSHIP_TEMPLATES) or {}
def _tpl_relationship_types(self):
return self.tpl.get(RELATIONSHIP_TYPES) or {}
def _tpl_outputs(self):
return self.tpl.get(OUTPUTS) or {}