Main translation code to handle parameters

Changes to the backend Heat generator to process the parameters
from the TOSCA input section, which would be passed in from the
CLI or accept the default value.  Thi also handles the similar
get_attribute function in the output section.
Add handler for the nodejs type, needed for the monitoring use
case.  Fix the test_blockstorage unit test to match the new
expected generator output, due to a change in the Heat Nova
server resource type.

Change-Id: Id73aba9ebaa6828c676a7a2afdee7ec6ef2579d3
This commit is contained in:
Ton Ngo 2014-12-03 16:33:06 -08:00
parent e814b85f37
commit 314fb8f2ed
6 changed files with 111 additions and 55 deletions

View File

@ -0,0 +1,30 @@
#
# 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 ToscaNodejs(HotResource):
'''Translate TOSCA node type tosca.nodes.SoftwareComponent.Nodejs.'''
# TODO(anyone): this is a custom TOSCA type so it should be kept separate
# from the TOSCA base types; need to come up with a scheme so new custom
# types can be added by users.
toscatype = 'tosca.nodes.SoftwareComponent.Nodejs'
def __init__(self, nodetemplate):
super(ToscaNodejs, self).__init__(nodetemplate)
pass
def handle_properties(self):
pass

View File

@ -25,12 +25,15 @@ class TOSCATranslator(object):
self.tosca = tosca
self.hot_template = HotTemplate()
self.parsed_params = parsed_params
self.node_translator = None
def translate(self):
self._resolve_input()
self.hot_template.description = self.tosca.description
self.hot_template.parameters = self._translate_inputs()
self.hot_template.resources = self._translate_node_templates()
self.node_translator = TranslateNodeTemplates(self.tosca.nodetemplates,
self.hot_template)
self.hot_template.resources = self.node_translator.translate()
self.hot_template.outputs = self._translate_outputs()
return self.hot_template.output_to_yaml()
@ -38,13 +41,8 @@ class TOSCATranslator(object):
translator = TranslateInputs(self.tosca.inputs, self.parsed_params)
return translator.translate()
def _translate_node_templates(self):
translator = TranslateNodeTemplates(self.tosca.nodetemplates,
self.hot_template)
return translator.translate()
def _translate_outputs(self):
translator = TranslateOutputs(self.tosca.outputs)
translator = TranslateOutputs(self.tosca.outputs, self.node_translator)
return translator.translate()
# check all properties for all node and ensure they are resolved

View File

@ -12,6 +12,7 @@
# under the License.
from translator.hot.syntax.hot_parameter import HotParameter
from translator.toscalib.utils.gettextutils import _
INPUT_CONSTRAINTS = (CONSTRAINTS, DESCRIPTION, LENGTH, RANGE,
@ -68,11 +69,17 @@ class TranslateInputs():
hc, hvalue = self._translate_constraints(
constraint.constraint_key, constraint.constraint_value)
hot_constraints.append({hc: hvalue})
cli_value = self.parsed_params[input.name]
if input.name in self.parsed_params:
hot_default = self.parsed_params[input.name]
elif input.default is not None:
hot_default = input.default
else:
raise Exception(_("Need to specify a value "
"for input {0}").format(input.name))
hot_inputs.append(HotParameter(name=input.name,
type=hot_input_type,
description=input.description,
default=cli_value,
default=hot_default,
constraints=hot_constraints))
return hot_inputs

View File

@ -11,6 +11,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import six
from translator.hot.tosca.tosca_block_storage import ToscaBlockStorage
from translator.hot.tosca.tosca_block_storage_attachment import (
ToscaBlockStorageAttachment
@ -18,8 +19,12 @@ from translator.hot.tosca.tosca_block_storage_attachment import (
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_nodejs import ToscaNodejs
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) = \
@ -45,7 +50,8 @@ TOSCA_TO_HOT_TYPE = {'tosca.nodes.Compute': ToscaCompute,
'tosca.nodes.DBMS': ToscaDbms,
'tosca.nodes.Database': ToscaDatabase,
'tosca.nodes.WebApplication.WordPress': ToscaWordpress,
'tosca.nodes.BlockStorage': ToscaBlockStorage}
'tosca.nodes.BlockStorage': ToscaBlockStorage,
'tosca.nodes.SoftwareComponent.Nodejs': ToscaNodejs}
TOSCA_TO_HOT_REQUIRES = {'container': 'server', 'host': 'server',
'dependency': 'depends_on', "connects": 'depends_on'}
@ -59,20 +65,22 @@ class TranslateNodeTemplates():
def __init__(self, nodetemplates, hot_template):
self.nodetemplates = 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 _translate_nodetemplates(self):
hot_resources = []
hot_lookup = {}
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
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.
@ -80,7 +88,7 @@ class TranslateNodeTemplates():
volume_name = None
requirements = node.requirements
if requirements:
# Find the name of associated BlockStorage node
# Find the name of associated BlockStorage node
for requires in requirements:
for value in requires.values():
for n in self.nodetemplates:
@ -92,16 +100,16 @@ class TranslateNodeTemplates():
suffix,
volume_name)
if attachment_node:
hot_resources.append(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 hot_resources:
for resource in self.hot_resources:
expanded = resource.handle_life_cycle()
if expanded:
lifecycle_resources += expanded
hot_resources += lifecycle_resources
self.hot_resources += lifecycle_resources
# Copy the initial dependencies based on the relationship in
# the TOSCA template
@ -110,22 +118,44 @@ class TranslateNodeTemplates():
# if the source of dependency is a server, add dependency
# as properties.get_resource
if node_depend.type == 'tosca.nodes.Compute':
hot_lookup[node].properties['server'] = \
{'get_resource': hot_lookup[node_depend].name}
self.hot_lookup[node].properties['server'] = \
{'get_resource': self.hot_lookup[node_depend].name}
# for all others, add dependency as depends_on
else:
hot_lookup[node].depends_on.append(hot_lookup[node_depend].
top_of_chain())
self.hot_lookup[node].depends_on.append(
self.hot_lookup[node_depend].top_of_chain())
# handle hosting relationship
for resource in hot_resources:
for resource in self.hot_resources:
resource.handle_hosting()
# Handle properties
for resource in hot_resources:
# handle built-in properties of HOT resources
for resource in self.hot_resources:
resource.handle_properties()
return hot_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
@ -145,3 +175,8 @@ class TranslateNodeTemplates():
volume_name
)
return hot_node
def find_hot_resource(self, name):
for resource in self.hot_resources:
if resource.name == name:
return resource

View File

@ -12,8 +12,6 @@
# under the License.
from translator.hot.syntax.hot_output import HotOutput
from translator.toscalib import functions
from translator.toscalib.utils.gettextutils import _
TOSCA_TO_HOT_GET_ATTRS = {'ip_address': 'first_address'}
@ -21,8 +19,9 @@ TOSCA_TO_HOT_GET_ATTRS = {'ip_address': 'first_address'}
class TranslateOutputs():
'''Translate TOSCA Outputs to Heat Outputs.'''
def __init__(self, outputs):
def __init__(self, outputs, node_translator):
self.outputs = outputs
self.nodes = node_translator
def translate(self):
return self._translate_outputs()
@ -30,29 +29,16 @@ class TranslateOutputs():
def _translate_outputs(self):
hot_outputs = []
for output in self.outputs:
hot_value = {}
if isinstance(output.value, functions.GetAttribute):
func = output.value
get_parameters = [
func.get_referenced_node_template().name,
self._translate_attribute_name(func.attribute_name)]
hot_value['get_attr'] = get_parameters
elif isinstance(output.value, functions.GetProperty):
func = output.value
if func.req_or_cap:
raise NotImplementedError(_(
'get_property with requirement/capability in outputs '
'translation is not supported'))
get_parameters = [
func.node_template_name,
self._translate_attribute_name(func.property_name)]
hot_value['get_attr'] = get_parameters
if output.value.name == 'get_attribute':
get_parameters = output.value.args
hot_target = self.nodes.find_hot_resource(get_parameters[0])
hot_value = hot_target.get_hot_attribute(get_parameters[1],
get_parameters)
hot_outputs.append(HotOutput(output.name,
hot_value,
output.description))
else:
hot_value['get_attr'] = output.value
hot_outputs.append(HotOutput(output.name,
hot_value,
output.description))
hot_outputs.append(HotOutput(output.name,
output.value,
output.description))
return hot_outputs
def _translate_attribute_name(self, attribute_name):
return TOSCA_TO_HOT_GET_ATTRS.get(attribute_name, attribute_name)

View File

@ -51,12 +51,12 @@ class ToscaBlockStorageTest(TestCase):
self.assertEqual(
'Public IP address of the newly created compute instance.',
outputs['public_ip']['description'])
self.assertEqual({'get_attr': ['my_server', 'first_address']},
self.assertEqual({'get_attr': ['my_server', 'networks', 'private', 0]},
outputs['public_ip']['value'])
self.assertIn('volume_id', outputs)
self.assertEqual('The volume id of the block storage instance.',
outputs['volume_id']['description'])
self.assertEqual({'get_attr': ['my_storage', 'volume_id']},
self.assertEqual({'get_resource': 'my_storage'},
outputs['volume_id']['value'])
def test_translate_multi_storage(self):