Updates to mapper and network check

 - support for proto types: DHCP, MANUAL, STATIC
 - proper interface mappings reclass/runtime
 - updated map
 - first step in bridge check support

Change-Id: Idd9f2aa22e34bcaa59a18776c8ebb6be086d598f
Related-PROD: PROD-28199
diff --git a/cfg_checker/modules/network/mapper.py b/cfg_checker/modules/network/mapper.py
index 08cc99f..e0c3113 100644
--- a/cfg_checker/modules/network/mapper.py
+++ b/cfg_checker/modules/network/mapper.py
@@ -1,5 +1,6 @@
 import ipaddress
 import json
+from copy import deepcopy
 
 from cfg_checker.common import logger_cli
 from cfg_checker.common.exception import InvalidReturnException
@@ -12,6 +13,7 @@
     "name": "unnamed interface",
     "mac": "",
     "routes": {},
+    "proto": "",
     "ip": [],
     "parameters": {}
 }
@@ -31,8 +33,12 @@
 
     def __init__(self, errors_class=None):
         logger_cli.info("# Initializing mapper")
+        # init networks and nodes
         self.networks = {}
         self.nodes = salt_master.get_nodes()
+        # init and pre-populate interfaces
+        self.interfaces = {k: {} for k in self.nodes}
+        # Init errors class
         if errors_class:
             self.errors = errors_class
         else:
@@ -111,8 +117,12 @@
                 )
                 continue
 
-            # build map based on IPs
+            # build map based on IPs and save info too
             for _if_name, _if_data in _pillar.iteritems():
+                if _if_name not in self.interfaces[node]:
+                    self.interfaces[node][_if_name] = deepcopy(_network_item)
+                self.interfaces[node][_if_name]['reclass'] = deepcopy(_if_data)
+                # map network if any
                 if 'address' in _if_data:
                     _if = ipaddress.IPv4Interface(
                         _if_data['address'] + '/' + _if_data['netmask']
@@ -185,6 +195,14 @@
                 if net_name in ['lo']:
                     # skip the localhost
                     continue
+                else:
+                    # add collected data to interface storage
+                    if net_name not in self.interfaces[host]:
+                        self.interfaces[host][net_name] = \
+                            deepcopy(_network_item)
+                    self.interfaces[host][net_name]['runtime'] = \
+                        deepcopy(net_data)
+
                 #  get data and make sure that wide mask goes first
                 _ip4s = sorted(
                     net_data['ipv4'],
@@ -237,22 +255,20 @@
         """
         _runtime = self.networks[self.RUNTIME]
         _reclass = self.networks[self.RECLASS]
-        logger_cli.info("# Reclass networks")
+        logger_cli.info("# Networks")
         logger_cli.info(
-            "    {0:17} {1:25}: "
-            "{2:19} {3:5}{4:10} {5}{6} {7} / {8} / {9}".format(
-                "Hostname",
+            "    {0:8} {1:25} {2:25} {3:6} {4:10} {5:10} {6}/{7}".format(
+                "Host",
                 "IF",
                 "IP",
-                "rtMTU",
-                "rcMTU",
-                "rtState",
-                "rcState",
-                "rtGate",
-                "rtDef.Gate",
-                "rcGate"
+                "Proto",
+                "MTU",
+                "State",
+                "Gate",
+                "Def.Gate"
             )
         )
+        # No matter of proto, at least one IP will be present for the network
         for network in _reclass:
             # shortcuts
             _net = str(network)
@@ -265,105 +281,112 @@
                 )
                 logger_cli.info("    {:-^50}".format(" No runtime network "))
                 continue
+            # hostnames
             names = sorted(_runtime[network].keys())
             for hostname in names:
+                node = hostname.split('.')[0]
                 if not salt_master.is_node_available(hostname, log=False):
                     logger_cli.info(
-                        "    {0:17} {1}".format(
-                            hostname.split('.')[0],
-                            "... no data for the node"
-                        )
+                        "    {0:8} {1}".format(node, "node not available")
                     )
                     # add non-responsive node erorr
                     self.errors.add_error(
                         self.errors.NET_NODE_NON_RESPONSIVE,
                         host=hostname
                     )
-
-                    # print empty row
-                    _text = " # node non-responsive"
-                    logger_cli.info(
-                        "    {0:17} {1}".format(
-                            hostname.split('.')[0],
-                            _text
-                        )
-                    )
                     continue
 
-                # get the gateway for current net
-                _routes = salt_master.nodes[hostname]['routes']
-                _route = _routes[_net] if _net in _routes else None
-                if not _route:
-                    _gate = "no route!"
+                # lookup interface name on node using network CIDR
+                _if_name = _runtime[network][hostname][0]["name"]
+                # get proper reclass
+                _r = self.interfaces[hostname][_if_name]['reclass']
+                _if_rc = "+" if _r else "*"
+                _if_name_suffix = ""
+                # get the proto value
+                if "proto" in _r:
+                    _proto = _r['proto']
                 else:
-                    _gate = _route['gateway'] if _route['gateway'] else "-"
+                    _proto = "-"
 
-                # get the default gateway
-                if 'default' in _routes:
-                    _d_gate = ipaddress.IPv4Address(
-                        _routes['default']['gateway']
+                if "type" in _r:
+                    _if_name_suffix += _r["type"]
+                if "use_interfaces" in _r:
+                    _if_name_suffix += "->" + ",".join(_r["use_interfaces"])
+
+                if _if_name_suffix:
+                    _if_name_suffix = "({})".format(_if_name_suffix)
+
+                # check reclass has this interface
+                if _proto != "-" and not _r:
+                    self.errors.add_error(
+                        self.errors.NET_NODE_UNEXPECTED_IF,
+                        host=hostname,
+                        if_name=_if_name
                     )
-                else:
-                    _d_gate = None
-                _d_gate_str = _d_gate if _d_gate else "No default gateway!"
 
+                # get gate and routes if proto is static
+                if _proto == 'static':
+                    # get the gateway for current net
+                    _routes = salt_master.nodes[hostname]['routes']
+                    _route = _routes[_net] if _net in _routes else None
+                    if not _route:
+                        _gate = "no route!"
+                    else:
+                        _gate = _route['gateway'] if _route['gateway'] else "-"
+
+                    # get the default gateway
+                    if 'default' in _routes:
+                        _d_gate = ipaddress.IPv4Address(
+                            _routes['default']['gateway']
+                        )
+                    else:
+                        _d_gate = None
+                    _d_gate_str = _d_gate if _d_gate else "No default gateway!"
+                else:
+                    # in case of manual and dhcp, no check possible
+                    _gate = "-"
+                    _d_gate = "-"
+                # iterate through interfaces
                 _a = _runtime[network][hostname]
                 for _host in _a:
                     for _if in _host['ifs']:
-                        # get proper reclass
                         _ip_str = str(_if.exploded)
-                        _r = {}
-                        if hostname in _reclass[network]:
-                            for _item in _reclass[network][hostname]:
-                                for _item_ifs in _item['ifs']:
-                                    if _ip_str == str(_item_ifs.exploded):
-                                        _r = _item
-                        else:
-                            self.errors.add_error(
-                                self.errors.NET_NODE_UNEXPECTED_IF,
-                                host=hostname,
-                                if_name=_host['name'],
-                                if_ip=_ip_str
-                            )
 
                         # check if node is UP
                         if not salt_master.is_node_available(hostname):
                             _r_gate = "-"
                         # get proper network from reclass
-                        else:
+                        elif _proto == 'static':
                             # Lookup match for the ip
-                            _r_gate = "no IF in reclass!"
-                            # get all networks with this hostname
-                            _nets = filter(
-                                lambda n: hostname in _reclass[n].keys(),
-                                _reclass
-                            )
-                            _rd = None
-                            for _item in _nets:
-                                # match ip
-                                _r_dat = _reclass[_item][hostname]
-                                for _r_ifs in _r_dat:
-                                    for _r_if in _r_ifs['ifs']:
-                                        if _if.ip == _r_if.ip:
-                                            _rd = _r_ifs
-                                            break
-                                if _rd:
-                                    _gs = 'gateway'
-                                    _e = "empty"
-                                    _r_gate = _rd[_gs] if _gs in _rd else _e
-                                    break
+                            _r_gate = "-"
+                            if "gateway" in _r:
+                                _r_gate = _r["gateway"]
+                            # if values match, put + for reclass
+                            if _gate == _r_gate:
+                                _r_gate = "+"
+                            else:
+                                _r_gate = "*"
 
                         # IF status in reclass
-                        if 'enabled' not in _r:
-                            _enabled = "(no record!)"
-                        else:
-                            _e = "enabled"
-                            _d = "disabled"
-                            _enabled = "("+_e+")" if _r[_e] else "("+_d+")"
+                        _e = "enabled"
+                        _enabled = "*"
+                        if _e in _r:
+                            _enabled = "+" if _r[_e] else "-"
 
-                        _name = _host['name']
                         _rc_mtu = _r['mtu'] if 'mtu' in _r else None
-                        _rc_mtu_s = str(_rc_mtu) if _rc_mtu else '(-)'
+                        if _rc_mtu:
+                            # there is a reclass MTU, save it
+                            _rc_mtu_s = str(_rc_mtu)
+                        elif _host['mtu'] == 1500 \
+                                or _proto == "-" \
+                                or _proto == "dhcp":
+                            # 1500 is a default value => reclass not have it
+                            # or this is a fancy network
+                            _rc_mtu_s = "-"
+                        else:
+                            # this is an error
+                            _rc_mtu_s = "*"
+
                         # check if this is a VIP address
                         # no checks needed if yes.
                         if _host['vip'] != _ip_str:
@@ -373,7 +396,7 @@
                                     self.errors.add_error(
                                         self.errors.NET_MTU_MISMATCH,
                                         host=hostname,
-                                        if_name=_name,
+                                        if_name=_if_name,
                                         if_cidr=_ip_str,
                                         reclass_mtu=_rc_mtu,
                                         runtime_mtu=_host['mtu']
@@ -384,32 +407,33 @@
                                 self.errors.add_error(
                                     self.errors.NET_MTU_EMPTY,
                                     host=hostname,
-                                    if_name=_name,
+                                    if_name=_if_name,
                                     if_cidr=_ip_str,
                                     if_mtu=_host['mtu']
                                 )
                         else:
                             # this is a VIP
-                            _name = " "*20
+                            _if_name = " "*7
+                            _if_rc = ""
+                            _if_name_suffix = ""
                             _ip_str += " VIP"
-                            _enabled = "(-)"
+                            _enabled = "-"
                             _r_gate = "-"
-
-                        _text = "{0:25} {1:19} {2:5}{3:10} {4:4}{5:10} " \
-                                "{6} / {7} / {8}".format(
-                                    _name,
+                        # Host IF IP Proto MTU State Gate Def.Gate
+                        _text = "{:7} {:18} {:25} {:6} {:10} " \
+                                "{:10} {}/{}".format(
+                                    _if_name + _if_rc,
+                                    _if_name_suffix,
                                     _ip_str,
-                                    _host['mtu'],
-                                    _rc_mtu_s,
-                                    _host['state'],
-                                    _enabled,
-                                    _gate,
-                                    _d_gate_str,
-                                    _r_gate
+                                    _proto,
+                                    _host['mtu'] + "/" + _rc_mtu_s,
+                                    _host['state'] + _enabled,
+                                    _gate + _r_gate,
+                                    _d_gate_str
                                 )
                         logger_cli.info(
-                            "    {0:17} {1}".format(
-                                hostname.split('.')[0],
+                            "    {0:8} {1}".format(
+                                node,
                                 _text
                             )
                         )
@@ -432,3 +456,4 @@
                     logger_cli.info(
                         "    {0:17} {1}".format(hostname.split('.')[0], _text)
                     )
+        logger_cli.info("\n")