heat-translator/translator/hot/translate_node_templates.py
Ton Ngo 908e1fa120 Add handler for ConnectsTo relationship
The ConnectsTo relationship in a node can have an interface that
contains the Configure relationship.  This interface would have
a lifecycle operation associated with it, something like a
configuration script requiring some input parameters.
In the Heat template, each of these lifecycle operation would
be implemented as a software config node.
This patch adds the handler in the main translation loop to
process the ConnectsTo relationship.

Partially Implements: blueprint translate-tosca-monitoring-usecase
Change-Id: I5ca4b935cc8189ac46dca0d24a9aa8b40133928c
2015-06-22 11:40:14 -07:00

346 lines
16 KiB
Python

#
# 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 six
from translator.hot.syntax.hot_resource import HotResource
from translator.hot.tosca.tosca_block_storage import ToscaBlockStorage
from translator.hot.tosca.tosca_block_storage_attachment import (
ToscaBlockStorageAttachment
)
from translator.hot.tosca.tosca_collectd import ToscaCollectd
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_elasticsearch import ToscaElasticsearch
from translator.hot.tosca.tosca_kibana import ToscaKibana
from translator.hot.tosca.tosca_logstash import ToscaLogstash
from translator.hot.tosca.tosca_network_network import ToscaNetwork
from translator.hot.tosca.tosca_network_port import ToscaNetworkPort
from translator.hot.tosca.tosca_nodejs import ToscaNodejs
from translator.hot.tosca.tosca_object_storage import ToscaObjectStorage
from translator.hot.tosca.tosca_paypalpizzastore import ToscaPaypalPizzaStore
from translator.hot.tosca.tosca_rsyslog import ToscaRsyslog
from translator.hot.tosca.tosca_webserver import ToscaWebserver
from translator.hot.tosca.tosca_wordpress import ToscaWordpress
from translator.toscalib.functions import GetAttribute
from translator.toscalib.functions import GetInput
from translator.toscalib.functions import GetProperty
from translator.toscalib.relationship_template import RelationshipTemplate
SECTIONS = (TYPE, PROPERTIES, REQUIREMENTS, INTERFACES, LIFECYCLE, INPUT) = \
('type', 'properties', 'requirements',
'interfaces', 'lifecycle', 'input')
# TODO(anyone): the following requirement names should not be hard-coded
# in the translator. Since they are basically arbitrary names, we have to get
# them from TOSCA type definitions.
# To be fixed with the blueprint:
# https://blueprints.launchpad.net/heat-translator/+spec/tosca-custom-types
REQUIRES = (CONTAINER, DEPENDENCY, DATABASE_ENDPOINT, CONNECTION, HOST) = \
('container', 'dependency', 'database_endpoint',
'connection', 'host')
INTERFACES_STATE = (CREATE, START, CONFIGURE, START, DELETE) = \
('create', 'stop', 'configure', 'start', 'delete')
# dict to look up HOT translation class,
# TODO(replace with function to scan the classes in translator.hot.tosca)
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.BlockStorage': ToscaBlockStorage,
'tosca.nodes.SoftwareComponent.Nodejs': ToscaNodejs,
'tosca.nodes.network.Network': ToscaNetwork,
'tosca.nodes.network.Port': ToscaNetworkPort,
'tosca.nodes.ObjectStorage': ToscaObjectStorage,
'tosca.nodes.SoftwareComponent.Collectd': ToscaCollectd,
'tosca.nodes.SoftwareComponent.Rsyslog': ToscaRsyslog,
'tosca.nodes.SoftwareComponent.Kibana': ToscaKibana,
'tosca.nodes.SoftwareComponent.Logstash': ToscaLogstash,
'tosca.nodes.SoftwareComponent.Elasticsearch':
ToscaElasticsearch,
'tosca.nodes.WebApplication.PayPalPizzaStore':
ToscaPaypalPizzaStore}
TOSCA_TO_HOT_REQUIRES = {'container': 'server', 'host': 'server',
'dependency': 'depends_on', "connects": 'depends_on'}
TOSCA_TO_HOT_PROPERTIES = {'properties': 'input'}
class TranslateNodeTemplates(object):
'''Translate TOSCA NodeTemplates to Heat Resources.'''
def __init__(self, tosca, hot_template):
self.tosca = tosca
self.nodetemplates = self.tosca.nodetemplates
self.hot_template = hot_template
# list of all HOT resources generated
self.hot_resources = []
# mapping between TOSCA nodetemplate and HOT resource
self.hot_lookup = {}
def translate(self):
return self._translate_nodetemplates()
def _recursive_handle_properties(self, resource):
'''Recursively handle the properties of the depends_on_nodes nodes.'''
# Use of hashtable (dict) here should be faster?
if resource in self.processed_resources:
return
self.processed_resources.append(resource)
for depend_on in resource.depends_on_nodes:
self._recursive_handle_properties(depend_on)
resource.handle_properties()
def _translate_nodetemplates(self):
suffix = 0
# Copy the TOSCA graph: nodetemplate
for node in self.nodetemplates:
hot_node = TOSCA_TO_HOT_TYPE[node.type](node)
self.hot_resources.append(hot_node)
self.hot_lookup[node] = hot_node
# 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
requirements = node.requirements
if requirements:
# Find the name of associated BlockStorage node
for requires in requirements:
for value in requires.values():
if isinstance(value, dict):
for node_name in value.values():
for n in self.nodetemplates:
if n.name == node_name:
volume_name = node_name
break
else: # unreachable code !
for n in self.nodetemplates:
if n.name == node_name:
volume_name = node_name
break
suffix = suffix + 1
attachment_node = self._get_attachment_node(node,
suffix,
volume_name)
if attachment_node:
self.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 self.hot_resources:
expanded = resource.handle_life_cycle()
if expanded:
lifecycle_resources += expanded
self.hot_resources += lifecycle_resources
# Handle configuration from ConnectsTo relationship in the TOSCA node:
# this will generate multiple HOT resources, set of 2 for each
# configuration
connectsto_resources = []
for node in self.nodetemplates:
for requirement in node.requirements:
for endpoint, details in six.iteritems(requirement):
target = details.get('node')
relation = details.get('relationship')
if (target and relation and
not isinstance(relation, six.string_types)):
interfaces = relation.get('interfaces')
connectsto_resources += \
self._create_connect_configs(node,
target,
interfaces)
self.hot_resources += connectsto_resources
# Copy the initial dependencies based on the relationship in
# the TOSCA template
for node in self.nodetemplates:
for node_depend in node.related_nodes:
# if the source of dependency is a server and the
# relationship type is 'tosca.relationships.HostedOn',
# add dependency as properties.server
if node_depend.type == 'tosca.nodes.Compute' and \
node.related[node_depend].type == \
node.type_definition.HOSTEDON:
self.hot_lookup[node].properties['server'] = \
{'get_resource': self.hot_lookup[node_depend].name}
# for all others, add dependency as depends_on
else:
self.hot_lookup[node].depends_on.append(
self.hot_lookup[node_depend].top_of_chain())
self.hot_lookup[node].depends_on_nodes.append(
self.hot_lookup[node_depend].top_of_chain())
# handle hosting relationship
for resource in self.hot_resources:
resource.handle_hosting()
# handle built-in properties of HOT resources
# if a resource depends on other resources,
# their properties need to be handled first.
# Use recursion to handle the properties of the
# dependent nodes in correct order
self.processed_resources = []
for resource in self.hot_resources:
self._recursive_handle_properties(resource)
# handle resources that need to expand to more than one HOT resource
expansion_resources = []
for resource in self.hot_resources:
expanded = resource.handle_expansion()
if expanded:
expansion_resources += expanded
self.hot_resources += expansion_resources
# Resolve function calls: GetProperty, GetAttribute, GetInput
# at this point, all the HOT resources should have been created
# in the graph.
for resource in self.hot_resources:
# traverse the reference chain to get the actual value
inputs = resource.properties.get('input_values')
if inputs:
for name, value in six.iteritems(inputs):
if isinstance(value, GetAttribute):
# for the attribute
# get the proper target type to perform the translation
args = value.result()
target = args[0]
hot_target = self.find_hot_resource(target)
inputs[name] = hot_target.get_hot_attribute(args[1],
args)
else:
if isinstance(value, GetProperty) or \
isinstance(value, GetInput):
inputs[name] = value.result()
return self.hot_resources
def _get_attachment_node(self, node, suffix, volume_name):
attach = False
ntpl = self.nodetemplates
for key, value in node.relationships.items():
if key.type == 'tosca.relationships.AttachesTo':
if value.type == 'tosca.nodes.BlockStorage':
attach = True
if attach:
relationship_tpl = None
for req in node.requirements:
for key, val in req.items():
attach = val
for rkey, rval in val.items():
relship = val.get('relationship')
if relship and isinstance(relship, dict):
for rkey, rval in relship.items():
if rkey == 'type':
relationship_tpl = val
attach = rval
elif rkey == 'template':
rel_tpl_list = \
(self.tosca.topology_template.
_tpl_relationship_templates())
relationship_tpl = rel_tpl_list[rval]
attach = rval
else:
continue
elif isinstance(relship, str):
attach = relship
relationship_tpl = val
break
if relationship_tpl:
rval_new = attach + "_" + str(suffix)
att = RelationshipTemplate(
relationship_tpl, rval_new,
self.tosca._tpl_relationship_types())
hot_node = ToscaBlockStorageAttachment(att, ntpl,
node.name,
volume_name
)
return hot_node
def find_hot_resource(self, name):
for resource in self.hot_resources:
if resource.name == name:
return resource
def _find_tosca_node(self, tosca_name):
for node in self.nodetemplates:
if node.name == tosca_name:
return node
def _find_hot_resource_for_tosca(self, tosca_name):
for node in self.nodetemplates:
if node.name == tosca_name:
return self.hot_lookup[node]
def _create_connect_configs(self, source_node, target_name,
connect_interfaces):
connectsto_resources = []
if connect_interfaces:
for iname, interface in six.iteritems(connect_interfaces):
connectsto_resources += \
self._create_connect_config(source_node, target_name,
interface)
return connectsto_resources
def _create_connect_config(self, source_node, target_name,
connect_interface):
connectsto_resources = []
target_node = self._find_tosca_node(target_name)
# the configuration can occur on the source or the target
connect_config = connect_interface.get('pre_configure_target')
if connect_config is not None:
config_location = 'target'
else:
connect_config = connect_interface.get('pre_configure_source')
if connect_config is not None:
config_location = 'source'
else:
raise Exception(_("Template error: "
"no configuration found for ConnectsTo "
"in {1}").format(self.nodetemplate.name))
config_name = source_node.name + '_' + target_name + '_connect_config'
implement = connect_config.get('implementation')
if config_location == 'target':
hot_config = HotResource(target_node,
config_name,
'OS::Heat::SoftwareConfig',
{'config': {'get_file': implement}})
elif config_location == 'source':
hot_config = HotResource(source_node,
config_name,
'OS::Heat::SoftwareConfig',
{'config': {'get_file': implement}})
connectsto_resources.append(hot_config)
hot_target = self._find_hot_resource_for_tosca(target_name)
hot_source = self._find_hot_resource_for_tosca(source_node.name)
connectsto_resources.append(hot_config.
handle_connectsto(source_node,
target_node,
hot_source,
hot_target,
config_location,
connect_interface))
return connectsto_resources