Implement TOSCA Block Storage

Create TOSCA BlockStorage node type and its mapping in Heat. Introduce
relationship template which will also provide a base for implementing
relationship templates section in the TOSCA template. Introduce tests for
generator/translator code.

implements blueprint tosca-blockstorage-type
Closes-Bug: #1351129
Closes-Bug: #1354632

Change-Id: I7df198ea45875031557e2607d932f14f272bf2e1
This commit is contained in:
Sahdev Zala 2014-08-08 11:12:46 -05:00
parent f2ba5fa317
commit ccefa3514e
25 changed files with 748 additions and 222 deletions

View File

@ -103,7 +103,7 @@ class HotResource(object):
# in interfaces_deploy_sequence
# TODO(anyone): find some better way to encode this implicit sequence
group = {}
for op, hot in deploy_lookup.iteritems():
for op, hot in deploy_lookup.items():
# position to determine potential preceding nodes
op_index = interfaces_deploy_sequence.index(op)
for preceding_op in \

View File

@ -61,7 +61,7 @@ class HotTemplate(object):
all_outputs.update(output.get_dict_output())
dict_output.update({self.OUTPUTS: all_outputs})
yaml_string = yaml.dump(dict_output, default_flow_style=False)
yaml_string = yaml.dump(dict_output)
# get rid of the '' from yaml.dump around numbers
yaml_string = yaml_string.replace('\'', '')
return version_string + desc_str + yaml_string

View File

@ -0,0 +1,32 @@
#
# 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.syntax.hot_resource import HotResource
class ToscaBlockStorage(HotResource):
'''Translate TOSCA node type tosca.nodes.BlockStorage.'''
def __init__(self, nodetemplate):
super(ToscaBlockStorage, self).__init__(nodetemplate,
type='OS::Cinder::Volume')
pass
def handle_properties(self):
tosca_props = {}
for prop in self.nodetemplate.properties:
if isinstance(prop.value, dict):
for x, y in prop.value.items():
if x == 'get_input':
tosca_props[prop.name] = {'get_param': y}
self.properties = tosca_props

View File

@ -0,0 +1,40 @@
#
# 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.syntax.hot_resource import HotResource
class ToscaBlockStorageAttachment(HotResource):
'''Translate TOSCA relationship AttachTo for Compute and BlockStorage.'''
def __init__(self, template, nodetemplates, instace_uuid, volume_id):
super(ToscaBlockStorageAttachment,
self).__init__(template, type='OS::Cinder::VolumeAttachment')
self.nodetemplates = nodetemplates
self.instace_uuid = instace_uuid
self.volume_id = volume_id
def handle_properties(self):
tosca_props = {}
for prop in self.nodetemplate.properties:
if isinstance(prop.value, dict):
for x, y in prop.value.items():
if x == 'get_input':
tosca_props[prop.name] = {'get_param': y}
self.properties = tosca_props
#instance_uuid and volume_id for Cinder volume attachment
self.properties['instance_uuid'] = self.instace_uuid
self.properties['volume_id'] = self.volume_id
def handle_life_cycle(self):
pass

View File

@ -124,8 +124,9 @@ class ToscaCompute(HotResource):
return this_list
matching_flavors = []
for flavor in this_list:
if this_dict[flavor][attr] >= size:
matching_flavors.append(flavor)
if isinstance(size, int):
if this_dict[flavor][attr] >= size:
matching_flavors.append(flavor)
return matching_flavors
def _match_images(self, this_list, this_dict, attr, prop):

View File

@ -65,7 +65,9 @@ class TranslateInputs():
hot_constraints = []
if input.constraints:
for constraint in input.constraints:
constraint_name, value = constraint.iteritems().next()
for name, value in constraint.items():
constraint_name = name
value = value
hc, hvalue = self._translate_constraints(constraint_name,
value)
hot_constraints.append({hc: hvalue})

View File

@ -11,11 +11,16 @@
# License for the specific language governing permissions and limitations
# under the License.
from translator.hot.tosca.tosca_block_storage import ToscaBlockStorage
from translator.hot.tosca.tosca_block_storage_attachment import (
ToscaBlockStorageAttachment
)
from translator.hot.tosca.tosca_compute import ToscaCompute
from translator.hot.tosca.tosca_database import ToscaDatabase
from translator.hot.tosca.tosca_dbms import ToscaDbms
from translator.hot.tosca.tosca_webserver import ToscaWebserver
from translator.hot.tosca.tosca_wordpress import ToscaWordpress
from translator.toscalib.relationship_template import RelationshipTemplate
SECTIONS = (TYPE, PROPERTIES, REQUIREMENTS, INTERFACES, LIFECYCLE, INPUT) = \
('type', 'properties', 'requirements',
@ -39,7 +44,8 @@ TOSCA_TO_HOT_TYPE = {'tosca.nodes.Compute': ToscaCompute,
'tosca.nodes.WebServer': ToscaWebserver,
'tosca.nodes.DBMS': ToscaDbms,
'tosca.nodes.Database': ToscaDatabase,
'tosca.nodes.WebApplication.WordPress': ToscaWordpress}
'tosca.nodes.WebApplication.WordPress': ToscaWordpress,
'tosca.nodes.BlockStorage': ToscaBlockStorage}
TOSCA_TO_HOT_REQUIRES = {'container': 'server', 'host': 'server',
'dependency': 'depends_on', "connects": 'depends_on'}
@ -61,18 +67,39 @@ class TranslateNodeTemplates():
hot_resources = []
hot_lookup = {}
attachment_suffix = 0
# Copy the TOSCA graph: nodetemplate
for node in self.nodetemplates:
hot_node = TOSCA_TO_HOT_TYPE[node.type](node)
hot_resources.append(hot_node)
hot_lookup[node] = hot_node
# Handle life cycle operations: this may expand each node into
# multiple HOT resources and may change their name
# BlockStorage Attachment is a special case,
# which doesn't match to Heat Resources 1 to 1.
if node.type == "tosca.nodes.Compute":
volume_name = None
reuirements = node.requirements
# Find the name of associated BlockStorage node
for requires in reuirements:
for value in requires.values():
for n in self.nodetemplates:
if n.name == value:
volume_name = value
break
attachment_suffix = attachment_suffix + 1
attachment_node = self._get_attachment_node(node,
attachment_suffix,
volume_name)
if attachment_node:
hot_resources.append(attachment_node)
# Handle life cycle operations: this may expand each node
# into multiple HOT resources and may change their name
lifecycle_resources = []
for resource in hot_resources:
expanded = resource.handle_life_cycle()
lifecycle_resources += expanded
if expanded:
lifecycle_resources += expanded
hot_resources += lifecycle_resources
# Copy the initial dependencies based on the relationship in
@ -98,3 +125,22 @@ class TranslateNodeTemplates():
resource.handle_properties()
return hot_resources
def _get_attachment_node(self, node, suffix, volume_name):
attach = False
ntpl = self.nodetemplates
for key, value in node.relationship.items():
if key.type == 'tosca.relationships.AttachTo':
if value.type == 'tosca.nodes.BlockStorage':
attach = True
if attach:
for req in node.requirements:
for rkey, rval in req.items():
if rkey == 'type':
rval = rval + "_" + str(suffix)
att = RelationshipTemplate(req, rval, None)
hot_node = ToscaBlockStorageAttachment(att, ntpl,
node.name,
volume_name
)
return hot_node

View File

53
translator/tests/base.py Normal file
View File

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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 os
import fixtures
import testtools
_TRUE_VALUES = ('True', 'true', '1', 'yes')
class TestCase(testtools.TestCase):
"""Test case base class for all unit tests."""
def setUp(self):
"""Run before each test method to initialize test environment."""
super(TestCase, self).setUp()
test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
try:
test_timeout = int(test_timeout)
except ValueError:
# If timeout value is invalid do not set a timeout.
test_timeout = 0
if test_timeout > 0:
self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
self.useFixture(fixtures.NestedTempfile())
self.useFixture(fixtures.TempHomeDir())
if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES:
stdout = self.useFixture(fixtures.StringStream('stdout')).stream
self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
if os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES:
stderr = self.useFixture(fixtures.StringStream('stderr')).stream
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
self.log_fixture = self.useFixture(fixtures.FakeLogger())

View File

@ -0,0 +1,50 @@
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_server:
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: AttachTo
properties:
location: { get_input: storage_location }
my_storage:
type: tosca.nodes.BlockStorage
properties:
size: { get_input: storage_size }
snapshot_id: { get_input: storage_snapshot_id }
outputs:
public_ip:
description: Public IP address of the newly created compute instance.
value: { get_attr: [server, ip_address] }

View File

@ -0,0 +1,73 @@
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_server:
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: AttachTo
properties:
location: { get_input: storage_location }
my_storage:
type: tosca.nodes.BlockStorage
properties:
size: { get_input: storage_size }
snapshot_id: { get_input: storage_snapshot_id }
my_server2:
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_storage2
type: AttachTo
properties:
location: { get_input: storage_location }
my_storage2:
type: tosca.nodes.BlockStorage
properties:
size: { get_input: storage_size }
snapshot_id: { get_input: storage_snapshot_id }
outputs:
public_ip:
description: Public IP address of the newly created compute instance.
value: { get_attr: [server, ip_address] }

View File

@ -0,0 +1,80 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 os
from translator.hot.tosca_translator import TOSCATranslator
from translator.tests.base import TestCase
from translator.toscalib.tosca_template import ToscaTemplate
import translator.toscalib.utils.yamlparser
class ToscaBlockStorageTest(TestCase):
parsed_params = {'storage_snapshot_id': 'test_id',
'storage_location': '/test', 'cpus': '1',
'storage_size': '1'}
def test_translate_single_storage(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.yaml")
tosca = ToscaTemplate(tosca_tpl)
translate = TOSCATranslator(tosca, self.parsed_params)
output = translate.translate()
expected_resouce = {'attachto_1':
{'type': 'OS::Cinder::VolumeAttachment',
'properties':
{'instance_uuid': 'my_server',
'location': {'get_param': 'storage_location'},
'volume_id': 'my_storage'}}}
output_dict = translator.toscalib.utils.yamlparser.simple_parse(output)
resources = output_dict.get('resources')
translated_value = resources.get('attachto_1')
expected_value = expected_resouce.get('attachto_1')
self.assertEqual(translated_value, expected_value)
def test_translate_multi_storage(self):
'''TOSCA template with multiple BlockStorage and Attachment.'''
tosca_tpl = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"data/tosca_multiple_blockstorage_with_attachment.yaml")
tosca = ToscaTemplate(tosca_tpl)
translated_volume_attachment = []
translate = TOSCATranslator(tosca, self.parsed_params)
output = translate.translate()
expected_resource_1 = {'type': 'OS::Cinder::VolumeAttachment',
'properties':
{'instance_uuid': 'my_server',
'location': {'get_param': 'storage_location'},
'volume_id': 'my_storage'}}
expected_resource_2 = {'type': 'OS::Cinder::VolumeAttachment',
'properties':
{'instance_uuid': 'my_server2',
'location': {'get_param': 'storage_location'},
'volume_id': 'my_storage2'}}
output_dict = translator.toscalib.utils.yamlparser.simple_parse(output)
resources = output_dict.get('resources')
translated_volume_attachment.append(resources.get('attachto_1'))
translated_volume_attachment.append(resources.get('attachto_2'))
self.assertIn(expected_resource_1, translated_volume_attachment)
self.assertIn(expected_resource_2, translated_volume_attachment)

View File

@ -92,6 +92,9 @@ tosca.nodes.Compute:
capabilities:
host:
type: tosca.capabilities.Container
requirements:
- attachment: tosca.nodes.BlockStorage
- type: AttachTo
tosca.nodes.SoftwareComponent:
derived_from: tosca.nodes.Root
@ -169,6 +172,26 @@ tosca.nodes.WebApplication:
requirements:
- host: tosca.nodes.WebServer
tosca.nodes.BlockStorage:
derived_from: tosca.nodes.Root
properties:
size:
type: integer
constraints:
- greater_or_equal: 1
volume_id:
type: string
required: false
snapshot_id:
type: string
required: false
attributes:
volumeId:
type: string
capabilities:
attachment:
type: tosca.capabilities.Attachment
##########################################################################
# Relationship Type.
# A Relationship Type is a reusable entity that defines the type of one
@ -189,6 +212,19 @@ tosca.relationships.ConnectsTo:
derived_from: tosca.relationships.DependsOn
valid_targets: [ tosca.capabilities.Endpoint ]
tosca.relationships.AttachTo:
derived_from: tosca.relationships.Root
valid_targets: [ tosca.capabilities.Attachment ]
properties:
location:
required: true
type: string
constraints:
- min_length: 1
device:
required: false
type: string
##########################################################################
# Capability Type.
# A Capability Type is a reusable entity that describes a kind of
@ -220,6 +256,9 @@ tosca.capabilities.Endpoint:
tosca.capabilities.DatabaseEndpoint:
derived_from: tosca.capabilities.Endpoint
tosca.capabilities.Attachment:
derived_from: tosca.capabilities.Root
##########################################################################
# Interfaces Type.
# The Interfaces element describes a list of one or more interface

View File

@ -13,20 +13,16 @@
# License for the specific language governing permissions and limitations
# under the License.
from translator.toscalib.elements.entitytype import EntityType
from translator.toscalib.elements.property_definition import PropertyDef
from translator.toscalib.elements.statefulentitytype import StatefulEntityType
class CapabilityTypeDef(EntityType):
class CapabilityTypeDef(StatefulEntityType):
'''TOSCA built-in capabilities type.'''
def __init__(self, name, ctype, ntype, properties):
self.name = name
if self.CAPABILITY_PREFIX not in ctype:
ctype = self.CAPABILITY_PREFIX + ctype
if self.NODE_PREFIX not in ntype:
ntype = self.NODE_PREFIX + ntype
self.type = ctype
super(CapabilityTypeDef, self).__init__(ctype, self.CAPABILITY_PREFIX)
self.nodetype = ntype
self.properties = properties
self.defs = {}

View File

@ -23,6 +23,11 @@ log = logging.getLogger('tosca')
class EntityType(object):
'''Base class for TOSCA elements.'''
SECTIONS = (DERIVED_FROM, PROPERTIES, ATTRIBUTES, REQUIREMENTS,
INTERFACES, CAPABILITIES) = \
('derived_from', 'properties', 'attributes', 'requirements',
'interfaces', 'capabilities')
'''TOSCA definition file.'''
TOSCA_DEF_FILE = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
@ -32,10 +37,11 @@ class EntityType(object):
TOSCA_DEF = loader(TOSCA_DEF_FILE)
RELATIONSHIP_TYPE = (DEPENDSON, HOSTEDON, CONNECTSTO) = \
RELATIONSHIP_TYPE = (DEPENDSON, HOSTEDON, CONNECTSTO, ATTACHTO) = \
('tosca.relationships.DependsOn',
'tosca.relationships.HostedOn',
'tosca.relationships.ConnectsTo')
'tosca.relationships.ConnectsTo',
'tosca.relationships.AttachTo')
NODE_PREFIX = 'tosca.nodes.'
RELATIONSHIP_PREFIX = 'tosca.relationships.'
@ -49,3 +55,21 @@ class EntityType(object):
def entity_value(self, defs, key):
if key in defs:
return defs[key]
def get_value(self, ndtype, defs=None, parent=None):
value = None
if defs is None:
defs = self.defs
if ndtype in defs:
value = defs[ndtype]
if parent and not value:
p = self.parent_type
while value is None:
#check parent node
if not p:
break
if p and p.type == 'tosca.nodes.Root':
break
value = p.get_value(ndtype)
p = p.parent_type
return value

View File

@ -49,7 +49,7 @@ class InterfacesDef(StatefulEntityType):
elif i == 'input':
self.input = self._create_input_functions(j)
else:
what = ('Interfaces of node template %s' %
what = ('Interfaces of template %s' %
self.node_template.name)
raise UnknownFieldError(what=what, field=i)
else:

View File

@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from translator.toscalib.common.exception import InvalidNodeTypeError
from translator.toscalib.elements.attribute_definition import AttributeDef
from translator.toscalib.elements.capabilitytype import CapabilityTypeDef
from translator.toscalib.elements.interfaces import InterfacesDef
@ -22,26 +21,11 @@ from translator.toscalib.elements.relationshiptype import RelationshipType
from translator.toscalib.elements.statefulentitytype import StatefulEntityType
SECTIONS = (DERIVED_FROM, PROPERTIES, ATTRIBUTES, REQUIREMENTS,
INTERFACES, CAPABILITIES) = \
('derived_from', 'properties', 'attributes', 'requirements',
'interfaces', 'capabilities')
class NodeType(StatefulEntityType):
'''TOSCA built-in node type.'''
def __init__(self, ntype, custom_def=None):
super(NodeType, self).__init__()
if self.NODE_PREFIX not in ntype:
ntype = self.NODE_PREFIX + ntype
if ntype in list(self.TOSCA_DEF.keys()):
self.defs = self.TOSCA_DEF[ntype]
elif custom_def and ntype in list(custom_def.keys()):
self.defs = custom_def[ntype]
else:
raise InvalidNodeTypeError(what=ntype)
self.type = ntype
super(NodeType, self).__init__(ntype, self.NODE_PREFIX, custom_def)
self.related = {}
@property
@ -55,7 +39,7 @@ class NodeType(StatefulEntityType):
def properties_def(self):
'''Return a list of property definition objects.'''
properties = []
props = self.get_value(PROPERTIES)
props = self.get_value(self.PROPERTIES)
if props:
for prop, schema in props.items():
properties.append(PropertyDef(prop, None, schema))
@ -64,7 +48,7 @@ class NodeType(StatefulEntityType):
@property
def attributes_def(self):
'''Return a list of attribute definition objects.'''
attrs = self.get_value(ATTRIBUTES)
attrs = self.get_value(self.ATTRIBUTES)
if attrs:
return [AttributeDef(attr, None, schema)
for attr, schema in attrs.items()]
@ -84,6 +68,8 @@ class NodeType(StatefulEntityType):
if requires:
for req in requires:
for key, value in req.items():
if key == 'type':
continue
relation = self._get_relation(key, value)
rtype = RelationshipType(relation, key)
relatednode = NodeType(value)
@ -112,9 +98,9 @@ class NodeType(StatefulEntityType):
typecapabilities = []
self.cap_prop = None
self.cap_type = None
caps = self.get_value(CAPABILITIES)
caps = self.get_value(self.CAPABILITIES)
if caps is None:
caps = self.get_value(CAPABILITIES, None, True)
caps = self.get_value(self.CAPABILITIES, None, True)
if caps:
cproperties = None
for name, value in caps.items():
@ -128,24 +114,24 @@ class NodeType(StatefulEntityType):
@property
def requirements(self):
return self.get_value(REQUIREMENTS)
return self.get_value(self.REQUIREMENTS)
def get_all_requirements(self):
requires = self.requirements
parent_node = self.parent_type
if requires is None:
requires = self.get_value(REQUIREMENTS, None, True)
requires = self.get_value(self.REQUIREMENTS, None, True)
parent_node = parent_node.parent_type
if parent_node:
while parent_node.type != 'tosca.nodes.Root':
req = parent_node.get_value(REQUIREMENTS, None, True)
req = parent_node.get_value(self.REQUIREMENTS, None, True)
requires.extend(req)
parent_node = parent_node.parent_type
return requires
@property
def interfaces(self):
return self.get_value(INTERFACES)
return self.get_value(self.INTERFACES)
@property
def lifecycle_inputs(self):
@ -180,21 +166,3 @@ class NodeType(StatefulEntityType):
for key, value in self.get_capability(name):
if key == type:
return value
def get_value(self, ndtype, defs=None, parent=None):
value = None
if defs is None:
defs = self.defs
if ndtype in defs:
value = defs[ndtype]
if parent and not value:
p = self.parent_type
while value is None:
#check parent node
if not p:
break
if p and p.type == 'tosca.nodes.Root':
break
value = p.get_value(ndtype)
p = p.parent_type
return value

View File

@ -13,18 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
from translator.toscalib.elements.property_definition import PropertyDef
from translator.toscalib.elements.statefulentitytype import StatefulEntityType
class RelationshipType(StatefulEntityType):
'''TOSCA built-in relationship type.'''
def __init__(self, type, capability_name):
super(RelationshipType, self).__init__()
self.defs = self.TOSCA_DEF[type]
if self.RELATIONSHIP_PREFIX not in type:
type = self.RELATIONSHIP_PREFIX + type
self.type = type
def __init__(self, type, capability_name, custom_def=None):
super(RelationshipType, self).__init__(type, self.RELATIONSHIP_PREFIX,
custom_def)
self.capability_name = capability_name
@property
@ -35,3 +32,13 @@ class RelationshipType(StatefulEntityType):
def parent_type(self):
'''Return a relationship this relationship is derived from.'''
return self.derived_from(self.defs)
@property
def properties_def(self):
'''Return a list of property definition objects.'''
properties = []
props = self.get_value(self.PROPERTIES)
if props:
for prop, schema in props.items():
properties.append(PropertyDef(prop, None, schema))
return properties

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from translator.toscalib.common.exception import InvalidNodeTypeError
from translator.toscalib.elements.entitytype import EntityType
@ -27,3 +28,14 @@ class StatefulEntityType(EntityType):
'post_configure_target',
'add_target',
'remove_target']
def __init__(self, entitytype, prefix, custom_def=None):
if prefix not in entitytype:
entitytype = prefix + entitytype
if entitytype in list(self.TOSCA_DEF.keys()):
self.defs = self.TOSCA_DEF[entitytype]
elif custom_def and entitytype in list(custom_def.keys()):
self.defs = custom_def[entitytype]
else:
raise InvalidNodeTypeError(what=entitytype)
self.type = entitytype

View File

@ -0,0 +1,157 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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.toscalib.common.exception import MissingRequiredFieldError
from translator.toscalib.common.exception import UnknownFieldError
from translator.toscalib.elements.capabilitytype import CapabilityTypeDef
from translator.toscalib.elements.interfaces import InterfacesDef
from translator.toscalib.elements.nodetype import NodeType
from translator.toscalib.elements.relationshiptype import RelationshipType
from translator.toscalib.properties import Property
class EntityTemplate(object):
'''Base class for TOSCA templates.'''
SECTIONS = (DERIVED_FROM, PROPERTIES, REQUIREMENTS,
INTERFACES, CAPABILITIES, TYPE) = \
('derived_from', 'properties', 'requirements', 'interfaces',
'capabilities', 'type')
def __init__(self, name, template, entity_name, custom_def=None):
self.name = name
self.entity_tpl = template
self.custom_def = custom_def
self._validate_field(self.entity_tpl)
if entity_name == 'node_type':
self.type_definition = NodeType(self.entity_tpl['type'],
custom_def)
if entity_name == 'relationship_type':
self.type_definition = RelationshipType(self.entity_tpl['type'],
custom_def)
@property
def type(self):
return self.type_definition.type
@property
def requirements(self):
return self.type_definition.get_value(self.REQUIREMENTS,
self.entity_tpl)
@property
def properties(self):
props = []
properties = self.type_definition.get_value(self.PROPERTIES,
self.entity_tpl)
if properties:
for name, value in properties.items():
for p in self.type_definition.properties_def:
if p.name == name:
prop = Property(name, value, p.schema)
props.append(prop)
return props
@property
def interfaces(self):
interfaces = []
type_interfaces = self.type_definition.get_value(self.INTERFACES,
self.entity_tpl)
if type_interfaces:
for interface_type, value in type_interfaces.items():
for op, op_def in value.items():
iface = InterfacesDef(self.type_definition,
interfacetype=interface_type,
node_template=self,
name=op,
value=op_def)
interfaces.append(iface)
return interfaces
@property
def capabilities(self):
capability = []
properties = {}
cap_type = None
caps = self.type_definition.get_value(self.CAPABILITIES,
self.entity_tpl)
if caps:
for name, value in caps.items():
for prop, val in value.items():
properties = val
for c in self.type_definition.capabilities:
if c.name == name:
cap_type = c.type
cap = CapabilityTypeDef(name, cap_type,
self.name, properties)
capability.append(cap)
return capability
def _validate_properties(self, template, entitytype):
properties = entitytype.get_value(self.PROPERTIES, template)
allowed_props = []
required_props = []
for p in entitytype.properties_def:
allowed_props.append(p.name)
if p.required:
required_props.append(p.name)
if properties:
self._common_validate_field(properties, allowed_props,
'Properties')
#make sure it's not missing any property required by a tosca type
missingprop = []
for r in required_props:
if r not in properties.keys():
missingprop.append(r)
if missingprop:
raise MissingRequiredFieldError(
what='Properties of template %s' % self.name,
required=missingprop)
else:
if required_props:
raise MissingRequiredFieldError(
what='Properties of template %s' % self.name,
required=missingprop)
def _validate_capabilities(self):
type_capabilities = self.type_definition.capabilities
allowed_caps = []
if type_capabilities:
for tcap in type_capabilities:
allowed_caps.append(tcap.name)
capabilities = self.type_definition.get_value(self.CAPABILITIES,
self.entity_tpl)
if capabilities:
self._common_validate_field(capabilities, allowed_caps,
'Capabilities')
def _validate_field(self, template):
if not isinstance(template, dict):
raise MissingRequiredFieldError(
what='Template %s' % self.name, required=self.TYPE)
try:
template[self.TYPE]
except KeyError:
raise MissingRequiredFieldError(
what='Template %s' % self.name, required=self.TYPE)
def _common_validate_field(self, schema, allowedlist, section):
for name in schema:
if name not in allowedlist:
raise UnknownFieldError(
what='%(section)s of template %(nodename)s'
% {'section': section, 'nodename': self.name},
field=name)

View File

@ -76,7 +76,7 @@ class GetRefProperty(Function):
if name:
from translator.toscalib.nodetemplate import NodeTemplate
tpl = NodeTemplate(
name, self.node_template.node_templates)
name, self.node_template.templates)
caps = tpl.capabilities
required_cap = self.args[1]
required_property = self.args[2]

View File

@ -16,42 +16,24 @@
import logging
from translator.toscalib.common.exception import MissingRequiredFieldError
from translator.toscalib.common.exception import TypeMismatchError
from translator.toscalib.common.exception import UnknownFieldError
from translator.toscalib.elements.capabilitytype import CapabilityTypeDef
from translator.toscalib.elements.interfaces import InterfacesDef
from translator.toscalib.elements.interfaces import LIFECYCLE, CONFIGURE
from translator.toscalib.elements.nodetype import NodeType
from translator.toscalib.properties import Property
SECTIONS = (DERIVED_FROM, PROPERTIES, REQUIREMENTS,
INTERFACES, CAPABILITIES, TYPE) = \
('derived_from', 'properties', 'requirements', 'interfaces',
'capabilities', 'type')
from translator.toscalib.entity_template import EntityTemplate
log = logging.getLogger('tosca')
class NodeTemplate(object):
class NodeTemplate(EntityTemplate):
'''Node template from a Tosca profile.'''
def __init__(self, name, node_templates, custom_def=None):
self.name = name
self.node_templates = node_templates
self._validate_field()
self.node_template = node_templates[self.name]
self.node_type = NodeType(self.node_template[TYPE], custom_def)
super(NodeTemplate, self).__init__(name, node_templates[name],
'node_type',
custom_def)
self.templates = node_templates
self.related = {}
@property
def type(self):
return self.node_type.type
@property
def requirements(self):
return self.node_type.get_value(REQUIREMENTS, self.node_template)
@property
def relationship(self):
relation = {}
@ -59,72 +41,26 @@ class NodeTemplate(object):
if requires:
for r in requires:
for cap, node in r.items():
for rtype in self.node_type.relationship.keys():
for rtype in self.type_definition.relationship.keys():
if cap == rtype.capability_name:
rtpl = NodeTemplate(node, self.node_templates)
rtpl = NodeTemplate(node, self.templates)
relation[rtype] = rtpl
return relation
@property
def capabilities(self):
capability = []
properties = {}
cap_type = None
caps = self.node_type.get_value(CAPABILITIES, self.node_template)
if caps:
for name, value in caps.items():
for prop, val in value.items():
properties = val
for c in self.node_type.capabilities:
if c.name == name:
cap_type = c.type
cap = CapabilityTypeDef(name, cap_type,
self.name, properties)
capability.append(cap)
return capability
@property
def interfaces(self):
interfaces = []
type_interfaces = self.node_type.get_value(INTERFACES,
self.node_template)
if type_interfaces:
for interface_type, value in type_interfaces.items():
for op, op_def in value.items():
iface = InterfacesDef(self.node_type,
interfacetype=interface_type,
node_template=self,
name=op,
value=op_def)
interfaces.append(iface)
return interfaces
@property
def properties(self):
props = []
properties = self.node_type.get_value(PROPERTIES, self.node_template)
if properties:
for name, value in properties.items():
for p in self.node_type.properties_def:
if p.name == name:
prop = Property(name, value, p.schema)
props.append(prop)
return props
def _add_next(self, nodetpl, relationship):
self.related[nodetpl] = relationship
@property
def related_nodes(self):
if not self.related:
for relation, node in self.node_type.relationship.items():
for relation, node in self.type_definition.relationship.items():
for tpl in self.node_templates:
if tpl == node.type:
self.related[NodeTemplate(tpl)] = relation
return self.related.keys()
def ref_property(self, cap, cap_name, property):
requires = self.node_type.requirements
requires = self.type_definition.requirements
name = None
if requires:
for r in requires:
@ -142,42 +78,32 @@ class NodeTemplate(object):
def validate(self):
self._validate_capabilities()
self._validate_requirments()
self._validate_properties()
self._validate_requirements()
self._validate_properties(self.entity_tpl, self.type_definition)
self._validate_interfaces()
for prop in self.properties:
prop.validate()
def _validate_capabilities(self):
type_capabilities = self.node_type.capabilities
allowed_caps = []
if type_capabilities:
for tcap in type_capabilities:
allowed_caps.append(tcap.name)
capabilities = self.node_type.get_value(CAPABILITIES,
self.node_template)
if capabilities:
self._common_validate_field(capabilities, allowed_caps,
'Capabilities')
def _validate_requirments(self):
type_requires = self.node_type.get_all_requirements()
allowed_reqs = []
def _validate_requirements(self):
type_requires = self.type_definition.get_all_requirements()
allowed_reqs = ['type', 'properties', 'interfaces']
if type_requires:
for treq in type_requires:
for key in treq:
allowed_reqs.append(key)
requires = self.node_type.get_value(REQUIREMENTS, self.node_template)
requires = self.type_definition.get_value(self.REQUIREMENTS,
self.entity_tpl)
if requires:
if not isinstance(requires, list):
raise TypeMismatchError(
what='Requirements of node template %s' % self.name,
what='Requirements of template %s' % self.name,
type='list')
for req in requires:
self._common_validate_field(req, allowed_reqs, 'Requirements')
def _validate_interfaces(self):
ifaces = self.node_type.get_value(INTERFACES, self.node_template)
ifaces = self.type_definition.get_value(self.INTERFACES,
self.entity_tpl)
if ifaces:
for i in ifaces:
for name, value in ifaces.items():
@ -193,51 +119,5 @@ class NodeTemplate(object):
'Interfaces')
else:
raise UnknownFieldError(
what='Interfaces of node template %s' % self.name,
what='Interfaces of template %s' % self.name,
field=name)
def _validate_properties(self):
properties = self.node_type.get_value(PROPERTIES, self.node_template)
allowed_props = []
required_props = []
for p in self.node_type.properties_def:
allowed_props.append(p.name)
if p.required:
required_props.append(p.name)
if properties:
self._common_validate_field(properties, allowed_props,
'Properties')
#make sure it's not missing any property required by a node type
missingprop = []
for r in required_props:
if r not in properties.keys():
missingprop.append(r)
if missingprop:
raise MissingRequiredFieldError(
what='Properties of node template %s' % self.name,
required=missingprop)
else:
if required_props:
raise MissingRequiredFieldError(
what='Properties of node template %s' % self.name,
required=missingprop)
def _validate_field(self):
if not isinstance(self.node_templates[self.name], dict):
raise MissingRequiredFieldError(
what='Node template %s' % self.name, required=TYPE)
try:
self.node_templates[self.name][TYPE]
except KeyError:
raise MissingRequiredFieldError(
what='Node template %s' % self.name, required=TYPE)
self._common_validate_field(self.node_templates[self.name], SECTIONS,
'Second level')
def _common_validate_field(self, schema, allowedlist, section):
for name in schema:
if name not in allowedlist:
raise UnknownFieldError(
what='%(section)s of node template %(nodename)s'
% {'section': section, 'nodename': self.name},
field=name)

View File

@ -0,0 +1,39 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 translator.toscalib.entity_template import EntityTemplate
SECTIONS = (DERIVED_FROM, PROPERTIES, REQUIREMENTS,
INTERFACES, CAPABILITIES, TYPE) = \
('derived_from', 'properties', 'requirements', 'interfaces',
'capabilities', 'type')
log = logging.getLogger('tosca')
class RelationshipTemplate(EntityTemplate):
'''Relationship template.'''
def __init__(self, relationship_template, name, custom_def=None):
super(RelationshipTemplate, self).__init__(name,
relationship_template,
'relationship_type',
custom_def=None)
self.name = name.lower()
def validate(self):
self._validate_properties(self.entity_tpl, self.type_definition)

View File

@ -49,7 +49,6 @@ class ToscaDefTest(TestCase):
sorted([p.name for p in compute_type.attributes_def]))
def test_requirements(self):
self.assertEqual(compute_type.requirements, None)
self.assertEqual(
[{'host': 'tosca.nodes.Compute'}],
[r for r in component_type.requirements])

View File

@ -14,12 +14,15 @@
# under the License.
import os
import six
from translator.toscalib.common.exception import InvalidNodeTypeError
from translator.toscalib.common.exception import MissingRequiredFieldError
from translator.toscalib.common.exception import TypeMismatchError
from translator.toscalib.common.exception import UnknownFieldError
from translator.toscalib.nodetemplate import NodeTemplate
from translator.toscalib.parameters import Input, Output
from translator.toscalib.relationship_template import RelationshipTemplate
from translator.toscalib.tests.base import TestCase
from translator.toscalib.tosca_template import ToscaTemplate
import translator.toscalib.utils.yamlparser
@ -156,6 +159,7 @@ class ToscaTemplateValidationTest(TestCase):
nodetemplate.capabilities
nodetemplate.properties
nodetemplate.interfaces
except Exception as err:
self.assertTrue(isinstance(err, expectederror))
self.assertEqual(expectedmessage, err.__str__())
@ -175,7 +179,7 @@ class ToscaTemplateValidationTest(TestCase):
os_distribution: Fedora
os_version: 18
'''
expectedmessage = ('Node template server is missing '
expectedmessage = ('Template server is missing '
'required field: "type".')
self._single_node_template_content_test(tpl_snippet,
MissingRequiredFieldError,
@ -200,7 +204,7 @@ class ToscaTemplateValidationTest(TestCase):
db_root_password: { get_property: [ mysql_dbms, \
dbms_root_password ] }
'''
expectedmessage = ('Second level of node template mysql_dbms '
expectedmessage = ('Second level of template mysql_dbms '
'contain(s) unknown field: "requirement", '
'refer to the TOSCA specs to verify valid values.')
self._single_node_template_content_test(tpl_snippet,
@ -244,7 +248,7 @@ class ToscaTemplateValidationTest(TestCase):
create: webserver_install.sh
start: webserver_start.sh
'''
expectedmessage = ('Requirements of node template webserver '
expectedmessage = ('Requirements of template webserver '
'must be of type: "list".')
self._single_node_template_content_test(tpl_snippet,
TypeMismatchError,
@ -269,7 +273,7 @@ class ToscaTemplateValidationTest(TestCase):
tosca.interfaces.node.Lifecycle:
configure: mysql_database_configure.sh
'''
expectedmessage = ('Requirements of node template mysql_database '
expectedmessage = ('Requirements of template mysql_database '
'contain(s) unknown field: "database_endpoint", '
'refer to the TOSCA specs to verify valid values.')
self._single_node_template_content_test(tpl_snippet,
@ -295,7 +299,7 @@ class ToscaTemplateValidationTest(TestCase):
tosca.interfaces.node.Lifecycle:
configure: mysql_database_configure.sh
'''
expectedmessage = ('Capabilities of node template mysql_database '
expectedmessage = ('Capabilities of template mysql_database '
'contain(s) unknown field: "http_endpoint", '
'refer to the TOSCA specs to verify valid values.')
self._single_node_template_content_test(tpl_snippet,
@ -317,7 +321,7 @@ class ToscaTemplateValidationTest(TestCase):
os_distribution: Fedora
os_version: 18
'''
expectedmessage = ('Properties of node template server is missing '
expectedmessage = ('Properties of template server is missing '
'required field: "[\'os_type\']".')
self._single_node_template_content_test(tpl_snippet,
MissingRequiredFieldError,
@ -339,7 +343,7 @@ class ToscaTemplateValidationTest(TestCase):
os_version: 18
os_image: F18_x86_64
'''
expectedmessage = ('Properties of node template server contain(s) '
expectedmessage = ('Properties of template server contain(s) '
'unknown field: "os_image", refer to the TOSCA '
'specs to verify valid values.')
self._single_node_template_content_test(tpl_snippet,
@ -367,7 +371,7 @@ class ToscaTemplateValidationTest(TestCase):
wp_db_port: { get_ref_property: [ database_endpoint, \
database_endpoint, port ] }
'''
expectedmessage = ('Interfaces of node template wordpress '
expectedmessage = ('Interfaces of template wordpress '
'contain(s) unknown field: '
'"tosca.interfaces.node.Lifecycles", '
'refer to the TOSCA specs to verify valid values.')
@ -395,7 +399,7 @@ class ToscaTemplateValidationTest(TestCase):
wp_db_port: { get_ref_property: [ database_endpoint, \
database_endpoint, port ] }
'''
expectedmessage = ('Interfaces of node template wordpress contain(s) '
expectedmessage = ('Interfaces of template wordpress contain(s) '
'unknown field: "config", refer to the TOSCA specs'
' to verify valid values.')
self._single_node_template_content_test(tpl_snippet,
@ -422,9 +426,33 @@ class ToscaTemplateValidationTest(TestCase):
wp_db_port: { get_ref_property: [ database_endpoint, \
database_endpoint, port ] }
'''
expectedmessage = ('Interfaces of node template wordpress contain(s) '
expectedmessage = ('Interfaces of template wordpress contain(s) '
'unknown field: "inputs", refer to the TOSCA specs'
' to verify valid values.')
self._single_node_template_content_test(tpl_snippet,
UnknownFieldError,
expectedmessage)
def test_relationship_template_properties(self):
tpl_snippet = '''
relationship_templates:
storage_attachto:
type: AttachTo
properties:
device: test_device
'''
expectedmessage = ('Properties of template '
'storage_attachto is missing required field: '
'"[\'location\']".')
self._single_rel_template_content_test(tpl_snippet,
MissingRequiredFieldError,
expectedmessage)
def _single_rel_template_content_test(self, tpl_snippet, expectederror,
expectedmessage):
rel_template = (translator.toscalib.utils.yamlparser.
simple_parse(tpl_snippet))['relationship_templates']
name = list(rel_template.keys())[0]
rel_template = RelationshipTemplate(rel_template[name], name)
err = self.assertRaises(expectederror, rel_template.validate)
self.assertEqual(expectedmessage, six.text_type(err))