diff --git a/translator/hot/tosca/tosca_block_storage_attachment.py b/translator/hot/tosca/tosca_block_storage_attachment.py index 7eba8683..2b474bb2 100644 --- a/translator/hot/tosca/tosca_block_storage_attachment.py +++ b/translator/hot/tosca/tosca_block_storage_attachment.py @@ -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 diff --git a/translator/hot/tosca_translator.py b/translator/hot/tosca_translator.py index 08f606da..8d5af60e 100644 --- a/translator/hot/tosca_translator.py +++ b/translator/hot/tosca_translator.py @@ -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() diff --git a/translator/hot/translate_node_templates.py b/translator/hot/translate_node_templates.py index eeb0bc3c..71474270 100644 --- a/translator/hot/translate_node_templates.py +++ b/translator/hot/translate_node_templates.py @@ -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 diff --git a/translator/tests/data/tosca_blockstorage_with_attachment.yaml b/translator/tests/data/tosca_blockstorage_with_attachment.yaml index a5a7286d..30a7912d 100644 --- a/translator/tests/data/tosca_blockstorage_with_attachment.yaml +++ b/translator/tests/data/tosca_blockstorage_with_attachment.yaml @@ -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: diff --git a/translator/tests/data/tosca_blockstorage_with_attachment_notation1.yaml b/translator/tests/data/tosca_blockstorage_with_attachment_notation1.yaml new file mode 100644 index 00000000..67cf90f5 --- /dev/null +++ b/translator/tests/data/tosca_blockstorage_with_attachment_notation1.yaml @@ -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 diff --git a/translator/tests/data/tosca_blockstorage_with_attachment_notation2.yaml b/translator/tests/data/tosca_blockstorage_with_attachment_notation2.yaml new file mode 100644 index 00000000..47b5e82f --- /dev/null +++ b/translator/tests/data/tosca_blockstorage_with_attachment_notation2.yaml @@ -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 + diff --git a/translator/tests/test_blockstorage.py b/translator/tests/test_blockstorage.py index 5c8ff8c3..fa7ca82e 100644 --- a/translator/tests/test_blockstorage.py +++ b/translator/tests/test_blockstorage.py @@ -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( diff --git a/translator/toscalib/elements/property_definition.py b/translator/toscalib/elements/property_definition.py index ad5d04e1..b1b477b1 100644 --- a/translator/toscalib/elements/property_definition.py +++ b/translator/toscalib/elements/property_definition.py @@ -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: diff --git a/translator/toscalib/elements/relationshiptype.py b/translator/toscalib/elements/relationshiptype.py index b3babb70..8c6349bc 100644 --- a/translator/toscalib/elements/relationshiptype.py +++ b/translator/toscalib/elements/relationshiptype.py @@ -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') diff --git a/translator/toscalib/elements/statefulentitytype.py b/translator/toscalib/elements/statefulentitytype.py index 930c08ac..a3c8e144 100644 --- a/translator/toscalib/elements/statefulentitytype.py +++ b/translator/toscalib/elements/statefulentitytype.py @@ -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: diff --git a/translator/toscalib/entity_template.py b/translator/toscalib/entity_template.py index 476e0d9f..0aed9e92 100644 --- a/translator/toscalib/entity_template.py +++ b/translator/toscalib/entity_template.py @@ -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 diff --git a/translator/toscalib/nodetemplate.py b/translator/toscalib/nodetemplate.py index 814ed59f..6ee7d4bd 100644 --- a/translator/toscalib/nodetemplate.py +++ b/translator/toscalib/nodetemplate.py @@ -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: diff --git a/translator/toscalib/relationship_template.py b/translator/toscalib/relationship_template.py index 6e1170cb..378470cb 100644 --- a/translator/toscalib/relationship_template.py +++ b/translator/toscalib/relationship_template.py @@ -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): diff --git a/translator/toscalib/tests/test_properties.py b/translator/toscalib/tests/test_properties.py index 8c7dc2bb..75333e7a 100644 --- a/translator/toscalib/tests/test_properties.py +++ b/translator/toscalib/tests/test_properties.py @@ -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'} diff --git a/translator/toscalib/tests/test_toscatplvalidation.py b/translator/toscalib/tests/test_toscatplvalidation.py index 3ad883fe..7b8f86de 100644 --- a/translator/toscalib/tests/test_toscatplvalidation.py +++ b/translator/toscalib/tests/test_toscatplvalidation.py @@ -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".') diff --git a/translator/toscalib/tosca_template.py b/translator/toscalib/tosca_template.py index ac66845f..8399458d 100644 --- a/translator/toscalib/tosca_template.py +++ b/translator/toscalib/tosca_template.py @@ -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 {}