blob: 5989a3973c00a3a15f02a015548d39d9fa9ec15f [file] [log] [blame]
Alex Savatieiev9b2f6512019-02-20 18:05:00 -06001import json
2import os
3import sys
Alex Savatieieve9613992019-02-21 18:20:35 -06004import ipaddress
Alex Savatieiev9b2f6512019-02-20 18:05:00 -06005
6from copy import deepcopy
7
Alex Savatieievf526dc02019-03-06 10:11:32 -06008from cfg_checker.reports import reporter
Alex Savatieiev9b2f6512019-02-20 18:05:00 -06009from cfg_checker.common import utils, const
10from cfg_checker.common import config, logger, logger_cli, pkg_dir
11from cfg_checker.common import salt_utils
12from cfg_checker.nodes import SaltNodes, node_tmpl
13
14
15class NetworkChecker(SaltNodes):
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060016 @staticmethod
17 def _map_network_for_host(host, if_class, net_list, data):
Alex Savatieiev08428f22019-03-14 21:21:31 -050018 if if_class.network in net_list.keys():
19 # There is a network
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060020 net_list[if_class.network][host] = data
21 else:
Alex Savatieiev08428f22019-03-14 21:21:31 -050022 # create subnet key
23 net_list[if_class.network] = {}
24 # add the host to the dict
25 net_list[if_class.network][host] = data
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060026
27 return net_list
28
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060029 def collect_network_info(self):
30 """
31 Collects info on the network using ifs_data.py script
32
33 :return: none
34 """
Alex Savatieiev42b89fa2019-03-07 18:45:26 -060035 logger_cli.info("# Mapping node runtime network data")
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060036 _result = self.execute_script_on_active_nodes("ifs_data.py", args=["json"])
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060037
38 for key in self.nodes.keys():
Alex Savatieievefa79c42019-03-14 19:14:04 -050039 # check if we are to work with this node
40 if not self.is_node_available(key):
41 continue
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060042 # due to much data to be passed from salt, it is happening in order
43 if key in _result:
44 _text = _result[key]
45 _dict = json.loads(_text[_text.find('{'):])
Alex Savatieievd79dde12019-03-13 19:07:46 -050046 self.nodes[key]['routes'] = _dict.pop("routes")
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060047 self.nodes[key]['networks'] = _dict
48 else:
49 self.nodes[key]['networks'] = {}
Alex Savatieievd79dde12019-03-13 19:07:46 -050050 self.nodes[key]['routes'] = {}
Alex Savatieiev42b89fa2019-03-07 18:45:26 -060051 logger_cli.debug("... {} has {} networks".format(
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060052 key,
53 len(self.nodes[key]['networks'].keys())
54 ))
Alex Savatieievf808cd22019-03-01 13:17:59 -060055 logger_cli.info("-> done collecting networks data")
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060056
Alex Savatieieve9613992019-02-21 18:20:35 -060057 logger_cli.info("### Building network tree")
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060058 # match interfaces by IP subnets
Alex Savatieieve9613992019-02-21 18:20:35 -060059 _all_nets = {}
Alex Savatieieva05921f2019-02-21 18:21:39 -060060 for host, node_data in self.nodes.iteritems():
Alex Savatieievefa79c42019-03-14 19:14:04 -050061 if not self.is_node_available(host):
62 continue
63
Alex Savatieieve9613992019-02-21 18:20:35 -060064 for net_name, net_data in node_data['networks'].iteritems():
65 # get ips and calculate subnets
Alex Savatieievd79dde12019-03-13 19:07:46 -050066 if net_name in ['lo']:
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060067 # skip the localhost
Alex Savatieieve9613992019-02-21 18:20:35 -060068 continue
69 _ip4s = net_data['ipv4']
70 for _ip_str in _ip4s.keys():
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060071 # create interface class
Alex Savatieieve9613992019-02-21 18:20:35 -060072 _if = ipaddress.IPv4Interface(_ip_str)
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060073 net_data['name'] = net_name
74 net_data['if'] = _if
75
76 _all_nets = self._map_network_for_host(
77 host,
78 _if,
79 _all_nets,
80 net_data
81 )
Alex Savatieieve9613992019-02-21 18:20:35 -060082
83 # save collected info
Alex Savatieiev42b89fa2019-03-07 18:45:26 -060084 self.all_nets = _all_nets
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060085
Alex Savatieiev9c642112019-02-26 13:55:43 -060086
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060087 def collect_reclass_networks(self):
Alex Savatieiev42b89fa2019-03-07 18:45:26 -060088 logger_cli.info("# Mapping reclass networks")
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060089 # Get networks from reclass and mark them
90 _reclass_nets = {}
91 # Get required pillars
92 self.get_specific_pillar_for_nodes("linux:network")
93 for node in self.nodes.keys():
Alex Savatieievefa79c42019-03-14 19:14:04 -050094 # check if this node
95 if not self.is_node_available(node):
96 continue
97 # get the reclass value
98 _pillar = self.nodes[node]['pillars']['linux']['network']
Alex Savatieieva1f6f8c2019-03-18 17:13:55 -050099 # we should be ready if there is no interface in reclass for a node
100 # for example on APT node
101 if 'interface' in _pillar:
102 _pillar = _pillar['interface']
103 else:
104 logger_cli.info("...skipped '{}', no IF in reclass".format(
105 node
106 ))
107 continue
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600108 for _if_name, _if_data in _pillar.iteritems():
109 if 'address' in _if_data:
110 _if = ipaddress.IPv4Interface(
111 _if_data['address'] + '/' + _if_data['netmask']
112 )
113 _if_data['name'] = _if_name
114 _if_data['if'] = _if
115
116 _reclass_nets = self._map_network_for_host(
117 node,
118 _if,
119 _reclass_nets,
120 _if_data
121 )
122
123 self.reclass_nets = _reclass_nets
124
Alex Savatieiev9c642112019-02-26 13:55:43 -0600125
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600126 def print_network_report(self):
127 """
128 Create text report for CLI
129
130 :return: none
131 """
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600132 _all_nets = self.all_nets.keys()
133 logger_cli.info("# Reclass networks")
Alex Savatieievefa79c42019-03-14 19:14:04 -0500134 logger_cli.info(
135 " {0:17} {1:25}: {2:19} {3:5}{4:10} {5}{6} {7} / {8} / {9}".format(
136 "Hostname",
137 "IF",
138 "IP",
139 "rtMTU",
140 "rcMTU",
141 "rtState",
142 "rcState",
143 "rtGate",
144 "rtDef.Gate",
145 "rcGate"
146 )
Alex Savatieievd79dde12019-03-13 19:07:46 -0500147 )
148
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600149 _reclass = [n for n in _all_nets if n in self.reclass_nets]
150 for network in _reclass:
151 # shortcuts
Alex Savatieievd79dde12019-03-13 19:07:46 -0500152 _net = str(network)
153 logger_cli.info("-> {}".format(_net))
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600154 names = sorted(self.all_nets[network].keys())
Alex Savatieieve9613992019-02-21 18:20:35 -0600155 for hostname in names:
Alex Savatieievefa79c42019-03-14 19:14:04 -0500156 if not self.is_node_available(hostname, log=False):
157 logger_cli.info(
158 " {0:17} {1}".format(
159 hostname.split('.')[0],
160 "... no data for the node"
161 )
162 )
163
Alex Savatieievd79dde12019-03-13 19:07:46 -0500164 # get the gateway for current net
165 _routes = self.nodes[hostname]['routes']
166 _route = _routes[_net] if _net in _routes else None
Alex Savatieievefa79c42019-03-14 19:14:04 -0500167 if not _route:
168 _gate = "no route!"
169 else:
170 _gate = _route['gateway'] if _route['gateway'] else "empty"
Alex Savatieievd79dde12019-03-13 19:07:46 -0500171
172 # get the default gateway
173 if 'default' in _routes:
174 _d_gate = ipaddress.IPv4Address(
175 _routes['default']['gateway']
176 )
177 else:
178 _d_gate = None
179 _d_gate_str = _d_gate if _d_gate else "No default gateway!"
180
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600181 _a = self.all_nets[network][hostname]
182 _r = self.reclass_nets[network][hostname]
Alex Savatieievd79dde12019-03-13 19:07:46 -0500183
184 # Take gateway parameter for this IF
185 # from corresponding reclass record
186 _pillar = self.nodes[hostname]['pillars']
Alex Savatieievefa79c42019-03-14 19:14:04 -0500187 _pillar = _pillar['linux']['network']['interface']
188 if not self.is_node_available(hostname):
189 _r_gate = "-"
Alex Savatieiev08428f22019-03-14 21:21:31 -0500190 elif _a['if'].network not in self.reclass_nets:
Alex Savatieievefa79c42019-03-14 19:14:04 -0500191 _r_gate = "no IF in reclass!"
192 else:
Alex Savatieiev08428f22019-03-14 21:21:31 -0500193 _rd = self.reclass_nets[_a['if'].network][hostname]
Alex Savatieievefa79c42019-03-14 19:14:04 -0500194 _r_gate = _rd['gateway'] if 'gateway' in _rd else "empty"
Alex Savatieievd79dde12019-03-13 19:07:46 -0500195
Alex Savatieievefa79c42019-03-14 19:14:04 -0500196 _text = "{0:25}: {1:19} {2:5}{3:10} {4:4}{5:10} {6} / {7} / {8}".format(
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600197 _a['name'],
198 str(_a['if'].ip),
199 _a['mtu'],
200 '('+str(_r['mtu'])+')' if 'mtu' in _r else '(unset!)',
201 _a['state'],
Alex Savatieievd79dde12019-03-13 19:07:46 -0500202 "(enabled)" if _r['enabled'] else "(disabled)",
203 _gate,
204 _d_gate_str,
205 _r_gate
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600206 )
Alex Savatieieve9613992019-02-21 18:20:35 -0600207 logger_cli.info(
Alex Savatieiev588b2c42019-03-11 10:39:21 -0500208 " {0:17} {1}".format(hostname.split('.')[0], _text)
Alex Savatieieve9613992019-02-21 18:20:35 -0600209 )
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600210
211 logger_cli.info("\n# Other networks")
212 _other = [n for n in _all_nets if n not in self.reclass_nets]
213 for network in _other:
214 logger_cli.info("-> {}".format(str(network)))
215 names = sorted(self.all_nets[network].keys())
216
217 for hostname in names:
Alex Savatieiev588b2c42019-03-11 10:39:21 -0500218 _text = "{0:25}: {1:19} {2:5} {3:4}".format(
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600219 self.all_nets[network][hostname]['name'],
220 str(self.all_nets[network][hostname]['if'].ip),
221 self.all_nets[network][hostname]['mtu'],
222 self.all_nets[network][hostname]['state']
223 )
224 logger_cli.info(
Alex Savatieiev588b2c42019-03-11 10:39:21 -0500225 " {0:17} {1}".format(hostname.split('.')[0], _text)
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600226 )
227
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600228
229 def create_html_report(self, filename):
230 """
231 Create static html showing network schema-like report
232
233 :return: none
234 """
235 logger_cli.info("### Generating report to '{}'".format(filename))
236 _report = reporter.ReportToFile(
237 reporter.HTMLNetworkReport(),
238 filename
239 )
240 _report({
241 "nodes": self.nodes,
242 "diffs": {}
243 })
244 logger_cli.info("-> Done")