blob: 523003911b42414d19887c58010b869cb2e2447f [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):
18 if not any(if_class.ip in net for net in net_list.keys()):
19 # IP not fits into existing networks
20 if if_class.network not in net_list.keys():
21 # create subnet key
22 net_list[if_class.network] = {}
23 # add the host to the dict
24 net_list[if_class.network][host] = data
25 else:
26 # There is a network that ip fits into
27 for _net in net_list.keys():
28 if if_class.ip in _net:
29 net_list[_net][host] = data
30
31 return net_list
32
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060033 def collect_network_info(self):
34 """
35 Collects info on the network using ifs_data.py script
36
37 :return: none
38 """
Alex Savatieiev42b89fa2019-03-07 18:45:26 -060039 logger_cli.info("# Mapping node runtime network data")
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060040 _result = self.execute_script_on_active_nodes("ifs_data.py", args=["json"])
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060041
42 for key in self.nodes.keys():
Alex Savatieievefa79c42019-03-14 19:14:04 -050043 # check if we are to work with this node
44 if not self.is_node_available(key):
45 continue
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060046 # due to much data to be passed from salt, it is happening in order
47 if key in _result:
48 _text = _result[key]
49 _dict = json.loads(_text[_text.find('{'):])
Alex Savatieievd79dde12019-03-13 19:07:46 -050050 self.nodes[key]['routes'] = _dict.pop("routes")
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060051 self.nodes[key]['networks'] = _dict
52 else:
53 self.nodes[key]['networks'] = {}
Alex Savatieievd79dde12019-03-13 19:07:46 -050054 self.nodes[key]['routes'] = {}
Alex Savatieiev42b89fa2019-03-07 18:45:26 -060055 logger_cli.debug("... {} has {} networks".format(
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060056 key,
57 len(self.nodes[key]['networks'].keys())
58 ))
Alex Savatieievf808cd22019-03-01 13:17:59 -060059 logger_cli.info("-> done collecting networks data")
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060060
Alex Savatieieve9613992019-02-21 18:20:35 -060061 logger_cli.info("### Building network tree")
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060062 # match interfaces by IP subnets
Alex Savatieieve9613992019-02-21 18:20:35 -060063 _all_nets = {}
Alex Savatieieva05921f2019-02-21 18:21:39 -060064 for host, node_data in self.nodes.iteritems():
Alex Savatieievefa79c42019-03-14 19:14:04 -050065 if not self.is_node_available(host):
66 continue
67
Alex Savatieieve9613992019-02-21 18:20:35 -060068 for net_name, net_data in node_data['networks'].iteritems():
69 # get ips and calculate subnets
Alex Savatieievd79dde12019-03-13 19:07:46 -050070 if net_name in ['lo']:
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060071 # skip the localhost
Alex Savatieieve9613992019-02-21 18:20:35 -060072 continue
73 _ip4s = net_data['ipv4']
74 for _ip_str in _ip4s.keys():
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060075 # create interface class
Alex Savatieieve9613992019-02-21 18:20:35 -060076 _if = ipaddress.IPv4Interface(_ip_str)
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060077 net_data['name'] = net_name
78 net_data['if'] = _if
79
80 _all_nets = self._map_network_for_host(
81 host,
82 _if,
83 _all_nets,
84 net_data
85 )
Alex Savatieieve9613992019-02-21 18:20:35 -060086
87 # save collected info
Alex Savatieiev42b89fa2019-03-07 18:45:26 -060088 self.all_nets = _all_nets
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060089
Alex Savatieiev9c642112019-02-26 13:55:43 -060090
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060091 def collect_reclass_networks(self):
Alex Savatieiev42b89fa2019-03-07 18:45:26 -060092 logger_cli.info("# Mapping reclass networks")
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060093 # Get networks from reclass and mark them
94 _reclass_nets = {}
95 # Get required pillars
96 self.get_specific_pillar_for_nodes("linux:network")
97 for node in self.nodes.keys():
Alex Savatieievefa79c42019-03-14 19:14:04 -050098 # check if this node
99 if not self.is_node_available(node):
100 continue
101 # get the reclass value
102 _pillar = self.nodes[node]['pillars']['linux']['network']
103 _pillar = _pillar['interface']
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600104 for _if_name, _if_data in _pillar.iteritems():
105 if 'address' in _if_data:
106 _if = ipaddress.IPv4Interface(
107 _if_data['address'] + '/' + _if_data['netmask']
108 )
109 _if_data['name'] = _if_name
110 _if_data['if'] = _if
111
112 _reclass_nets = self._map_network_for_host(
113 node,
114 _if,
115 _reclass_nets,
116 _if_data
117 )
118
119 self.reclass_nets = _reclass_nets
120
Alex Savatieiev9c642112019-02-26 13:55:43 -0600121
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600122 def print_network_report(self):
123 """
124 Create text report for CLI
125
126 :return: none
127 """
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600128 _all_nets = self.all_nets.keys()
129 logger_cli.info("# Reclass networks")
Alex Savatieievefa79c42019-03-14 19:14:04 -0500130 logger_cli.info(
131 " {0:17} {1:25}: {2:19} {3:5}{4:10} {5}{6} {7} / {8} / {9}".format(
132 "Hostname",
133 "IF",
134 "IP",
135 "rtMTU",
136 "rcMTU",
137 "rtState",
138 "rcState",
139 "rtGate",
140 "rtDef.Gate",
141 "rcGate"
142 )
Alex Savatieievd79dde12019-03-13 19:07:46 -0500143 )
144
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600145 _reclass = [n for n in _all_nets if n in self.reclass_nets]
146 for network in _reclass:
147 # shortcuts
Alex Savatieievd79dde12019-03-13 19:07:46 -0500148 _net = str(network)
149 logger_cli.info("-> {}".format(_net))
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600150 names = sorted(self.all_nets[network].keys())
Alex Savatieieve9613992019-02-21 18:20:35 -0600151 for hostname in names:
Alex Savatieievefa79c42019-03-14 19:14:04 -0500152 if not self.is_node_available(hostname, log=False):
153 logger_cli.info(
154 " {0:17} {1}".format(
155 hostname.split('.')[0],
156 "... no data for the node"
157 )
158 )
159
Alex Savatieievd79dde12019-03-13 19:07:46 -0500160 # get the gateway for current net
161 _routes = self.nodes[hostname]['routes']
162 _route = _routes[_net] if _net in _routes else None
Alex Savatieievefa79c42019-03-14 19:14:04 -0500163 if not _route:
164 _gate = "no route!"
165 else:
166 _gate = _route['gateway'] if _route['gateway'] else "empty"
Alex Savatieievd79dde12019-03-13 19:07:46 -0500167
168 # get the default gateway
169 if 'default' in _routes:
170 _d_gate = ipaddress.IPv4Address(
171 _routes['default']['gateway']
172 )
173 else:
174 _d_gate = None
175 _d_gate_str = _d_gate if _d_gate else "No default gateway!"
176
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600177 _a = self.all_nets[network][hostname]
178 _r = self.reclass_nets[network][hostname]
Alex Savatieievd79dde12019-03-13 19:07:46 -0500179
180 # Take gateway parameter for this IF
181 # from corresponding reclass record
182 _pillar = self.nodes[hostname]['pillars']
Alex Savatieievefa79c42019-03-14 19:14:04 -0500183 _pillar = _pillar['linux']['network']['interface']
184 if not self.is_node_available(hostname):
185 _r_gate = "-"
186 elif _a['name'] not in _pillar:
187 _r_gate = "no IF in reclass!"
188 else:
189 _rd = _pillar[_a['name']]
190 _r_gate = _rd['gateway'] if 'gateway' in _rd else "empty"
Alex Savatieievd79dde12019-03-13 19:07:46 -0500191
Alex Savatieievefa79c42019-03-14 19:14:04 -0500192 _text = "{0:25}: {1:19} {2:5}{3:10} {4:4}{5:10} {6} / {7} / {8}".format(
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600193 _a['name'],
194 str(_a['if'].ip),
195 _a['mtu'],
196 '('+str(_r['mtu'])+')' if 'mtu' in _r else '(unset!)',
197 _a['state'],
Alex Savatieievd79dde12019-03-13 19:07:46 -0500198 "(enabled)" if _r['enabled'] else "(disabled)",
199 _gate,
200 _d_gate_str,
201 _r_gate
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600202 )
Alex Savatieieve9613992019-02-21 18:20:35 -0600203 logger_cli.info(
Alex Savatieiev588b2c42019-03-11 10:39:21 -0500204 " {0:17} {1}".format(hostname.split('.')[0], _text)
Alex Savatieieve9613992019-02-21 18:20:35 -0600205 )
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600206
207 logger_cli.info("\n# Other networks")
208 _other = [n for n in _all_nets if n not in self.reclass_nets]
209 for network in _other:
210 logger_cli.info("-> {}".format(str(network)))
211 names = sorted(self.all_nets[network].keys())
212
213 for hostname in names:
Alex Savatieiev588b2c42019-03-11 10:39:21 -0500214 _text = "{0:25}: {1:19} {2:5} {3:4}".format(
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600215 self.all_nets[network][hostname]['name'],
216 str(self.all_nets[network][hostname]['if'].ip),
217 self.all_nets[network][hostname]['mtu'],
218 self.all_nets[network][hostname]['state']
219 )
220 logger_cli.info(
Alex Savatieiev588b2c42019-03-11 10:39:21 -0500221 " {0:17} {1}".format(hostname.split('.')[0], _text)
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600222 )
223
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600224
225 def create_html_report(self, filename):
226 """
227 Create static html showing network schema-like report
228
229 :return: none
230 """
231 logger_cli.info("### Generating report to '{}'".format(filename))
232 _report = reporter.ReportToFile(
233 reporter.HTMLNetworkReport(),
234 filename
235 )
236 _report({
237 "nodes": self.nodes,
238 "diffs": {}
239 })
240 logger_cli.info("-> Done")