Merge upstream version

Related-PROD: PROD-28199

Change-Id: I5d9dbde1c3ac577fb30fa5d6b1ff18bcee28a0d7
diff --git a/cfg_checker/reports/reporter.py b/cfg_checker/reports/reporter.py
index e1d6b6f..377c061 100644
--- a/cfg_checker/reports/reporter.py
+++ b/cfg_checker/reports/reporter.py
@@ -1,35 +1,82 @@
-import jinja2
-import six
 import abc
+import jinja2
 import os
+import six
+import time
 
 from cfg_checker.common import const
+from cfg_checker.common import logger, logger_cli
+from cfg_checker.helpers.console_utils import Progress
 
 pkg_dir = os.path.dirname(__file__)
 pkg_dir = os.path.join(pkg_dir, os.pardir, os.pardir)
 pkg_dir = os.path.normpath(pkg_dir)
 
 
-def shortname(node_fqdn):
-    # form shortname out of node fqdn
-    return node_fqdn.split(".", 1)[0]
-
-
-def is_equal(pkg_dict):
-    # compare versions of given package
-    return pkg_dict['installed'] == pkg_dict['candidate']
-
-
-def is_active(node_dict):
-    # check node status in node dict
-    return node_dict['status'] == const.NODE_UP
-
-
 def line_breaks(text):
     # replace python linebreaks with html breaks
     return text.replace("\n", "<br />")
 
 
+def get_sorted_keys(td):
+    # detect if we can sort by desc
+    # Yes, this is slow, but bullet-proof from empty desc
+    _desc = all([bool(td[k]['desc']) for k in td.keys()])
+    # Get sorted list
+    if not _desc:
+        return sorted(td.keys())
+    else:
+        return sorted(
+            td.keys(),
+            key=lambda k: (
+                td[k]['desc']['component'],
+                td[k]['desc']['app'],
+                k
+            )
+        )
+
+
+def get_max(_list):
+    return sorted(_list)[-1]
+
+
+def make_action_label(act):
+    _act_labels = {
+        const.ACT_UPGRADE: "Upgrade possible",
+        const.ACT_NEED_UP: "Needs upgrade",
+        const.ACT_NEED_DOWN: "Needs downgrade",
+        const.ACT_REPO: "Needs repo update",
+        const.ACT_NA: ""
+    }
+    return _act_labels[act]
+
+
+def make_action_class(act):
+    _act_classes = {
+        const.ACT_UPGRADE: "possible",
+        const.ACT_NEED_UP: "needs_up",
+        const.ACT_NEED_DOWN: "needs_down",
+        const.ACT_REPO: "needs_repo",
+        const.ACT_NA: ""
+    }
+    return _act_classes[act]
+
+
+def make_status_label(sts):
+    _status_labels = {
+        const.VERSION_OK: "OK",
+        const.VERSION_UP: "Upgraded",
+        const.VERSION_DOWN: "Downgraded",
+        const.VERSION_ERR: "ERROR",
+        const.VERSION_NA: "N/A"
+    }
+    return _status_labels[sts]
+
+
+def make_status_class(sts):
+    return const.all_statuses[sts]
+
+
 @six.add_metaclass(abc.ABCMeta)
 class _Base(object):
     def __init__(self):
@@ -59,14 +106,9 @@
     def __call__(self, payload):
         # init data structures
         data = self.common_data()
-        # payload should have pre-sorted structure
-        # system, nodes, clusters, and the rest in other
-        data.update({
-            "nodes": payload['nodes'],
-            "rc_diffs": payload['rc_diffs'],
-            "pkg_diffs": payload['pkg_diffs'],
-            "tabs": {}
-        })
+        # payload should have pre-sorted structure according to report called
+        # nodes, openstack_release, mcp_release, etc...
+        data.update(payload)
 
         # add template specific data
         self._extend_data(data)
@@ -75,83 +117,40 @@
         self._count_totals(data)
 
         # specific filters
-        self.jinja2_env.filters['shortname'] = shortname
-        self.jinja2_env.filters['is_equal'] = is_equal
-        self.jinja2_env.filters['is_active'] = is_active
         self.jinja2_env.filters['linebreaks'] = line_breaks
+        self.jinja2_env.filters['get_max'] = get_max
+
+        self.jinja2_env.filters['get_sorted_keys'] = get_sorted_keys
+        self.jinja2_env.filters['make_status_label'] = make_status_label
+        self.jinja2_env.filters['make_status_class'] = make_status_class
+        self.jinja2_env.filters['make_action_label'] = make_action_label
+        self.jinja2_env.filters['make_action_class'] = make_action_class
 
         # render!
+        logger_cli.info("-> Using template: {}".format(self.tmpl))
         tmpl = self.jinja2_env.get_template(self.tmpl)
+        logger_cli.info("-> Rendering")
         return tmpl.render(data)
 
     def common_data(self):
         return {
             'counters': {},
-            'salt_info': {}
+            'salt_info': {},
+            'gen_date': time.strftime("%m/%d/%Y %H:%M:%S")
         }
 
     def _extend_data(self, data):
         pass
 
 
-# Package versions report
+# HTML Package versions report
+class CSVAllPackages(_TMPLBase):
+    tmpl = "pkg_versions_csv.j2"
+
+
+# HTML Package versions report
 class HTMLPackageCandidates(_TMPLBase):
-    tmpl = "pkg_versions_tmpl.j2"
-
-    @staticmethod
-    def is_fail_uniq(p_dict, p_name, nodes, node_name):
-        # look up package fail for nodes with similar role
-        _tgroup = nodes[node_name]['node_group']
-        # filter all nodes with the same role
-        _nodes_list = filter(
-            lambda nd: nodes[nd]['node_group'] == _tgroup and nd != node_name,
-            nodes
-        )
-        # lookup same package
-        _fail_uniq = False
-        for _node_name in _nodes_list:
-            # check if there is a package present on node
-            _nd = nodes[_node_name]['packages']
-            if p_name not in _nd:
-                continue
-            # if both backages has same version and differ from candidate
-            if p_dict['candidate'] == _nd[p_name]['candidate'] \
-                    and _nd[p_name]['candidate'] == _nd[p_name]['installed']:
-                # it is not uniq, mark and break
-                _fail_uniq = True
-        return _fail_uniq
-
-    def _extend_data(self, data):
-        # Count values on per-node basis
-        for key, value in data['nodes'].iteritems():
-            # count differences
-            data['counters'][key] = {}
-            data['counters'][key]['packages'] = len(value['packages'].keys())
-            data['counters'][key]['package_diff'] = 0
-            data['counters'][key]['package_eq'] = 0
-
-            # Lookup if this fail is uniq for this node
-            for pkg_name, pkg_value in value['packages'].iteritems():
-                if pkg_value['is_equal']:
-                    pkg_value['fail_uniq'] = False
-                    data['counters'][key]['package_eq'] += 1
-                else:
-                    pkg_value['fail_uniq'] = self.is_fail_uniq(
-                        pkg_value,
-                        pkg_name,
-                        data['nodes'],
-                        key
-                    )
-                    data['counters'][key]['package_diff'] += 1
-       
-        # Count values on all-diffs basis
-        for key, value in data['pkg_diffs'].iteritems():
-            data['counters'][key] = {}
-            data['counters'][key]['df_nodes'] = len(value['df_nodes'].keys())
-            data['counters'][key]['eq_nodes'] = len(value['eq_nodes'])
-
-        # Save all packages counter
-        data['counters']['total_packages'] = data['pkg_diffs'].keys()
+    tmpl = "pkg_versions_html.j2"
 
 
 # Package versions report
@@ -171,10 +170,6 @@
 class HTMLNetworkReport(_TMPLBase):
     tmpl = "network_check_tmpl.j2"
 
-    def _extend_data(self, data):
-
-        return
-
 
 class ReportToFile(object):
     def __init__(self, report, target):