From 314fb8f2ed76b8e872808840d4612147c6c34bde Mon Sep 17 00:00:00 2001 From: Ton Ngo Date: Wed, 3 Dec 2014 16:33:06 -0800 Subject: [PATCH] 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 --- translator/hot/tosca/tosca_nodejs.py | 30 ++++++++++ translator/hot/tosca_translator.py | 12 ++-- translator/hot/translate_inputs.py | 11 +++- translator/hot/translate_node_templates.py | 69 ++++++++++++++++------ translator/hot/translate_outputs.py | 40 ++++--------- translator/tests/test_blockstorage.py | 4 +- 6 files changed, 111 insertions(+), 55 deletions(-) create mode 100755 translator/hot/tosca/tosca_nodejs.py diff --git a/translator/hot/tosca/tosca_nodejs.py b/translator/hot/tosca/tosca_nodejs.py new file mode 100755 index 00000000..99fad698 --- /dev/null +++ b/translator/hot/tosca/tosca_nodejs.py @@ -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 diff --git a/translator/hot/tosca_translator.py b/translator/hot/tosca_translator.py index 8e0a3be0..08f606da 100644 --- a/translator/hot/tosca_translator.py +++ b/translator/hot/tosca_translator.py @@ -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 diff --git a/translator/hot/translate_inputs.py b/translator/hot/translate_inputs.py index 0bbf978c..44e318a5 100644 --- a/translator/hot/translate_inputs.py +++ b/translator/hot/translate_inputs.py @@ -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 diff --git a/translator/hot/translate_node_templates.py b/translator/hot/translate_node_templates.py index fd007222..eeb0bc3c 100644 --- a/translator/hot/translate_node_templates.py +++ b/translator/hot/translate_node_templates.py @@ -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 diff --git a/translator/hot/translate_outputs.py b/translator/hot/translate_outputs.py index 68464123..c2e11f68 100644 --- a/translator/hot/translate_outputs.py +++ b/translator/hot/translate_outputs.py @@ -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) diff --git a/translator/tests/test_blockstorage.py b/translator/tests/test_blockstorage.py index 6724df7a..5c8ff8c3 100644 --- a/translator/tests/test_blockstorage.py +++ b/translator/tests/test_blockstorage.py @@ -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):