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/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")