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:
parent
f2ba5fa317
commit
ccefa3514e
@ -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 \
|
||||
|
@ -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
|
||||
|
32
translator/hot/tosca/tosca_block_storage.py
Normal file
32
translator/hot/tosca/tosca_block_storage.py
Normal 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
|
40
translator/hot/tosca/tosca_block_storage_attachment.py
Normal file
40
translator/hot/tosca/tosca_block_storage_attachment.py
Normal 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
|
@ -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):
|
||||
|
@ -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})
|
||||
|
@ -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
|
||||
|
0
translator/tests/__init__.py
Normal file
0
translator/tests/__init__.py
Normal file
53
translator/tests/base.py
Normal file
53
translator/tests/base.py
Normal 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())
|
@ -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] }
|
@ -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] }
|
80
translator/tests/test_blockstorage.py
Normal file
80
translator/tests/test_blockstorage.py
Normal 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)
|
@ -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
|
||||
|
@ -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 = {}
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
157
translator/toscalib/entity_template.py
Normal file
157
translator/toscalib/entity_template.py
Normal 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)
|
@ -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]
|
||||
|
@ -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)
|
||||
|
39
translator/toscalib/relationship_template.py
Normal file
39
translator/toscalib/relationship_template.py
Normal 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)
|
@ -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])
|
||||
|
@ -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))
|
||||
|
Loading…
x
Reference in New Issue
Block a user