Network check for MCC/MOS
- Network info gathering using DaemonSet with 'hostNetwork=True'
- DaemonSet handling routines
- Mapper and Checker refactoring for Kube
Fixes
- SSH timeouts handling using env vars
MCP_SSH_TIMEOUT when connecting
MCP_SCRIPT_RUN_TIMEOUT when running command
- Progress class supports 0 as an index
Related-PROD: PROD-36575
Change-Id: Ie03a9051007eeb788901acae3696ea2bfdfe33e2
diff --git a/cfg_checker/modules/network/mapper.py b/cfg_checker/modules/network/mapper.py
index 2e925f2..fff6bb6 100644
--- a/cfg_checker/modules/network/mapper.py
+++ b/cfg_checker/modules/network/mapper.py
@@ -4,6 +4,8 @@
from cfg_checker.common import logger_cli
from cfg_checker.common.exception import InvalidReturnException
+from cfg_checker.common.exception import ConfigException
+from cfg_checker.common.exception import KubeException
from cfg_checker.modules.network.network_errors import NetworkErrors
from cfg_checker.nodes import SaltNodes, KubeNodes
@@ -57,11 +59,6 @@
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:
@@ -168,23 +165,17 @@
return _confs
- def _map_runtime_networks(self):
+ def _map_runtime_networks(self, result):
# class uses nodes from self.nodes dict
_runtime = {}
- logger_cli.info("# Mapping node runtime network data")
- self.master.prepare_script_on_active_nodes("ifs_data.py")
- _result = self.master.execute_script_on_active_nodes(
- "ifs_data.py",
- args=["json"]
- )
for key in self.master.nodes.keys():
# check if we are to work with this node
if not self.master.is_node_available(key):
continue
# due to much data to be passed from master,
# it is happening in order
- if key in _result:
- _text = _result[key]
+ if key in result:
+ _text = result[key]
if '{' in _text and '}' in _text:
_text = _text[_text.find('{'):]
else:
@@ -415,6 +406,40 @@
node_data['networks'][_ifname]['lines'] = lines
return _runtime
+
+class SaltNetworkMapper(NetworkMapper):
+ def __init__(
+ self,
+ config,
+ errors_class=None,
+ skip_list=None,
+ skip_list_file=None
+ ):
+ self.master = SaltNodes(config)
+ super(SaltNetworkMapper, self).__init__(
+ config,
+ errors_class=errors_class,
+ skip_list=skip_list,
+ skip_list_file=skip_list_file
+ )
+
+ def get_script_output(self):
+ """
+ Get runtime networks by executing script on nodes
+ """
+ logger_cli.info("# Mapping node runtime network data")
+ self.master.prepare_script_on_active_nodes("ifs_data.py")
+ _result = self.master.execute_script_on_active_nodes(
+ "ifs_data.py",
+ args="json"
+ )
+
+ return _result
+
+ def map_networks(self):
+ self.map_network(self.RECLASS)
+ self.map_network(self.RUNTIME)
+
def map_network(self, source):
# maps target network using given source
_networks = None
@@ -424,7 +449,8 @@
elif source == self.CONFIG:
_networks = self._map_configured_networks()
elif source == self.RUNTIME:
- _networks = self._map_runtime_networks()
+ _r = self.get_script_output()
+ _networks = self._map_runtime_networks(_r)
self.networks[source] = _networks
return _networks
@@ -662,9 +688,6 @@
# save map
self.map = _map
- # other runtime networks found
- # docker, etc
-
return
def print_map(self):
@@ -732,23 +755,7 @@
# " {0:8} {1}".format(hostname.split('.')[0], _text)
# )
# logger_cli.info("\n")
-
-
-class SaltNetworkMapper(NetworkMapper):
- def __init__(
- self,
- config,
- errors_class=None,
- skip_list=None,
- skip_list_file=None
- ):
- self.master = SaltNodes(config)
- super(SaltNetworkMapper, self).__init__(
- config,
- errors_class=errors_class,
- skip_list=skip_list,
- skip_list_file=skip_list_file
- )
+ return
class KubeNetworkMapper(NetworkMapper):
@@ -766,3 +773,156 @@
skip_list=skip_list,
skip_list_file=skip_list_file
)
+
+ def get_script_output(self, script, args=None):
+ """
+ Get runtime network by creating DaemonSet with Host network parameter
+ """
+ # prepare daemonset
+ logger_cli.info("-> Preparing daemonset to get node info")
+ _daemonset = self.master.prepare_daemonset(
+ "daemonset_template.yaml",
+ config_map=script
+ )
+
+ # wait for daemonset, normally less than 60 sec for all
+ # but still, let us give it 10 second per pod
+ _timeout = self.master.nodes.__len__() * 10
+ if not self.master.wait_for_daemonset(_daemonset, timeout=_timeout):
+ raise KubeException("Daemonset deployment fail")
+ logger_cli.info("-> Running script on daemonset")
+ # exec script on all pods in daemonset
+ _result = self.master.execute_script_on_daemon_set(
+ _daemonset,
+ script,
+ args=args
+ )
+
+ # delete daemonset
+ self.master.delete_daemonset(_daemonset)
+
+ return _result
+
+ def map_networks(self):
+ self.map_network(self.RUNTIME)
+
+ def map_network(self, source):
+ # maps target network using given source
+ _networks = None
+
+ if source == self.RUNTIME:
+ logger_cli.info("# Mapping node runtime network data")
+ _r = self.get_script_output("ifs_data.py", args="json")
+ _networks = self._map_runtime_networks(_r)
+ else:
+ raise ConfigException(
+ "Network type not supported in 'Kube': '{}'".format(source)
+ )
+
+ self.networks[source] = _networks
+ return _networks
+
+ def create_map(self):
+ """Create all needed elements for map output
+
+ :return: none
+ """
+ _runtime = self.networks[self.RUNTIME]
+
+ # 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 _runtime:
+ # shortcuts
+ _net = str(network)
+ _map[_net] = {}
+ # hostnames
+ names = sorted(_runtime[network].keys())
+ for hostname in names:
+ _notes = []
+ node = hostname.split('.')[0]
+ if not self.master.is_node_available(hostname, log=False):
+ logger_cli.info(
+ " {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
+ )
+ _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']
+ _if_name_suffix = ""
+ _a = _runtime[network][hostname]
+ for _host in _a:
+ for _if in _host['ifs']:
+ _ip_str = str(_if.exploded)
+
+ # Save all data
+ _values = {
+ "interface": _if_name,
+ "interface_note": _if_name_suffix,
+ "interface_map": "\n".join(_host['lines']),
+ "interface_matrix": _host['matrix'],
+ "ip_address": _ip_str,
+ "rt_mtu": _host['mtu'],
+ "status": _host['state'],
+ "raw_data": _raw,
+ }
+ 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
+ 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:10} {4:10}".format(
+ "Host",
+ "IF",
+ "IP",
+ "MTU",
+ "State"
+ )
+ )
+ 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} {:5} {:10}".format(
+ _i['interface'],
+ _i['interface_note'],
+ _i['ip_address'],
+ _i['rt_mtu'],
+ _i['status']
+ )
+ logger_cli.info(
+ " {0:8} {1}".format(
+ node,
+ _text
+ )
+ )
+ return