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