Additions and fixes to network check

- Per interface tree maps
- proper virtial nodes detection
- KVM nodes listing
- CPU count fix
- Basic service fail check (wip)

Change-Id: I62b68793404eeff957ef70468c954df2fda869a5
Related-PROD: PROD-38972
diff --git a/cfg_checker/modules/network/mapper.py b/cfg_checker/modules/network/mapper.py
index ba9a256..cea81bf 100644
--- a/cfg_checker/modules/network/mapper.py
+++ b/cfg_checker/modules/network/mapper.py
@@ -243,6 +243,123 @@
                         # data is already there, just add VIP
                         net_data['ifs'].append(_if)
 
+            def process_interface(lvl, interface, tree, res):
+                # get childs for each root
+                # tree row item (<if_name>, [<parents>], [<childs>])
+                if lvl not in tree:
+                    # - no level - add it
+                    tree[lvl] = {}
+                # there is such interface in this level?
+                if interface not in tree[lvl]:
+                    # - IF not present
+                    # -- get parents, add
+                    _p = res[interface]['lower']
+                    # if None, put empty list
+                    _p = _p if _p else []
+                    # -- get childs, add
+                    _c = res[interface]['upper']
+                    # if None, put empty list
+                    _c = _c if _c else []
+                    tree[lvl].update({
+                        interface: {
+                            "parents": _p,
+                            "children": _c,
+                            "size": len(_p) if len(_p) > len(_c) else len(_c)
+                        }
+                    })
+                    for p_if in tree[lvl][interface]["parents"]:
+                        # -- cycle: execute process for next parent, lvl-1
+                        process_interface(lvl-1, p_if, tree, res)
+                    for c_if in tree[lvl][interface]["children"]:
+                        # -- cycle: execute process for next child, lvl+1
+                        process_interface(lvl+1, c_if, tree, res)
+                else:
+                    # - IF present - exit (been here already)
+                    return
+
+            def _put(cNet, cIndex, _list):
+                for _cI in range(cIndex, len(_list)):
+                    # add child per index
+                    # if space is free
+                    if not _list[_cI]:
+                        _list[_cI] = cNet
+                        break
+
+            # build network hierachy
+            nr = node_data['networks']
+            # walk interface tree
+            for _ifname in node_data['networks']:
+                _tree = {}
+                _level = 0
+                process_interface(_level, _ifname, _tree, nr)
+                # save tree for node/if
+                node_data['networks'][_ifname]['tree'] = _tree
+
+                # debug, print built tree
+                # logger_cli.debug("# '{}'".format(_ifname))
+                lvls = _tree.keys()
+                lvls.sort()
+                n = len(lvls)
+                m = max([len(_tree[k].keys()) for k in _tree.keys()])
+                matrix = [["" for i in range(m)] for j in range(n)]
+                x = 0
+                while True:
+                    _lv = lvls.pop(0)
+                    # get all interfaces on this level
+                    nets = _tree[_lv].keys()
+                    while True:
+                        y = 0
+                        # get next interface
+                        _net = nets.pop(0)
+                        # all nets
+                        _a = [_net]
+                        # put current interface if this is only one left
+                        if not _tree[_lv][_net]['children']:
+                            if _net not in matrix[x]:
+                                _put(_net, y, matrix[x])
+                            y += 1
+                        else:
+                            # get all nets with same child
+                            for _c in _tree[_lv][_net]['children']:
+                                for _o_net in nets:
+                                    if _c in _tree[_lv][_o_net]['children']:
+                                        _a.append(_o_net)
+                                # flush collected nets
+                                for idx in range(len(_a)):
+                                    if _a[idx] in matrix[x]:
+                                        # there is such interface on this level
+                                        # get index
+                                        _nI = matrix[x].index(_a[idx])
+                                        _put(_c, _nI, matrix[x+1])
+                                    else:
+                                        # there is no such interface
+                                        # add it
+                                        for _nI in range(len(matrix[x])):
+                                            if not matrix[x][_nI]:
+                                                matrix[x][_nI] = _a[idx]
+                                                # also, put child
+                                                _put(_c, _nI, matrix[x+1])
+                                                break
+                                    # remove collected nets from processing
+                                    if _a[idx] in nets:
+                                        nets.remove(_a[idx])
+                                y += len(_a)
+                        if not nets:
+                            x += 1
+                            break
+                    if not lvls:
+                        break
+
+                lines = []
+                _columns = [len(max([i for i in li])) for li in matrix]
+                for idx_y in range(m):
+                    line = ""
+                    for idx_x in range(n):
+                        _fmt = "{" + ":{}".format(_columns[idx_x]) + "} "
+                        line += _fmt.format(matrix[idx_x][idx_y])
+                    lines.append(line)
+                node_data['networks'][_ifname]['matrix'] = matrix
+                node_data['networks'][_ifname]['lines'] = lines
         return _runtime
 
     def map_network(self, source):
@@ -282,7 +399,11 @@
                     self.errors.NET_NO_RUNTIME_NETWORK,
                     reclass_net=str(network)
                 )
-                logger_cli.info("    {:-^50}".format(" No runtime network "))
+                logger_cli.warn(
+                    "WARN: {}: {}".format(
+                        " No runtime network ", str(network)
+                    )
+                )
                 continue
             # hostnames
             names = sorted(_runtime[network].keys())
@@ -464,6 +585,8 @@
                             "interface": _if_name,
                             "interface_error": _if_rc,
                             "interface_note": _if_name_suffix,
+                            "interface_map": "\n".join(_host['lines']),
+                            "interface_matrix": _host['matrix'],
                             "ip_address": _ip_str,
                             "address_type": _proto,
                             "rt_mtu": _host['mtu'],