| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 1 | import ipaddress | 
|  | 2 | import json | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 3 | from copy import deepcopy | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 4 |  | 
|  | 5 | from cfg_checker.common import logger_cli | 
|  | 6 | from cfg_checker.common.exception import InvalidReturnException | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 7 | from cfg_checker.common.exception import ConfigException | 
|  | 8 | from cfg_checker.common.exception import KubeException | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 9 | from cfg_checker.modules.network.network_errors import NetworkErrors | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 10 | from cfg_checker.nodes import SaltNodes, KubeNodes | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 11 |  | 
|  | 12 | # TODO: use templated approach | 
|  | 13 | # net interface structure should be the same | 
|  | 14 | _if_item = { | 
|  | 15 | "name": "unnamed interface", | 
|  | 16 | "mac": "", | 
|  | 17 | "routes": {}, | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 18 | "proto": "", | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 19 | "ip": [], | 
|  | 20 | "parameters": {} | 
|  | 21 | } | 
|  | 22 |  | 
|  | 23 | # collection of configurations | 
|  | 24 | _network_item = { | 
|  | 25 | "runtime": {}, | 
|  | 26 | "config": {}, | 
|  | 27 | "reclass": {} | 
|  | 28 | } | 
|  | 29 |  | 
|  | 30 |  | 
|  | 31 | class NetworkMapper(object): | 
|  | 32 | RECLASS = "reclass" | 
|  | 33 | CONFIG = "config" | 
|  | 34 | RUNTIME = "runtime" | 
|  | 35 |  | 
| Alex | e9908f7 | 2020-05-19 16:04:53 -0500 | [diff] [blame] | 36 | def __init__( | 
|  | 37 | self, | 
| Alex | 9a4ad21 | 2020-10-01 18:04:25 -0500 | [diff] [blame] | 38 | config, | 
| Alex | e9908f7 | 2020-05-19 16:04:53 -0500 | [diff] [blame] | 39 | errors_class=None, | 
|  | 40 | skip_list=None, | 
|  | 41 | skip_list_file=None | 
|  | 42 | ): | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 43 | logger_cli.info("# Initializing mapper") | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 44 | self.env_config = config | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 45 | # init networks and nodes | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 46 | self.networks = {} | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 47 | self.nodes = self.master.get_nodes( | 
| Alex | e9908f7 | 2020-05-19 16:04:53 -0500 | [diff] [blame] | 48 | skip_list=skip_list, | 
|  | 49 | skip_list_file=skip_list_file | 
|  | 50 | ) | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 51 | self.cluster = self.master.get_info() | 
|  | 52 | self.domain = self.master.domain | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 53 | # init and pre-populate interfaces | 
|  | 54 | self.interfaces = {k: {} for k in self.nodes} | 
|  | 55 | # Init errors class | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 56 | if errors_class: | 
|  | 57 | self.errors = errors_class | 
|  | 58 | else: | 
|  | 59 | logger_cli.debug("... init error logs folder") | 
|  | 60 | self.errors = NetworkErrors() | 
|  | 61 |  | 
|  | 62 | # adding net data to tree | 
|  | 63 | def _add_data(self, _list, _n, _h, _d): | 
|  | 64 | if _n not in _list: | 
|  | 65 | _list[_n] = {} | 
|  | 66 | _list[_n][_h] = [_d] | 
|  | 67 | elif _h not in _list[_n]: | 
|  | 68 | # there is no such host, just create it | 
|  | 69 | _list[_n][_h] = [_d] | 
|  | 70 | else: | 
|  | 71 | # there is such host... this is an error | 
|  | 72 | self.errors.add_error( | 
|  | 73 | self.errors.NET_DUPLICATE_IF, | 
|  | 74 | host=_h, | 
|  | 75 | dup_if=_d['name'] | 
|  | 76 | ) | 
|  | 77 | _list[_n][_h].append(_d) | 
|  | 78 |  | 
|  | 79 | # TODO: refactor map creation. Build one map instead of two separate | 
|  | 80 | def _map_network_for_host(self, host, if_class, net_list, data): | 
|  | 81 | # filter networks for this IF IP | 
|  | 82 | _nets = [n for n in net_list.keys() if if_class.ip in n] | 
|  | 83 | _masks = [n.netmask for n in _nets] | 
|  | 84 | if len(_nets) > 1: | 
|  | 85 | # There a multiple network found for this IP, Error | 
|  | 86 | self.errors.add_error( | 
|  | 87 | self.errors.NET_SUBNET_INTERSECT, | 
|  | 88 | host=host, | 
|  | 89 | ip=str(if_class.exploded), | 
|  | 90 | networks="; ".join([str(_n) for _n in _nets]) | 
|  | 91 | ) | 
|  | 92 | # check mask match | 
|  | 93 | if len(_nets) > 0 and if_class.netmask not in _masks: | 
|  | 94 | self.errors.add_error( | 
|  | 95 | self.errors.NET_MASK_MISMATCH, | 
|  | 96 | host=host, | 
|  | 97 | if_name=data['name'], | 
|  | 98 | if_cidr=if_class.exploded, | 
|  | 99 | if_mapped_networks=", ".join([str(_n) for _n in _nets]) | 
|  | 100 | ) | 
|  | 101 |  | 
|  | 102 | if len(_nets) < 1: | 
|  | 103 | self._add_data(net_list, if_class.network, host, data) | 
|  | 104 | else: | 
|  | 105 | # add all data | 
|  | 106 | for net in _nets: | 
|  | 107 | self._add_data(net_list, net, host, data) | 
|  | 108 |  | 
|  | 109 | return net_list | 
|  | 110 |  | 
|  | 111 | def _map_reclass_networks(self): | 
|  | 112 | # class uses nodes from self.nodes dict | 
|  | 113 | _reclass = {} | 
|  | 114 | # Get required pillars | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 115 | self.master.get_specific_pillar_for_nodes("linux:network") | 
|  | 116 | for node in self.master.nodes.keys(): | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 117 | # check if this node | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 118 | if not self.master.is_node_available(node): | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 119 | continue | 
|  | 120 | # get the reclass value | 
| Alex | 9a4ad21 | 2020-10-01 18:04:25 -0500 | [diff] [blame] | 121 | _pillar = \ | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 122 | self.master.nodes[node]['pillars']['linux']['network'] | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 123 | # we should be ready if there is no interface in reclass for a node | 
| Alex | 92e07ce | 2019-05-31 16:00:03 -0500 | [diff] [blame] | 124 | # for example on APT node | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 125 | if 'interface' in _pillar: | 
|  | 126 | _pillar = _pillar['interface'] | 
|  | 127 | else: | 
|  | 128 | logger_cli.info( | 
|  | 129 | "... node '{}' skipped, no IF section in reclass".format( | 
|  | 130 | node | 
|  | 131 | ) | 
|  | 132 | ) | 
|  | 133 | continue | 
| Alex | 92e07ce | 2019-05-31 16:00:03 -0500 | [diff] [blame] | 134 |  | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 135 | # build map based on IPs and save info too | 
| Alex | 3bc95f6 | 2020-03-05 17:00:04 -0600 | [diff] [blame] | 136 | for if_name, _dat in _pillar.items(): | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 137 | # get proper IF name | 
|  | 138 | _if_name = if_name if 'name' not in _dat else _dat['name'] | 
|  | 139 | # place it | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 140 | if _if_name not in self.interfaces[node]: | 
|  | 141 | self.interfaces[node][_if_name] = deepcopy(_network_item) | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 142 | self.interfaces[node][_if_name]['reclass'] = deepcopy(_dat) | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 143 | # map network if any | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 144 | if 'address' in _dat: | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 145 | _if = ipaddress.IPv4Interface( | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 146 | _dat['address'] + '/' + _dat['netmask'] | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 147 | ) | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 148 | _dat['name'] = _if_name | 
|  | 149 | _dat['ifs'] = [_if] | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 150 |  | 
|  | 151 | _reclass = self._map_network_for_host( | 
|  | 152 | node, | 
|  | 153 | _if, | 
|  | 154 | _reclass, | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 155 | _dat | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 156 | ) | 
|  | 157 |  | 
|  | 158 | return _reclass | 
|  | 159 |  | 
|  | 160 | def _map_configured_networks(self): | 
|  | 161 | # class uses nodes from self.nodes dict | 
|  | 162 | _confs = {} | 
|  | 163 |  | 
| Alex | 92e07ce | 2019-05-31 16:00:03 -0500 | [diff] [blame] | 164 | # TODO: parse /etc/network/interfaces | 
|  | 165 |  | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 166 | return _confs | 
|  | 167 |  | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 168 | def _map_runtime_networks(self, result): | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 169 | # class uses nodes from self.nodes dict | 
|  | 170 | _runtime = {} | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 171 | for key in self.master.nodes.keys(): | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 172 | # check if we are to work with this node | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 173 | if not self.master.is_node_available(key): | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 174 | continue | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 175 | # due to much data to be passed from master, | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 176 | # it is happening in order | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 177 | if key in result: | 
|  | 178 | _text = result[key] | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 179 | if '{' in _text and '}' in _text: | 
|  | 180 | _text = _text[_text.find('{'):] | 
|  | 181 | else: | 
|  | 182 | raise InvalidReturnException( | 
|  | 183 | "Non-json object returned: '{}'".format( | 
|  | 184 | _text | 
|  | 185 | ) | 
|  | 186 | ) | 
|  | 187 | _dict = json.loads(_text[_text.find('{'):]) | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 188 | self.master.nodes[key]['routes'] = _dict.pop("routes") | 
|  | 189 | self.master.nodes[key]['networks'] = _dict | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 190 | else: | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 191 | self.master.nodes[key]['networks'] = {} | 
|  | 192 | self.master.nodes[key]['routes'] = {} | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 193 | logger_cli.debug("... {} has {} networks".format( | 
|  | 194 | key, | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 195 | len(self.master.nodes[key]['networks'].keys()) | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 196 | )) | 
|  | 197 | logger_cli.info("-> done collecting networks data") | 
|  | 198 |  | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 199 | logger_cli.info("-> mapping runtime network IPs") | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 200 | # match interfaces by IP subnets | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 201 | for host, node_data in self.master.nodes.items(): | 
|  | 202 | if not self.master.is_node_available(host): | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 203 | continue | 
|  | 204 |  | 
| Alex | 3bc95f6 | 2020-03-05 17:00:04 -0600 | [diff] [blame] | 205 | for net_name, net_data in node_data['networks'].items(): | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 206 | # cut net name | 
|  | 207 | _i = net_name.find('@') | 
|  | 208 | _name = net_name if _i < 0 else net_name[:_i] | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 209 | # get ips and calculate subnets | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 210 | if _name in ['lo']: | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 211 | # skip the localhost | 
|  | 212 | continue | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 213 | else: | 
|  | 214 | # add collected data to interface storage | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 215 | if _name not in self.interfaces[host]: | 
|  | 216 | self.interfaces[host][_name] = \ | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 217 | deepcopy(_network_item) | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 218 | self.interfaces[host][_name]['runtime'] = \ | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 219 | deepcopy(net_data) | 
|  | 220 |  | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 221 | #  get data and make sure that wide mask goes first | 
|  | 222 | _ip4s = sorted( | 
|  | 223 | net_data['ipv4'], | 
|  | 224 | key=lambda s: s[s.index('/'):] | 
|  | 225 | ) | 
|  | 226 | for _ip_str in _ip4s: | 
|  | 227 | # create interface class | 
|  | 228 | _if = ipaddress.IPv4Interface(_ip_str) | 
|  | 229 | # check if this is a VIP | 
|  | 230 | # ...all those will have /32 mask | 
|  | 231 | net_data['vip'] = None | 
|  | 232 | if _if.network.prefixlen == 32: | 
|  | 233 | net_data['vip'] = str(_if.exploded) | 
|  | 234 | if 'name' not in net_data: | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 235 | net_data['name'] = _name | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 236 | if 'ifs' not in net_data: | 
|  | 237 | net_data['ifs'] = [_if] | 
|  | 238 | # map it | 
|  | 239 | _runtime = self._map_network_for_host( | 
|  | 240 | host, | 
|  | 241 | _if, | 
|  | 242 | _runtime, | 
|  | 243 | net_data | 
|  | 244 | ) | 
|  | 245 | else: | 
|  | 246 | # data is already there, just add VIP | 
|  | 247 | net_data['ifs'].append(_if) | 
|  | 248 |  | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 249 | def process_interface(lvl, interface, tree, res): | 
|  | 250 | # get childs for each root | 
|  | 251 | # tree row item (<if_name>, [<parents>], [<childs>]) | 
|  | 252 | if lvl not in tree: | 
|  | 253 | # - no level - add it | 
|  | 254 | tree[lvl] = {} | 
|  | 255 | # there is such interface in this level? | 
|  | 256 | if interface not in tree[lvl]: | 
|  | 257 | # - IF not present | 
| Alex | f3dbe86 | 2019-10-07 15:17:04 -0500 | [diff] [blame] | 258 | _n = '' | 
|  | 259 | if interface not in res: | 
|  | 260 | _n = 'unknown IF' | 
|  | 261 | _p = None | 
|  | 262 | _c = None | 
|  | 263 | else: | 
|  | 264 | # -- get parents, add | 
|  | 265 | _p = res[interface]['lower'] | 
|  | 266 | # -- get childs, add | 
|  | 267 | _c = res[interface]['upper'] | 
|  | 268 |  | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 269 | # if None, put empty list | 
|  | 270 | _p = _p if _p else [] | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 271 | # if None, put empty list | 
|  | 272 | _c = _c if _c else [] | 
|  | 273 | tree[lvl].update({ | 
|  | 274 | interface: { | 
| Alex | f3dbe86 | 2019-10-07 15:17:04 -0500 | [diff] [blame] | 275 | "note": _n, | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 276 | "parents": _p, | 
|  | 277 | "children": _c, | 
|  | 278 | "size": len(_p) if len(_p) > len(_c) else len(_c) | 
|  | 279 | } | 
|  | 280 | }) | 
|  | 281 | for p_if in tree[lvl][interface]["parents"]: | 
|  | 282 | # -- cycle: execute process for next parent, lvl-1 | 
|  | 283 | process_interface(lvl-1, p_if, tree, res) | 
|  | 284 | for c_if in tree[lvl][interface]["children"]: | 
|  | 285 | # -- cycle: execute process for next child, lvl+1 | 
|  | 286 | process_interface(lvl+1, c_if, tree, res) | 
|  | 287 | else: | 
|  | 288 | # - IF present - exit (been here already) | 
|  | 289 | return | 
|  | 290 |  | 
|  | 291 | def _put(cNet, cIndex, _list): | 
| Alex | f3dbe86 | 2019-10-07 15:17:04 -0500 | [diff] [blame] | 292 | _added = False | 
|  | 293 | _actual_index = -1 | 
|  | 294 | # Check list len | 
|  | 295 | _len = len(_list) | 
|  | 296 | if cIndex >= _len: | 
|  | 297 | # grow list to meet index | 
|  | 298 | _list = _list + [''] * (cIndex - _len + 1) | 
|  | 299 | _len = len(_list) | 
|  | 300 |  | 
|  | 301 | for _cI in range(cIndex, _len): | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 302 | # add child per index | 
|  | 303 | # if space is free | 
|  | 304 | if not _list[_cI]: | 
|  | 305 | _list[_cI] = cNet | 
| Alex | f3dbe86 | 2019-10-07 15:17:04 -0500 | [diff] [blame] | 306 | _added = True | 
|  | 307 | _actual_index = _cI | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 308 | break | 
| Alex | f3dbe86 | 2019-10-07 15:17:04 -0500 | [diff] [blame] | 309 | if not _added: | 
|  | 310 | # grow list by one entry | 
|  | 311 | _list = _list + [cNet] | 
|  | 312 | _actual_index = len(_list) - 1 | 
|  | 313 | return _actual_index, _list | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 314 |  | 
|  | 315 | # build network hierachy | 
|  | 316 | nr = node_data['networks'] | 
|  | 317 | # walk interface tree | 
|  | 318 | for _ifname in node_data['networks']: | 
|  | 319 | _tree = {} | 
|  | 320 | _level = 0 | 
|  | 321 | process_interface(_level, _ifname, _tree, nr) | 
|  | 322 | # save tree for node/if | 
|  | 323 | node_data['networks'][_ifname]['tree'] = _tree | 
|  | 324 |  | 
|  | 325 | # debug, print built tree | 
|  | 326 | # logger_cli.debug("# '{}'".format(_ifname)) | 
| Alex | 3bc95f6 | 2020-03-05 17:00:04 -0600 | [diff] [blame] | 327 | lvls = list(_tree.keys()) | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 328 | lvls.sort() | 
|  | 329 | n = len(lvls) | 
|  | 330 | m = max([len(_tree[k].keys()) for k in _tree.keys()]) | 
|  | 331 | matrix = [["" for i in range(m)] for j in range(n)] | 
|  | 332 | x = 0 | 
|  | 333 | while True: | 
|  | 334 | _lv = lvls.pop(0) | 
|  | 335 | # get all interfaces on this level | 
| Alex | 3bc95f6 | 2020-03-05 17:00:04 -0600 | [diff] [blame] | 336 | nets = iter(_tree[_lv].keys()) | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 337 | while True: | 
|  | 338 | y = 0 | 
|  | 339 | # get next interface | 
| Alex | 3bc95f6 | 2020-03-05 17:00:04 -0600 | [diff] [blame] | 340 | try: | 
|  | 341 | _net = next(nets) | 
|  | 342 | except StopIteration: | 
|  | 343 | break | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 344 | # all nets | 
|  | 345 | _a = [_net] | 
|  | 346 | # put current interface if this is only one left | 
|  | 347 | if not _tree[_lv][_net]['children']: | 
|  | 348 | if _net not in matrix[x]: | 
| Alex | f3dbe86 | 2019-10-07 15:17:04 -0500 | [diff] [blame] | 349 | _, matrix[x] = _put( | 
|  | 350 | _net, | 
|  | 351 | y, | 
|  | 352 | matrix[x] | 
|  | 353 | ) | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 354 | y += 1 | 
|  | 355 | else: | 
|  | 356 | # get all nets with same child | 
|  | 357 | for _c in _tree[_lv][_net]['children']: | 
|  | 358 | for _o_net in nets: | 
|  | 359 | if _c in _tree[_lv][_o_net]['children']: | 
|  | 360 | _a.append(_o_net) | 
|  | 361 | # flush collected nets | 
|  | 362 | for idx in range(len(_a)): | 
|  | 363 | if _a[idx] in matrix[x]: | 
|  | 364 | # there is such interface on this level | 
|  | 365 | # get index | 
|  | 366 | _nI = matrix[x].index(_a[idx]) | 
| Alex | f3dbe86 | 2019-10-07 15:17:04 -0500 | [diff] [blame] | 367 | _, matrix[x+1] = _put( | 
|  | 368 | _c, | 
|  | 369 | _nI, | 
|  | 370 | matrix[x+1] | 
|  | 371 | ) | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 372 | else: | 
|  | 373 | # there is no such interface | 
|  | 374 | # add it | 
| Alex | f3dbe86 | 2019-10-07 15:17:04 -0500 | [diff] [blame] | 375 | _t, matrix[x] = _put( | 
|  | 376 | _a[idx], | 
|  | 377 | 0, | 
|  | 378 | matrix[x] | 
|  | 379 | ) | 
|  | 380 | # also, put child | 
|  | 381 | _, matrix[x+1] = _put( | 
|  | 382 | _c, | 
|  | 383 | _t, | 
|  | 384 | matrix[x+1] | 
|  | 385 | ) | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 386 | # remove collected nets from processing | 
|  | 387 | if _a[idx] in nets: | 
|  | 388 | nets.remove(_a[idx]) | 
|  | 389 | y += len(_a) | 
|  | 390 | if not nets: | 
|  | 391 | x += 1 | 
|  | 392 | break | 
|  | 393 | if not lvls: | 
|  | 394 | break | 
|  | 395 |  | 
|  | 396 | lines = [] | 
|  | 397 | _columns = [len(max([i for i in li])) for li in matrix] | 
|  | 398 | for idx_y in range(m): | 
|  | 399 | line = "" | 
|  | 400 | for idx_x in range(n): | 
| Alex | 9b2c1d1 | 2020-03-19 09:32:35 -0500 | [diff] [blame] | 401 | _len = _columns[idx_x] if _columns[idx_x] else 1 | 
|  | 402 | _fmt = "{" + ":{}".format(_len) + "} " | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 403 | line += _fmt.format(matrix[idx_x][idx_y]) | 
|  | 404 | lines.append(line) | 
|  | 405 | node_data['networks'][_ifname]['matrix'] = matrix | 
|  | 406 | node_data['networks'][_ifname]['lines'] = lines | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 407 | return _runtime | 
|  | 408 |  | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 409 |  | 
|  | 410 | class SaltNetworkMapper(NetworkMapper): | 
|  | 411 | def __init__( | 
|  | 412 | self, | 
|  | 413 | config, | 
|  | 414 | errors_class=None, | 
|  | 415 | skip_list=None, | 
|  | 416 | skip_list_file=None | 
|  | 417 | ): | 
|  | 418 | self.master = SaltNodes(config) | 
|  | 419 | super(SaltNetworkMapper, self).__init__( | 
|  | 420 | config, | 
|  | 421 | errors_class=errors_class, | 
|  | 422 | skip_list=skip_list, | 
|  | 423 | skip_list_file=skip_list_file | 
|  | 424 | ) | 
|  | 425 |  | 
|  | 426 | def get_script_output(self): | 
|  | 427 | """ | 
|  | 428 | Get runtime networks by executing script on nodes | 
|  | 429 | """ | 
|  | 430 | logger_cli.info("# Mapping node runtime network data") | 
|  | 431 | self.master.prepare_script_on_active_nodes("ifs_data.py") | 
|  | 432 | _result = self.master.execute_script_on_active_nodes( | 
|  | 433 | "ifs_data.py", | 
|  | 434 | args="json" | 
|  | 435 | ) | 
|  | 436 |  | 
|  | 437 | return _result | 
|  | 438 |  | 
|  | 439 | def map_networks(self): | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 440 | logger_cli.info("-> Mapping reclass networks") | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 441 | self.map_network(self.RECLASS) | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 442 | logger_cli.info("-> Mapping runtime networks") | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 443 | self.map_network(self.RUNTIME) | 
|  | 444 |  | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 445 | def map_network(self, source): | 
|  | 446 | # maps target network using given source | 
|  | 447 | _networks = None | 
|  | 448 |  | 
|  | 449 | if source == self.RECLASS: | 
|  | 450 | _networks = self._map_reclass_networks() | 
|  | 451 | elif source == self.CONFIG: | 
|  | 452 | _networks = self._map_configured_networks() | 
|  | 453 | elif source == self.RUNTIME: | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 454 | _r = self.get_script_output() | 
|  | 455 | _networks = self._map_runtime_networks(_r) | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 456 |  | 
|  | 457 | self.networks[source] = _networks | 
|  | 458 | return _networks | 
|  | 459 |  | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 460 | def create_map(self, skip_keywords=None): | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 461 | """Create all needed elements for map output | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 462 |  | 
|  | 463 | :return: none | 
|  | 464 | """ | 
|  | 465 | _runtime = self.networks[self.RUNTIME] | 
|  | 466 | _reclass = self.networks[self.RECLASS] | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 467 |  | 
|  | 468 | # main networks, target vars | 
|  | 469 | _map = {} | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 470 | # No matter of proto, at least one IP will be present for the network | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 471 | # we interested in, since we are to make sure that L3 level | 
|  | 472 | # is configured according to reclass model | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 473 | for network in _reclass: | 
|  | 474 | # shortcuts | 
|  | 475 | _net = str(network) | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 476 | _map[_net] = {} | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 477 | if network not in _runtime: | 
|  | 478 | # reclass has network that not found in runtime | 
|  | 479 | self.errors.add_error( | 
|  | 480 | self.errors.NET_NO_RUNTIME_NETWORK, | 
|  | 481 | reclass_net=str(network) | 
|  | 482 | ) | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 483 | logger_cli.warn( | 
|  | 484 | "WARN: {}: {}".format( | 
|  | 485 | " No runtime network ", str(network) | 
|  | 486 | ) | 
|  | 487 | ) | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 488 | continue | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 489 | # hostnames | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 490 | names = sorted(_runtime[network].keys()) | 
|  | 491 | for hostname in names: | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 492 | _notes = [] | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 493 | node = hostname.split('.')[0] | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 494 | if not self.master.is_node_available(hostname, log=False): | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 495 | logger_cli.info( | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 496 | "    {0:8} {1}".format(node, "node not available") | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 497 | ) | 
|  | 498 | # add non-responsive node erorr | 
|  | 499 | self.errors.add_error( | 
|  | 500 | self.errors.NET_NODE_NON_RESPONSIVE, | 
|  | 501 | host=hostname | 
|  | 502 | ) | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 503 | _notes.append( | 
|  | 504 | self.errors.get_error_type_text( | 
|  | 505 | self.errors.NET_NODE_NON_RESPONSIVE | 
|  | 506 | ) | 
|  | 507 | ) | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 508 | continue | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 509 | # lookup interface name on node using network CIDR | 
|  | 510 | _if_name = _runtime[network][hostname][0]["name"] | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 511 | _raw = self.interfaces[hostname][_if_name]['runtime'] | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 512 | # get proper reclass | 
|  | 513 | _r = self.interfaces[hostname][_if_name]['reclass'] | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 514 | _if_name_suffix = "" | 
|  | 515 | # get the proto value | 
| Alex | 3b8e543 | 2019-06-11 15:21:59 -0500 | [diff] [blame] | 516 | if _r: | 
|  | 517 | _if_rc = "" | 
|  | 518 | else: | 
|  | 519 | self.errors.add_error( | 
|  | 520 | self.errors.NET_NODE_UNEXPECTED_IF, | 
|  | 521 | host=hostname, | 
|  | 522 | if_name=_if_name | 
|  | 523 | ) | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 524 | _notes.append( | 
|  | 525 | self.errors.get_error_type_text( | 
|  | 526 | self.errors.NET_NODE_UNEXPECTED_IF | 
|  | 527 | ) | 
|  | 528 | ) | 
| Alex | 3b8e543 | 2019-06-11 15:21:59 -0500 | [diff] [blame] | 529 | _if_rc = "*" | 
|  | 530 |  | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 531 | if "proto" in _r: | 
|  | 532 | _proto = _r['proto'] | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 533 | else: | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 534 | _proto = "-" | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 535 |  | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 536 | if "type" in _r: | 
|  | 537 | _if_name_suffix += _r["type"] | 
|  | 538 | if "use_interfaces" in _r: | 
|  | 539 | _if_name_suffix += "->" + ",".join(_r["use_interfaces"]) | 
|  | 540 |  | 
|  | 541 | if _if_name_suffix: | 
|  | 542 | _if_name_suffix = "({})".format(_if_name_suffix) | 
|  | 543 |  | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 544 | # get gate and routes if proto is static | 
|  | 545 | if _proto == 'static': | 
|  | 546 | # get the gateway for current net | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 547 | _routes = self.master.nodes[hostname]['routes'] | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 548 | _route = _routes[_net] if _net in _routes else None | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 549 | # get the default gateway | 
|  | 550 | if 'default' in _routes: | 
|  | 551 | _d_gate = ipaddress.IPv4Address( | 
|  | 552 | _routes['default']['gateway'] | 
|  | 553 | ) | 
|  | 554 | else: | 
|  | 555 | _d_gate = None | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 556 | _d_gate_str = str(_d_gate) if _d_gate else "No default!" | 
|  | 557 | # match route with default | 
|  | 558 | if not _route: | 
|  | 559 | _gate = "?" | 
|  | 560 | else: | 
|  | 561 | _gate = _route['gateway'] if _route['gateway'] else "-" | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 562 | else: | 
|  | 563 | # in case of manual and dhcp, no check possible | 
|  | 564 | _gate = "-" | 
|  | 565 | _d_gate = "-" | 
| Alex | 4067f00 | 2019-06-11 10:47:16 -0500 | [diff] [blame] | 566 | _d_gate_str = "-" | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 567 | # iterate through interfaces | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 568 | _a = _runtime[network][hostname] | 
|  | 569 | for _host in _a: | 
|  | 570 | for _if in _host['ifs']: | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 571 | _ip_str = str(_if.exploded) | 
| Alex | ab232e4 | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 572 | _gate_error = "" | 
|  | 573 | _up_error = "" | 
|  | 574 | _mtu_error = "" | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 575 |  | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 576 | # Match gateway | 
| Alex | ab232e4 | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 577 | if _proto == 'static': | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 578 | # default reclass gate | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 579 | _r_gate = "-" | 
|  | 580 | if "gateway" in _r: | 
|  | 581 | _r_gate = _r["gateway"] | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 582 |  | 
| Alex | ab232e4 | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 583 | # if values not match, put * | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 584 | if _gate != _r_gate and _d_gate_str != _r_gate: | 
|  | 585 | # if values not match, check if default match | 
| Alex | 3b8e543 | 2019-06-11 15:21:59 -0500 | [diff] [blame] | 586 | self.errors.add_error( | 
|  | 587 | self.errors.NET_UNEXPECTED_GATEWAY, | 
|  | 588 | host=hostname, | 
|  | 589 | if_name=_if_name, | 
|  | 590 | ip=_ip_str, | 
|  | 591 | gateway=_gate | 
|  | 592 | ) | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 593 | _notes.append( | 
|  | 594 | self.errors.get_error_type_text( | 
|  | 595 | self.errors.NET_UNEXPECTED_GATEWAY | 
|  | 596 | ) | 
|  | 597 | ) | 
| Alex | ab232e4 | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 598 | _gate_error = "*" | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 599 |  | 
|  | 600 | # IF status in reclass | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 601 | _e = "enabled" | 
| Alex | ab232e4 | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 602 | if _e not in _r: | 
| Alex | 3b8e543 | 2019-06-11 15:21:59 -0500 | [diff] [blame] | 603 | self.errors.add_error( | 
|  | 604 | self.errors.NET_NO_RC_IF_STATUS, | 
|  | 605 | host=hostname, | 
|  | 606 | if_name=_if_name | 
|  | 607 | ) | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 608 | _notes.append( | 
|  | 609 | self.errors.get_error_type_text( | 
|  | 610 | self.errors.NET_NO_RC_IF_STATUS | 
|  | 611 | ) | 
|  | 612 | ) | 
| Alex | ab232e4 | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 613 | _up_error = "*" | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 614 |  | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 615 | _rc_mtu = _r['mtu'] if 'mtu' in _r else None | 
| Alex | ab232e4 | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 616 | _rc_mtu_s = "" | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 617 | # check if this is a VIP address | 
|  | 618 | # no checks needed if yes. | 
|  | 619 | if _host['vip'] != _ip_str: | 
|  | 620 | if _rc_mtu: | 
| Alex | 3b8e543 | 2019-06-11 15:21:59 -0500 | [diff] [blame] | 621 | _rc_mtu_s = str(_rc_mtu) | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 622 | # if there is an MTU value, match it | 
|  | 623 | if _host['mtu'] != _rc_mtu_s: | 
|  | 624 | self.errors.add_error( | 
|  | 625 | self.errors.NET_MTU_MISMATCH, | 
|  | 626 | host=hostname, | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 627 | if_name=_if_name, | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 628 | if_cidr=_ip_str, | 
|  | 629 | reclass_mtu=_rc_mtu, | 
|  | 630 | runtime_mtu=_host['mtu'] | 
|  | 631 | ) | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 632 | _notes.append( | 
|  | 633 | self.errors.get_error_type_text( | 
|  | 634 | self.errors.NET_MTU_MISMATCH | 
|  | 635 | ) | 
|  | 636 | ) | 
| Alex | b3dc859 | 2019-06-11 13:20:36 -0500 | [diff] [blame] | 637 | _rc_mtu_s = "/" + _rc_mtu_s | 
| Alex | ab232e4 | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 638 | _mtu_error = "*" | 
|  | 639 | else: | 
|  | 640 | # empty the matched value | 
|  | 641 | _rc_mtu_s = "" | 
| Alex | 3b8e543 | 2019-06-11 15:21:59 -0500 | [diff] [blame] | 642 | elif _host['mtu'] != '1500' and \ | 
|  | 643 | _proto not in ["-", "dhcp"]: | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 644 | # there is no MTU value in reclass | 
|  | 645 | # and runtime value is not default | 
|  | 646 | self.errors.add_error( | 
|  | 647 | self.errors.NET_MTU_EMPTY, | 
|  | 648 | host=hostname, | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 649 | if_name=_if_name, | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 650 | if_cidr=_ip_str, | 
|  | 651 | if_mtu=_host['mtu'] | 
|  | 652 | ) | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 653 | _notes.append( | 
|  | 654 | self.errors.get_error_type_text( | 
|  | 655 | self.errors.NET_MTU_EMPTY | 
|  | 656 | ) | 
|  | 657 | ) | 
| Alex | ab232e4 | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 658 | _mtu_error = "*" | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 659 | else: | 
|  | 660 | # this is a VIP | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 661 | _if_name = " "*7 | 
| Alex | 6b633ec | 2019-06-06 19:44:34 -0500 | [diff] [blame] | 662 | _if_name_suffix = "" | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 663 | _ip_str += " VIP" | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 664 | # Save all data | 
|  | 665 | _values = { | 
|  | 666 | "interface": _if_name, | 
|  | 667 | "interface_error": _if_rc, | 
|  | 668 | "interface_note": _if_name_suffix, | 
| Alex | 1839bbf | 2019-08-22 17:17:21 -0500 | [diff] [blame] | 669 | "interface_map": "\n".join(_host['lines']), | 
|  | 670 | "interface_matrix": _host['matrix'], | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 671 | "ip_address": _ip_str, | 
|  | 672 | "address_type": _proto, | 
|  | 673 | "rt_mtu": _host['mtu'], | 
|  | 674 | "rc_mtu": _rc_mtu_s, | 
|  | 675 | "mtu_error": _mtu_error, | 
|  | 676 | "status": _host['state'], | 
|  | 677 | "status_error": _up_error, | 
|  | 678 | "subnet_gateway": _gate, | 
|  | 679 | "subnet_gateway_error": _gate_error, | 
|  | 680 | "default_gateway": _d_gate_str, | 
|  | 681 | "raw_data": _raw, | 
|  | 682 | "error_note": " and ".join(_notes) | 
|  | 683 | } | 
|  | 684 | if node in _map[_net]: | 
|  | 685 | # add if to host | 
|  | 686 | _map[_net][node].append(_values) | 
|  | 687 | else: | 
|  | 688 | _map[_net][node] = [_values] | 
|  | 689 | _notes = [] | 
|  | 690 |  | 
|  | 691 | # save map | 
|  | 692 | self.map = _map | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 693 | return | 
|  | 694 |  | 
|  | 695 | def print_map(self): | 
|  | 696 | """ | 
|  | 697 | Create text report for CLI | 
|  | 698 |  | 
|  | 699 | :return: none | 
|  | 700 | """ | 
|  | 701 | logger_cli.info("# Networks") | 
|  | 702 | logger_cli.info( | 
|  | 703 | "    {0:8} {1:25} {2:25} {3:6} {4:10} {5:10} {6}/{7}".format( | 
|  | 704 | "Host", | 
|  | 705 | "IF", | 
|  | 706 | "IP", | 
|  | 707 | "Proto", | 
|  | 708 | "MTU", | 
|  | 709 | "State", | 
|  | 710 | "Gate", | 
|  | 711 | "Def.Gate" | 
|  | 712 | ) | 
|  | 713 | ) | 
|  | 714 | for network in self.map.keys(): | 
|  | 715 | logger_cli.info("-> {}".format(network)) | 
|  | 716 | for hostname in self.map[network].keys(): | 
|  | 717 | node = hostname.split('.')[0] | 
|  | 718 | _n = self.map[network][hostname] | 
|  | 719 | for _i in _n: | 
|  | 720 | # Host IF IP Proto MTU State Gate Def.Gate | 
|  | 721 | _text = "{:7} {:17} {:25} {:6} {:10} " \ | 
|  | 722 | "{:10} {} / {}".format( | 
|  | 723 | _i['interface'] + _i['interface_error'], | 
|  | 724 | _i['interface_note'], | 
|  | 725 | _i['ip_address'], | 
|  | 726 | _i['address_type'], | 
|  | 727 | _i['rt_mtu'] + _i['rc_mtu'] + _i['mtu_error'], | 
|  | 728 | _i['status'] + _i['status_error'], | 
|  | 729 | _i['subnet_gateway'] + | 
|  | 730 | _i['subnet_gateway_error'], | 
|  | 731 | _i['default_gateway'] | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 732 | ) | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 733 | logger_cli.info( | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 734 | "    {0:8} {1}".format( | 
|  | 735 | node, | 
|  | 736 | _text | 
|  | 737 | ) | 
| Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 738 | ) | 
| Alex | 836fac8 | 2019-08-22 13:36:16 -0500 | [diff] [blame] | 739 |  | 
|  | 740 | # logger_cli.info("\n# Other networks") | 
|  | 741 | # _other = [n for n in _runtime if n not in _reclass] | 
|  | 742 | # for network in _other: | 
|  | 743 | #     logger_cli.info("-> {}".format(str(network))) | 
|  | 744 | #     names = sorted(_runtime[network].keys()) | 
|  | 745 |  | 
|  | 746 | #     for hostname in names: | 
|  | 747 | #         for _n in _runtime[network][hostname]: | 
|  | 748 | #             _ifs = [str(ifs.ip) for ifs in _n['ifs']] | 
|  | 749 | #             _text = "{:25} {:25} {:6} {:10} {}".format( | 
|  | 750 | #                 _n['name'], | 
|  | 751 | #                 ", ".join(_ifs), | 
|  | 752 | #                 "-", | 
|  | 753 | #                 _n['mtu'], | 
|  | 754 | #                 _n['state'] | 
|  | 755 | #             ) | 
|  | 756 | #             logger_cli.info( | 
|  | 757 | #                 "    {0:8} {1}".format(hostname.split('.')[0], _text) | 
|  | 758 | #             ) | 
|  | 759 | # logger_cli.info("\n") | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 760 | return | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 761 |  | 
|  | 762 |  | 
|  | 763 | class KubeNetworkMapper(NetworkMapper): | 
|  | 764 | def __init__( | 
|  | 765 | self, | 
|  | 766 | config, | 
|  | 767 | errors_class=None, | 
|  | 768 | skip_list=None, | 
|  | 769 | skip_list_file=None | 
|  | 770 | ): | 
|  | 771 | self.master = KubeNodes(config) | 
| Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame] | 772 | self.daemonset = None | 
| Alex | 205546c | 2020-12-30 19:22:30 -0600 | [diff] [blame] | 773 | super(KubeNetworkMapper, self).__init__( | 
|  | 774 | config, | 
|  | 775 | errors_class=errors_class, | 
|  | 776 | skip_list=skip_list, | 
|  | 777 | skip_list_file=skip_list_file | 
|  | 778 | ) | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 779 |  | 
| Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame] | 780 | def get_daemonset(self): | 
|  | 781 | if not self.daemonset: | 
|  | 782 | _d = self.master.prepare_daemonset("daemonset_template.yaml") | 
|  | 783 |  | 
|  | 784 | # wait for daemonset, normally less than 60 sec for all | 
|  | 785 | # but still, let us give it 10 second per pod | 
|  | 786 | _timeout = self.master.nodes.__len__() * 10 | 
|  | 787 | if not self.master.wait_for_daemonset(_d, timeout=_timeout): | 
|  | 788 | raise KubeException("Daemonset deployment fail") | 
|  | 789 | self.daemonset = _d | 
|  | 790 | return self.daemonset | 
|  | 791 |  | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 792 | def get_script_output(self, script, args=None): | 
|  | 793 | """ | 
|  | 794 | Get runtime network by creating DaemonSet with Host network parameter | 
|  | 795 | """ | 
|  | 796 | # prepare daemonset | 
|  | 797 | logger_cli.info("-> Preparing daemonset to get node info") | 
| Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame] | 798 | _daemonset = self.get_daemonset() | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 799 | logger_cli.info("-> Running script on daemonset") | 
|  | 800 | # exec script on all pods in daemonset | 
|  | 801 | _result = self.master.execute_script_on_daemon_set( | 
|  | 802 | _daemonset, | 
|  | 803 | script, | 
|  | 804 | args=args | 
|  | 805 | ) | 
|  | 806 |  | 
|  | 807 | # delete daemonset | 
| Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame] | 808 | # TODO: handle daemonset delete properly | 
|  | 809 | # self.master.delete_daemonset(_daemonset) | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 810 |  | 
|  | 811 | return _result | 
|  | 812 |  | 
|  | 813 | def map_networks(self): | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 814 | logger_cli.info("-> Mapping runtime networks") | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 815 | self.map_network(self.RUNTIME) | 
|  | 816 |  | 
|  | 817 | def map_network(self, source): | 
| Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame] | 818 | # if network type is mapped - just return it | 
|  | 819 | if source in self.networks: | 
|  | 820 | return self.networks[source] | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 821 | # maps target network using given source | 
|  | 822 | _networks = None | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 823 | if source == self.RUNTIME: | 
|  | 824 | logger_cli.info("# Mapping node runtime network data") | 
|  | 825 | _r = self.get_script_output("ifs_data.py", args="json") | 
|  | 826 | _networks = self._map_runtime_networks(_r) | 
|  | 827 | else: | 
|  | 828 | raise ConfigException( | 
|  | 829 | "Network type not supported in 'Kube': '{}'".format(source) | 
|  | 830 | ) | 
|  | 831 |  | 
|  | 832 | self.networks[source] = _networks | 
|  | 833 | return _networks | 
|  | 834 |  | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 835 | def create_map(self, skip_keywords=None): | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 836 | """Create all needed elements for map output | 
|  | 837 |  | 
|  | 838 | :return: none | 
|  | 839 | """ | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 840 | # shortcut | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 841 | _runtime = self.networks[self.RUNTIME] | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 842 | # networks to skip | 
|  | 843 | _net_skip_list = [] | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 844 | # main networks, target vars | 
|  | 845 | _map = {} | 
|  | 846 | # No matter of proto, at least one IP will be present for the network | 
|  | 847 | # we interested in, since we are to make sure that L3 level | 
|  | 848 | # is configured according to reclass model | 
|  | 849 | for network in _runtime: | 
|  | 850 | # shortcuts | 
|  | 851 | _net = str(network) | 
|  | 852 | _map[_net] = {} | 
|  | 853 | # hostnames | 
|  | 854 | names = sorted(_runtime[network].keys()) | 
|  | 855 | for hostname in names: | 
|  | 856 | _notes = [] | 
|  | 857 | node = hostname.split('.')[0] | 
|  | 858 | if not self.master.is_node_available(hostname, log=False): | 
|  | 859 | logger_cli.info( | 
|  | 860 | "    {0:8} {1}".format(node, "node not available") | 
|  | 861 | ) | 
|  | 862 | # add non-responsive node erorr | 
|  | 863 | self.errors.add_error( | 
|  | 864 | self.errors.NET_NODE_NON_RESPONSIVE, | 
|  | 865 | host=hostname | 
|  | 866 | ) | 
|  | 867 | _notes.append( | 
|  | 868 | self.errors.get_error_type_text( | 
|  | 869 | self.errors.NET_NODE_NON_RESPONSIVE | 
|  | 870 | ) | 
|  | 871 | ) | 
|  | 872 | continue | 
|  | 873 | # lookup interface name on node using network CIDR | 
|  | 874 | _if_name = _runtime[network][hostname][0]["name"] | 
|  | 875 | _raw = self.interfaces[hostname][_if_name]['runtime'] | 
|  | 876 | _if_name_suffix = "" | 
|  | 877 | _a = _runtime[network][hostname] | 
|  | 878 | for _host in _a: | 
|  | 879 | for _if in _host['ifs']: | 
|  | 880 | _ip_str = str(_if.exploded) | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 881 | # Make sure we print VIP properly | 
|  | 882 | if _host['vip'] == _ip_str: | 
|  | 883 | _if_name = " "*7 | 
|  | 884 | _if_name_suffix = "" | 
|  | 885 | _ip_str += " VIP" | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 886 |  | 
|  | 887 | # Save all data | 
|  | 888 | _values = { | 
|  | 889 | "interface": _if_name, | 
|  | 890 | "interface_note": _if_name_suffix, | 
|  | 891 | "interface_map": "\n".join(_host['lines']), | 
|  | 892 | "interface_matrix": _host['matrix'], | 
|  | 893 | "ip_address": _ip_str, | 
|  | 894 | "rt_mtu": _host['mtu'], | 
|  | 895 | "status": _host['state'], | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 896 | "type": _host['type'], | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 897 | "raw_data": _raw, | 
|  | 898 | } | 
|  | 899 | if node in _map[_net]: | 
|  | 900 | # add if to host | 
|  | 901 | _map[_net][node].append(_values) | 
|  | 902 | else: | 
|  | 903 | _map[_net][node] = [_values] | 
|  | 904 | _notes = [] | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 905 | # process skips: if key substring found in interface name | 
|  | 906 | # then skip the whole network. | 
|  | 907 | if any([True for _w in skip_keywords if _w in _if_name]): | 
|  | 908 | _net_skip_list.append(_net) | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 909 |  | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 910 | # remove skipped networks from list | 
|  | 911 | _net_skip_list = list(set(_net_skip_list)) | 
|  | 912 | for _net in _net_skip_list: | 
|  | 913 | _map.pop(_net) | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 914 | # save map | 
|  | 915 | self.map = _map | 
|  | 916 | return | 
|  | 917 |  | 
|  | 918 | def print_map(self): | 
|  | 919 | """ | 
|  | 920 | Create text report for CLI | 
|  | 921 |  | 
|  | 922 | :return: none | 
|  | 923 | """ | 
|  | 924 | logger_cli.info("# Networks") | 
|  | 925 | logger_cli.info( | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 926 | "    {0:47} {1:12} {2:25} {3:5} {4:4}".format( | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 927 | "Host", | 
|  | 928 | "IF", | 
|  | 929 | "IP", | 
|  | 930 | "MTU", | 
|  | 931 | "State" | 
|  | 932 | ) | 
|  | 933 | ) | 
|  | 934 | for network in self.map.keys(): | 
|  | 935 | logger_cli.info("-> {}".format(network)) | 
|  | 936 | for hostname in self.map[network].keys(): | 
|  | 937 | node = hostname.split('.')[0] | 
|  | 938 | _n = self.map[network][hostname] | 
|  | 939 | for _i in _n: | 
|  | 940 | # Host IF IP Proto MTU State Gate Def.Gate | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 941 | _text = "{:10} {:2} {:25} {:5} {:4}".format( | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 942 | _i['interface'], | 
|  | 943 | _i['interface_note'], | 
|  | 944 | _i['ip_address'], | 
|  | 945 | _i['rt_mtu'], | 
|  | 946 | _i['status'] | 
|  | 947 | ) | 
|  | 948 | logger_cli.info( | 
| Alex | 3cdb1bd | 2021-09-10 15:51:11 -0500 | [diff] [blame] | 949 | "    {0:47} {1}".format( | 
| Alex | 1f90e7b | 2021-09-03 15:31:28 -0500 | [diff] [blame] | 950 | node, | 
|  | 951 | _text | 
|  | 952 | ) | 
|  | 953 | ) | 
|  | 954 | return |