From 54d040149f436fe4ae8ecc2d3e0a8194e58689b5 Mon Sep 17 00:00:00 2001 From: srinivas_tadepalli <srinivas.tadepalli@tcs.com> Date: Wed, 1 Apr 2015 12:02:25 +0530 Subject: [PATCH] The output template is close to HOT template heat-translator output is now generated exactly the same way as we insert (used OrderedDict instead of dict) eg: output section at the end type comes before properties of resources Added a testcase to compare generated translator output and expected hot output template Change-Id: Iba2e4bc73c471f3e3064a9481fc397ad08470c7a Closes-Bug: #1355542 --- translator/hot/syntax/hot_parameter.py | 5 +- translator/hot/syntax/hot_resource.py | 4 +- translator/hot/syntax/hot_template.py | 18 ++++-- .../data/hot_output/hot_single_server.yaml | 34 +++++++++++ .../tests/data/tosca_single_server.yaml | 32 ++++++++++ .../tests/test_translated_output_order.py | 58 +++++++++++++++++++ translator/toscalib/utils/yamlparser.py | 26 +++++++++ 7 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 translator/tests/data/hot_output/hot_single_server.yaml create mode 100644 translator/tests/data/tosca_single_server.yaml create mode 100644 translator/tests/test_translated_output_order.py diff --git a/translator/hot/syntax/hot_parameter.py b/translator/hot/syntax/hot_parameter.py index 3521ef24..02c27dab 100644 --- a/translator/hot/syntax/hot_parameter.py +++ b/translator/hot/syntax/hot_parameter.py @@ -11,6 +11,8 @@ # License for the specific language governing permissions and limitations # under the License. +from collections import OrderedDict + KEYS = (TYPE, DESCRIPTION, DEFAULT, CONSTRAINTS, HIDDEN, LABEL) = \ ('type', 'description', 'default', 'constraints', 'hidden', 'label') @@ -29,7 +31,8 @@ class HotParameter(object): self.constraints = constraints def get_dict_output(self): - param_sections = {TYPE: self.type} + param_sections = OrderedDict() + param_sections[TYPE] = self.type if self.label: param_sections[LABEL] = self.label if self.description: diff --git a/translator/hot/syntax/hot_resource.py b/translator/hot/syntax/hot_resource.py index 88c9ca6c..0e7b48a9 100644 --- a/translator/hot/syntax/hot_resource.py +++ b/translator/hot/syntax/hot_resource.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. +from collections import OrderedDict import six from translator.toscalib.functions import GetInput @@ -178,7 +179,8 @@ class HotResource(object): return dependent.top_of_chain() def get_dict_output(self): - resource_sections = {TYPE: self.type} + resource_sections = OrderedDict() + resource_sections[TYPE] = self.type if self.properties: resource_sections[PROPERTIES] = self.properties if self.metadata: diff --git a/translator/hot/syntax/hot_template.py b/translator/hot/syntax/hot_template.py index 898312e2..5cc97c64 100644 --- a/translator/hot/syntax/hot_template.py +++ b/translator/hot/syntax/hot_template.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. +from collections import OrderedDict import textwrap import yaml @@ -31,8 +32,16 @@ class HotTemplate(object): self.parameters = [] self.description = "" + def represent_ordereddict(self, dumper, data): + nodes = [] + for key, value in data.items(): + node_key = dumper.represent_data(key) + node_value = dumper.represent_data(value) + nodes.append((node_key, node_value)) + return yaml.nodes.MappingNode(u'tag:yaml.org,2002:map', nodes) + def output_to_yaml(self): - dict_output = {} + dict_output = OrderedDict() # Version version_string = self.VERSION + ": " + self.LATEST + "\n\n" @@ -44,24 +53,25 @@ class HotTemplate(object): desc_str = self.DESCRIPTION + ": >\n " + wrapped_txt + "\n\n" # Parameters - all_params = {} + all_params = OrderedDict() for parameter in self.parameters: all_params.update(parameter.get_dict_output()) dict_output.update({self.PARAMETERS: all_params}) # Resources - all_resources = {} + all_resources = OrderedDict() for resource in self.resources: if not resource.hide_resource: all_resources.update(resource.get_dict_output()) dict_output.update({self.RESOURCES: all_resources}) # Outputs - all_outputs = {} + all_outputs = OrderedDict() for output in self.outputs: all_outputs.update(output.get_dict_output()) dict_output.update({self.OUTPUTS: all_outputs}) + yaml.add_representer(OrderedDict, self.represent_ordereddict) yaml_string = yaml.dump(dict_output, default_flow_style=False) # get rid of the '' from yaml.dump around numbers yaml_string = yaml_string.replace('\'', '') diff --git a/translator/tests/data/hot_output/hot_single_server.yaml b/translator/tests/data/hot_output/hot_single_server.yaml new file mode 100644 index 00000000..6196310d --- /dev/null +++ b/translator/tests/data/hot_output/hot_single_server.yaml @@ -0,0 +1,34 @@ +heat_template_version: 2013-05-23 + +description: > + TOSCA simple profile for Compute. + +parameters: + cpus: + type: number + description: Number of CPUs for the server. + default: 2 + constraints: + - allowed_values: + - 1 + - 2 + - 4 + - 8 +resources: + server: + type: OS::Nova::Server + properties: + flavor: null + image: fedora-amd64-heat-config + key_name: userkey + user_data_format: SOFTWARE_CONFIG +outputs: + server_address: + description: IP address of server instance. + value: + get_attr: + - server + - networks + - private + - 0 + diff --git a/translator/tests/data/tosca_single_server.yaml b/translator/tests/data/tosca_single_server.yaml new file mode 100644 index 00000000..eab308ca --- /dev/null +++ b/translator/tests/data/tosca_single_server.yaml @@ -0,0 +1,32 @@ +tosca_definitions_version: tosca_simple_yaml_1_0_0 + +description: > + TOSCA simple profile for Compute. + +inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + +node_templates: + server: + type: tosca.nodes.Compute + properties: + # compute properties (flavor) + disk_size: 10 + num_cpus: { get_input: cpus } + mem_size: 4096 + capabilities: + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18 + +outputs: + server_address: + description: IP address of server instance. + value: { get_attribute: [server, private_address] } diff --git a/translator/tests/test_translated_output_order.py b/translator/tests/test_translated_output_order.py new file mode 100644 index 00000000..af6548f6 --- /dev/null +++ b/translator/tests/test_translated_output_order.py @@ -0,0 +1,58 @@ +# 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 +import os + +from translator.hot.tosca_translator import TOSCATranslator +from translator.toscalib.tests.base import TestCase +from translator.toscalib.tosca_template import ToscaTemplate +import translator.toscalib.utils.yamlparser + +YAML_PARSER = translator.toscalib.utils.yamlparser.simple_ordered_parse +log = logging.getLogger('tosca') + + +class ToscaTemplateOutputOrderTest(TestCase): + + def test_translate_output_order(self): + tosca_yaml_file = "data/tosca_single_server.yaml" + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + tosca_yaml_file) + parsed_params = {'cpus': 2} + tosca = ToscaTemplate(tosca_tpl) + translate = TOSCATranslator(tosca, parsed_params) + hot_translated_output = translate.translate() + hot_translated_dict = YAML_PARSER(hot_translated_output) + + #load expected hot yaml file + hot_yaml_file = "data/hot_output/hot_single_server.yaml" + hot_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + hot_yaml_file) + hot_expected_dict = {} + with open(hot_tpl) as f: + hot_expected_dict = YAML_PARSER(f.read()) + + #compare generated and expected hot templates + both_equal = True + for generated_item, expected_item in \ + zip(hot_translated_dict.items(), + hot_expected_dict.items()): + if generated_item != expected_item: + log.warning("Generated_template : %s \n is not equal to " + "\nExpected_template: %s", generated_item, + expected_item) + both_equal = False + break + self.assertEqual(both_equal, True) diff --git a/translator/toscalib/utils/yamlparser.py b/translator/toscalib/utils/yamlparser.py index 5db4ebf9..7e679c23 100644 --- a/translator/toscalib/utils/yamlparser.py +++ b/translator/toscalib/utils/yamlparser.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from collections import OrderedDict import yaml if hasattr(yaml, 'CSafeLoader'): @@ -32,3 +33,28 @@ def simple_parse(tmpl_str): if tpl is None: tpl = {} return tpl + + +def ordered_load(stream, Loader=yaml.Loader, object_pairs_hook=OrderedDict): + class OrderedLoader(Loader): + pass + + def construct_mapping(loader, node): + loader.flatten_mapping(node) + return object_pairs_hook(loader.construct_pairs(node)) + + OrderedLoader.add_constructor( + yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, + construct_mapping) + return yaml.load(stream, OrderedLoader) + + +def simple_ordered_parse(tmpl_str): + try: + tpl = ordered_load(tmpl_str) + except yaml.YAMLError as yea: + raise ValueError(yea) + else: + if tpl is None: + tpl = {} + return tpl