Network check HTML report

- uniform map generation
- POC for additional checks on env

Change-Id: I52edcc94f4d9826cbfb1159e5311514097a15f62
Related-PROD: PROD-32792
diff --git a/cfg_checker/modules/network/__init__.py b/cfg_checker/modules/network/__init__.py
index 6b06022..6b9013a 100644
--- a/cfg_checker/modules/network/__init__.py
+++ b/cfg_checker/modules/network/__init__.py
@@ -66,15 +66,6 @@
     return _parser
 
 
-def _prepare_map():
-    _mapper = mapper.NetworkMapper()
-    _mapper.map_network(_mapper.RECLASS)
-    _mapper.map_network(_mapper.RUNTIME)
-    _mapper.map_network(_mapper.CONFIG)
-
-    return _mapper
-
-
 def do_check(args):
     # Net Checks
     # should not print map, etc...
@@ -104,6 +95,10 @@
     _filename = args_utils.get_arg(args, 'html')
 
     netChecker = checker.NetworkChecker()
+    netChecker.check_networks(map=False)
+
+    # save what was collected
+    netChecker.errors.save_iteration_data()
     netChecker.create_html_report(_filename)
 
     return
@@ -114,7 +109,9 @@
     # Should generate network map to console or HTML
     logger_cli.info("# Network report")
 
-    networkMap = _prepare_map()
+    networkMap = mapper.NetworkMapper()
+    networkMap.prepare_all_maps()
+    networkMap.create_map()
     networkMap.print_map()
 
     return
diff --git a/cfg_checker/modules/network/checker.py b/cfg_checker/modules/network/checker.py
index 89db6ba..acd3bb1 100644
--- a/cfg_checker/modules/network/checker.py
+++ b/cfg_checker/modules/network/checker.py
@@ -10,11 +10,13 @@
         self.errors = NetworkErrors()
         self.mapper = NetworkMapper(self.errors)
 
-    def check_networks(self):
+    def check_networks(self, map=True):
         self.mapper.map_network(self.mapper.RECLASS)
         self.mapper.map_network(self.mapper.RUNTIME)
 
-        self.mapper.print_map()
+        self.mapper.create_map()
+        if map:
+            self.mapper.print_map()
 
     def print_summary(self):
         logger_cli.info(self.errors.get_summary(print_zeros=False))
@@ -39,10 +41,10 @@
             filename
         )
         _report({
-            "nodes": self.nodes,
-            "network": {},
-            "mcp_release": self.mcp_release,
-            "openstack_release": self.openstack_release
+            "nodes": self.mapper.nodes,
+            "map": self.mapper.map,
+            "mcp_release": self.mapper.cluster['mcp_release'],
+            "openstack_release": self.mapper.cluster['openstack_release']
 
         })
         logger_cli.info("-> Done")
diff --git a/cfg_checker/modules/network/mapper.py b/cfg_checker/modules/network/mapper.py
index c3e3b73..ba9a256 100644
--- a/cfg_checker/modules/network/mapper.py
+++ b/cfg_checker/modules/network/mapper.py
@@ -36,6 +36,7 @@
         # init networks and nodes
         self.networks = {}
         self.nodes = salt_master.get_nodes()
+        self.cluster = salt_master.get_info()
         # init and pre-populate interfaces
         self.interfaces = {k: {} for k in self.nodes}
         # Init errors class
@@ -45,6 +46,11 @@
             logger_cli.debug("... init error logs folder")
             self.errors = NetworkErrors()
 
+    def prepare_all_maps(self):
+        self.map_network(self.RECLASS)
+        self.map_network(self.RUNTIME)
+        self.map_network(self.CONFIG)
+
     # adding net data to tree
     def _add_data(self, _list, _n, _h, _d):
         if _n not in _list:
@@ -253,32 +259,23 @@
         self.networks[source] = _networks
         return _networks
 
-    def print_map(self):
-        """
-        Create text report for CLI
+    def create_map(self):
+        """Create all needed elements for map output
 
         :return: none
         """
         _runtime = self.networks[self.RUNTIME]
         _reclass = self.networks[self.RECLASS]
-        logger_cli.info("# Networks")
-        logger_cli.info(
-            "    {0:8} {1:25} {2:25} {3:6} {4:10} {5:10} {6}/{7}".format(
-                "Host",
-                "IF",
-                "IP",
-                "Proto",
-                "MTU",
-                "State",
-                "Gate",
-                "Def.Gate"
-            )
-        )
+
+        # main networks, target vars
+        _map = {}
         # No matter of proto, at least one IP will be present for the network
+        # we interested in, since we are to make sure that L3 level
+        # is configured according to reclass model
         for network in _reclass:
             # shortcuts
             _net = str(network)
-            logger_cli.info("-> {}".format(_net))
+            _map[_net] = {}
             if network not in _runtime:
                 # reclass has network that not found in runtime
                 self.errors.add_error(
@@ -290,6 +287,7 @@
             # hostnames
             names = sorted(_runtime[network].keys())
             for hostname in names:
+                _notes = []
                 node = hostname.split('.')[0]
                 if not salt_master.is_node_available(hostname, log=False):
                     logger_cli.info(
@@ -300,9 +298,15 @@
                         self.errors.NET_NODE_NON_RESPONSIVE,
                         host=hostname
                     )
+                    _notes.append(
+                        self.errors.get_error_type_text(
+                            self.errors.NET_NODE_NON_RESPONSIVE
+                        )
+                    )
                     continue
                 # lookup interface name on node using network CIDR
                 _if_name = _runtime[network][hostname][0]["name"]
+                _raw = self.interfaces[hostname][_if_name]['runtime']
                 # get proper reclass
                 _r = self.interfaces[hostname][_if_name]['reclass']
                 _if_name_suffix = ""
@@ -315,6 +319,11 @@
                         host=hostname,
                         if_name=_if_name
                     )
+                    _notes.append(
+                        self.errors.get_error_type_text(
+                            self.errors.NET_NODE_UNEXPECTED_IF
+                        )
+                    )
                     _if_rc = "*"
 
                 if "proto" in _r:
@@ -379,6 +388,11 @@
                                     ip=_ip_str,
                                     gateway=_gate
                                 )
+                                _notes.append(
+                                    self.errors.get_error_type_text(
+                                        self.errors.NET_UNEXPECTED_GATEWAY
+                                    )
+                                )
                                 _gate_error = "*"
 
                         # IF status in reclass
@@ -389,6 +403,11 @@
                                 host=hostname,
                                 if_name=_if_name
                             )
+                            _notes.append(
+                                self.errors.get_error_type_text(
+                                    self.errors.NET_NO_RC_IF_STATUS
+                                )
+                            )
                             _up_error = "*"
 
                         _rc_mtu = _r['mtu'] if 'mtu' in _r else None
@@ -408,6 +427,11 @@
                                         reclass_mtu=_rc_mtu,
                                         runtime_mtu=_host['mtu']
                                     )
+                                    _notes.append(
+                                        self.errors.get_error_type_text(
+                                            self.errors.NET_MTU_MISMATCH
+                                        )
+                                    )
                                     _rc_mtu_s = "/" + _rc_mtu_s
                                     _mtu_error = "*"
                                 else:
@@ -424,48 +448,111 @@
                                     if_cidr=_ip_str,
                                     if_mtu=_host['mtu']
                                 )
+                                _notes.append(
+                                    self.errors.get_error_type_text(
+                                        self.errors.NET_MTU_EMPTY
+                                    )
+                                )
                                 _mtu_error = "*"
                         else:
                             # this is a VIP
                             _if_name = " "*7
                             _if_name_suffix = ""
                             _ip_str += " VIP"
-                        # Host IF IP Proto MTU State Gate Def.Gate
-                        _text = "{:7} {:17} {:25} {:6} {:10} " \
-                                "{:10} {} / {}".format(
-                                    _if_name + _if_rc,
-                                    _if_name_suffix,
-                                    _ip_str,
-                                    _proto,
-                                    _host['mtu'] + _rc_mtu_s + _mtu_error,
-                                    _host['state'] + _up_error,
-                                    _gate + _gate_error,
-                                    _d_gate_str
-                                )
-                        logger_cli.info(
-                            "    {0:8} {1}".format(
-                                node,
-                                _text
+                        # Save all data
+                        _values = {
+                            "interface": _if_name,
+                            "interface_error": _if_rc,
+                            "interface_note": _if_name_suffix,
+                            "ip_address": _ip_str,
+                            "address_type": _proto,
+                            "rt_mtu": _host['mtu'],
+                            "rc_mtu": _rc_mtu_s,
+                            "mtu_error": _mtu_error,
+                            "status": _host['state'],
+                            "status_error": _up_error,
+                            "subnet_gateway": _gate,
+                            "subnet_gateway_error": _gate_error,
+                            "default_gateway": _d_gate_str,
+                            "raw_data": _raw,
+                            "error_note": " and ".join(_notes)
+                        }
+                        if node in _map[_net]:
+                            # add if to host
+                            _map[_net][node].append(_values)
+                        else:
+                            _map[_net][node] = [_values]
+                        _notes = []
+
+        # save map
+        self.map = _map
+        # other runtime networks found
+        # docker, etc
+
+        return
+
+    def print_map(self):
+        """
+        Create text report for CLI
+
+        :return: none
+        """
+        logger_cli.info("# Networks")
+        logger_cli.info(
+            "    {0:8} {1:25} {2:25} {3:6} {4:10} {5:10} {6}/{7}".format(
+                "Host",
+                "IF",
+                "IP",
+                "Proto",
+                "MTU",
+                "State",
+                "Gate",
+                "Def.Gate"
+            )
+        )
+        for network in self.map.keys():
+            logger_cli.info("-> {}".format(network))
+            for hostname in self.map[network].keys():
+                node = hostname.split('.')[0]
+                _n = self.map[network][hostname]
+                for _i in _n:
+                    # Host IF IP Proto MTU State Gate Def.Gate
+                    _text = "{:7} {:17} {:25} {:6} {:10} " \
+                            "{:10} {} / {}".format(
+                                _i['interface'] + _i['interface_error'],
+                                _i['interface_note'],
+                                _i['ip_address'],
+                                _i['address_type'],
+                                _i['rt_mtu'] + _i['rc_mtu'] + _i['mtu_error'],
+                                _i['status'] + _i['status_error'],
+                                _i['subnet_gateway'] +
+                                _i['subnet_gateway_error'],
+                                _i['default_gateway']
                             )
-                        )
-
-        logger_cli.info("\n# Other networks")
-        _other = [n for n in _runtime if n not in _reclass]
-        for network in _other:
-            logger_cli.info("-> {}".format(str(network)))
-            names = sorted(_runtime[network].keys())
-
-            for hostname in names:
-                for _n in _runtime[network][hostname]:
-                    _ifs = [str(ifs.ip) for ifs in _n['ifs']]
-                    _text = "{:25} {:25} {:6} {:10} {}".format(
-                        _n['name'],
-                        ", ".join(_ifs),
-                        "-",
-                        _n['mtu'],
-                        _n['state']
-                    )
                     logger_cli.info(
-                        "    {0:8} {1}".format(hostname.split('.')[0], _text)
+                        "    {0:8} {1}".format(
+                            node,
+                            _text
+                        )
                     )
-        logger_cli.info("\n")
+
+        # logger_cli.info("\n# Other networks")
+        # _other = [n for n in _runtime if n not in _reclass]
+        # for network in _other:
+        #     logger_cli.info("-> {}".format(str(network)))
+        #     names = sorted(_runtime[network].keys())
+
+        #     for hostname in names:
+        #         for _n in _runtime[network][hostname]:
+        #             _ifs = [str(ifs.ip) for ifs in _n['ifs']]
+        #             _text = "{:25} {:25} {:6} {:10} {}".format(
+        #                 _n['name'],
+        #                 ", ".join(_ifs),
+        #                 "-",
+        #                 _n['mtu'],
+        #                 _n['state']
+        #             )
+        #             logger_cli.info(
+        #                 "    {0:8} {1}".format(hostname.split('.')[0], _text)
+        #             )
+        # logger_cli.info("\n")