blob: f40e1d327affabfd0245377b71c0efd4f5c038a3 [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']
99 _pillar = _pillar['interface']
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600100 for _if_name, _if_data in _pillar.iteritems():
101 if 'address' in _if_data:
102 _if = ipaddress.IPv4Interface(
103 _if_data['address'] + '/' + _if_data['netmask']
104 )
105 _if_data['name'] = _if_name
106 _if_data['if'] = _if
107
108 _reclass_nets = self._map_network_for_host(
109 node,
110 _if,
111 _reclass_nets,
112 _if_data
113 )
114
115 self.reclass_nets = _reclass_nets
116
Alex Savatieiev9c642112019-02-26 13:55:43 -0600117
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600118 def print_network_report(self):
119 """
120 Create text report for CLI
121
122 :return: none
123 """
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600124 _all_nets = self.all_nets.keys()
125 logger_cli.info("# Reclass networks")
Alex Savatieievefa79c42019-03-14 19:14:04 -0500126 logger_cli.info(
127 " {0:17} {1:25}: {2:19} {3:5}{4:10} {5}{6} {7} / {8} / {9}".format(
128 "Hostname",
129 "IF",
130 "IP",
131 "rtMTU",
132 "rcMTU",
133 "rtState",
134 "rcState",
135 "rtGate",
136 "rtDef.Gate",
137 "rcGate"
138 )
Alex Savatieievd79dde12019-03-13 19:07:46 -0500139 )
140
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600141 _reclass = [n for n in _all_nets if n in self.reclass_nets]
142 for network in _reclass:
143 # shortcuts
Alex Savatieievd79dde12019-03-13 19:07:46 -0500144 _net = str(network)
145 logger_cli.info("-> {}".format(_net))
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600146 names = sorted(self.all_nets[network].keys())
Alex Savatieieve9613992019-02-21 18:20:35 -0600147 for hostname in names:
Alex Savatieievefa79c42019-03-14 19:14:04 -0500148 if not self.is_node_available(hostname, log=False):
149 logger_cli.info(
150 " {0:17} {1}".format(
151 hostname.split('.')[0],
152 "... no data for the node"
153 )
154 )
155
Alex Savatieievd79dde12019-03-13 19:07:46 -0500156 # get the gateway for current net
157 _routes = self.nodes[hostname]['routes']
158 _route = _routes[_net] if _net in _routes else None
Alex Savatieievefa79c42019-03-14 19:14:04 -0500159 if not _route:
160 _gate = "no route!"
161 else:
162 _gate = _route['gateway'] if _route['gateway'] else "empty"
Alex Savatieievd79dde12019-03-13 19:07:46 -0500163
164 # get the default gateway
165 if 'default' in _routes:
166 _d_gate = ipaddress.IPv4Address(
167 _routes['default']['gateway']
168 )
169 else:
170 _d_gate = None
171 _d_gate_str = _d_gate if _d_gate else "No default gateway!"
172
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600173 _a = self.all_nets[network][hostname]
174 _r = self.reclass_nets[network][hostname]
Alex Savatieievd79dde12019-03-13 19:07:46 -0500175
176 # Take gateway parameter for this IF
177 # from corresponding reclass record
178 _pillar = self.nodes[hostname]['pillars']
Alex Savatieievefa79c42019-03-14 19:14:04 -0500179 _pillar = _pillar['linux']['network']['interface']
180 if not self.is_node_available(hostname):
181 _r_gate = "-"
Alex Savatieiev08428f22019-03-14 21:21:31 -0500182 elif _a['if'].network not in self.reclass_nets:
Alex Savatieievefa79c42019-03-14 19:14:04 -0500183 _r_gate = "no IF in reclass!"
184 else:
Alex Savatieiev08428f22019-03-14 21:21:31 -0500185 _rd = self.reclass_nets[_a['if'].network][hostname]
Alex Savatieievefa79c42019-03-14 19:14:04 -0500186 _r_gate = _rd['gateway'] if 'gateway' in _rd else "empty"
Alex Savatieievd79dde12019-03-13 19:07:46 -0500187
Alex Savatieievefa79c42019-03-14 19:14:04 -0500188 _text = "{0:25}: {1:19} {2:5}{3:10} {4:4}{5:10} {6} / {7} / {8}".format(
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600189 _a['name'],
190 str(_a['if'].ip),
191 _a['mtu'],
192 '('+str(_r['mtu'])+')' if 'mtu' in _r else '(unset!)',
193 _a['state'],
Alex Savatieievd79dde12019-03-13 19:07:46 -0500194 "(enabled)" if _r['enabled'] else "(disabled)",
195 _gate,
196 _d_gate_str,
197 _r_gate
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600198 )
Alex Savatieieve9613992019-02-21 18:20:35 -0600199 logger_cli.info(
Alex Savatieiev588b2c42019-03-11 10:39:21 -0500200 " {0:17} {1}".format(hostname.split('.')[0], _text)
Alex Savatieieve9613992019-02-21 18:20:35 -0600201 )
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600202
203 logger_cli.info("\n# Other networks")
204 _other = [n for n in _all_nets if n not in self.reclass_nets]
205 for network in _other:
206 logger_cli.info("-> {}".format(str(network)))
207 names = sorted(self.all_nets[network].keys())
208
209 for hostname in names:
Alex Savatieiev588b2c42019-03-11 10:39:21 -0500210 _text = "{0:25}: {1:19} {2:5} {3:4}".format(
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600211 self.all_nets[network][hostname]['name'],
212 str(self.all_nets[network][hostname]['if'].ip),
213 self.all_nets[network][hostname]['mtu'],
214 self.all_nets[network][hostname]['state']
215 )
216 logger_cli.info(
Alex Savatieiev588b2c42019-03-11 10:39:21 -0500217 " {0:17} {1}".format(hostname.split('.')[0], _text)
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600218 )
219
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600220
221 def create_html_report(self, filename):
222 """
223 Create static html showing network schema-like report
224
225 :return: none
226 """
227 logger_cli.info("### Generating report to '{}'".format(filename))
228 _report = reporter.ReportToFile(
229 reporter.HTMLNetworkReport(),
230 filename
231 )
232 _report({
233 "nodes": self.nodes,
234 "diffs": {}
235 })
236 logger_cli.info("-> Done")