]>
Commit | Line | Data |
---|---|---|
35681c06 | 1 | #!/usr/bin/env python3 |
904908bc | 2 | # |
d486dd0d | 3 | # Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. |
904908bc RP |
4 | # Author: Roopa Prabhu, roopa@cumulusnetworks.com |
5 | # | |
6 | # utils -- | |
7 | # helper class | |
8 | # | |
a193d8d1 | 9 | |
d40e96ee | 10 | import os |
679e6567 | 11 | import re |
a193d8d1 JF |
12 | import shlex |
13 | import fcntl | |
a4a53f4b | 14 | import signal |
a193d8d1 JF |
15 | import logging |
16 | import subprocess | |
a4a53f4b JF |
17 | |
18 | from functools import partial | |
82908a2d | 19 | from ipaddr import IPNetwork, IPAddress |
a4a53f4b | 20 | |
d486dd0d JF |
21 | try: |
22 | from ifupdown2.ifupdown.iface import * | |
23 | ||
70a6640c | 24 | import ifupdown2.ifupdown.policymanager as policymanager |
d486dd0d JF |
25 | import ifupdown2.ifupdown.ifupdownflags as ifupdownflags |
26 | except ImportError: | |
27 | from ifupdown.iface import * | |
28 | ||
70a6640c | 29 | import ifupdown.policymanager as policymanager |
d486dd0d JF |
30 | import ifupdown.ifupdownflags as ifupdownflags |
31 | ||
22b49c28 | 32 | |
a4a53f4b JF |
33 | def signal_handler_f(ps, sig, frame): |
34 | if ps: | |
35 | ps.send_signal(sig) | |
36 | if sig == signal.SIGINT: | |
37 | raise KeyboardInterrupt | |
14dc390d | 38 | |
39 | class utils(): | |
a193d8d1 JF |
40 | logger = logging.getLogger('ifupdown') |
41 | DEVNULL = open(os.devnull, 'w') | |
70a6640c | 42 | vlan_aware_bridge_address_support = None |
14dc390d | 43 | |
594fb088 JF |
44 | _string_values = { |
45 | "on": True, | |
46 | "yes": True, | |
47 | "1": True, | |
d486dd0d | 48 | "fast": True, |
594fb088 JF |
49 | "off": False, |
50 | "no": False, | |
51 | "0": False, | |
d486dd0d | 52 | "slow": False |
594fb088 JF |
53 | } |
54 | ||
55 | _binary_bool = { | |
56 | True: "1", | |
57 | False: "0", | |
58 | } | |
59 | ||
60 | _yesno_bool = { | |
61 | True: 'yes', | |
62 | False: 'no' | |
63 | } | |
64 | ||
65 | _onoff_bool = { | |
66 | 'yes': 'on', | |
67 | 'no': 'off' | |
68 | } | |
69 | ||
d486dd0d JF |
70 | _onoff_onezero = { |
71 | '1' : 'on', | |
72 | '0' : 'off' | |
73 | } | |
74 | ||
75 | _yesno_onezero = { | |
76 | '1' : 'yes', | |
77 | '0' : 'no' | |
78 | } | |
79 | ||
80 | """ | |
81 | Set debian path as default path for all the commands. | |
82 | If command not present in debian path, search for the | |
83 | commands in the other system directories. | |
84 | This search is carried out to handle different locations | |
85 | on different distros. | |
86 | If the command is not found in any of the system | |
87 | directories, command execution will fail because we have | |
88 | set default path same as debian path. | |
89 | """ | |
90 | bridge_cmd = '/sbin/bridge' | |
91 | ip_cmd = '/bin/ip' | |
92 | brctl_cmd = '/sbin/brctl' | |
93 | pidof_cmd = '/bin/pidof' | |
94 | service_cmd = '/usr/sbin/service' | |
95 | sysctl_cmd = '/sbin/sysctl' | |
96 | modprobe_cmd = '/sbin/modprobe' | |
97 | pstree_cmd = '/usr/bin/pstree' | |
98 | ss_cmd = '/bin/ss' | |
99 | vrrpd_cmd = '/usr/sbin/vrrpd' | |
100 | ifplugd_cmd = '/usr/sbin/ifplugd' | |
101 | mstpctl_cmd = '/sbin/mstpctl' | |
102 | ethtool_cmd = '/sbin/ethtool' | |
103 | systemctl_cmd = '/bin/systemctl' | |
104 | dpkg_cmd = '/usr/bin/dpkg' | |
105 | ||
223ba5af | 106 | logger.info("utils init command paths") |
d486dd0d JF |
107 | for cmd in ['bridge', |
108 | 'ip', | |
109 | 'brctl', | |
110 | 'pidof', | |
111 | 'service', | |
112 | 'sysctl', | |
113 | 'modprobe', | |
114 | 'pstree', | |
115 | 'ss', | |
116 | 'vrrpd', | |
117 | 'ifplugd', | |
118 | 'mstpctl', | |
119 | 'ethtool', | |
120 | 'systemctl', | |
121 | 'dpkg' | |
122 | ]: | |
123 | if os.path.exists(vars()[cmd + '_cmd']): | |
124 | continue | |
125 | for path in ['/bin/', | |
126 | '/sbin/', | |
127 | '/usr/bin/', | |
128 | '/usr/sbin/',]: | |
129 | if os.path.exists(path + cmd): | |
130 | vars()[cmd + '_cmd'] = path + cmd | |
131 | else: | |
132 | logger.debug('warning: path %s not found: %s won\'t be usable' % (path + cmd, cmd)) | |
133 | ||
3b01ed76 | 134 | mac_translate_tab = str.maketrans(":.-,", " ") |
223ba5af JF |
135 | |
136 | @classmethod | |
137 | def mac_str_to_int(cls, hw_address): | |
138 | mac = 0 | |
139 | if hw_address: | |
3b01ed76 | 140 | pass |
223ba5af JF |
141 | for i in hw_address.translate(cls.mac_translate_tab).split(): |
142 | mac = mac << 8 | |
143 | mac += int(i, 16) | |
144 | return mac | |
145 | ||
d486dd0d JF |
146 | @staticmethod |
147 | def get_onff_from_onezero(value): | |
148 | if value in utils._onoff_onezero: | |
149 | return utils._onoff_onezero[value] | |
150 | return value | |
151 | ||
152 | @staticmethod | |
153 | def get_yesno_from_onezero(value): | |
154 | if value in utils._yesno_onezero: | |
155 | return utils._yesno_onezero[value] | |
156 | return value | |
157 | ||
594fb088 JF |
158 | @staticmethod |
159 | def get_onoff_bool(value): | |
160 | if value in utils._onoff_bool: | |
161 | return utils._onoff_bool[value] | |
162 | return value | |
163 | ||
164 | @staticmethod | |
70a6640c JF |
165 | def get_boolean_from_string(value, default=False): |
166 | return utils._string_values.get(value, default) | |
594fb088 JF |
167 | |
168 | @staticmethod | |
169 | def get_yesno_boolean(bool): | |
170 | return utils._yesno_bool[bool] | |
171 | ||
172 | @staticmethod | |
173 | def boolean_support_binary(value): | |
174 | return utils._binary_bool[utils.get_boolean_from_string(value)] | |
175 | ||
176 | @staticmethod | |
177 | def is_binary_bool(value): | |
178 | return value == '0' or value == '1' | |
179 | ||
180 | @staticmethod | |
181 | def support_yesno_attrs(attrsdict, attrslist, ifaceobj=None): | |
182 | if ifaceobj: | |
183 | for attr in attrslist: | |
184 | value = ifaceobj.get_attr_value_first(attr) | |
185 | if value and not utils.is_binary_bool(value): | |
186 | if attr in attrsdict: | |
187 | bool = utils.get_boolean_from_string(attrsdict[attr]) | |
188 | attrsdict[attr] = utils.get_yesno_boolean(bool) | |
189 | else: | |
190 | for attr in attrslist: | |
191 | if attr in attrsdict: | |
192 | attrsdict[attr] = utils.boolean_support_binary(attrsdict[attr]) | |
193 | ||
d486dd0d JF |
194 | @staticmethod |
195 | def get_int_from_boolean_and_string(value): | |
196 | try: | |
197 | return int(value) | |
198 | except: | |
199 | return int(utils.get_boolean_from_string(value)) | |
200 | ||
201 | @staticmethod | |
202 | def strip_hwaddress(hwaddress): | |
203 | if hwaddress and hwaddress.startswith("ether"): | |
204 | hwaddress = hwaddress[5:].strip() | |
205 | return hwaddress.lower() if hwaddress else hwaddress | |
206 | # we need to "normalize" the user provided MAC so it can match with | |
207 | # what we have in the cache (data retrieved via a netlink dump by | |
208 | # nlmanager). nlmanager return all macs in lower-case | |
209 | ||
14dc390d | 210 | @classmethod |
211 | def importName(cls, modulename, name): | |
212 | """ Import a named object """ | |
213 | try: | |
214 | module = __import__(modulename, globals(), locals(), [name]) | |
215 | except ImportError: | |
216 | return None | |
217 | return getattr(module, name) | |
d40e96ee | 218 | |
219 | @classmethod | |
220 | def lockFile(cls, lockfile): | |
221 | try: | |
222 | fp = os.open(lockfile, os.O_CREAT | os.O_TRUNC | os.O_WRONLY) | |
223 | fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB) | |
a193d8d1 | 224 | fcntl.fcntl(fp, fcntl.F_SETFD, fcntl.FD_CLOEXEC) |
d40e96ee | 225 | except IOError: |
226 | return False | |
227 | return True | |
228 | ||
679e6567 RP |
229 | @classmethod |
230 | def parse_iface_range(cls, name): | |
eba4da6e RP |
231 | # eg: swp1.[2-100] |
232 | # return (prefix, range-start, range-end) | |
233 | # eg return ("swp1.", 1, 20, ".100") | |
234 | range_match = re.match("^([\w]+)\[([\d]+)-([\d]+)\]([\.\w]+)", name) | |
679e6567 RP |
235 | if range_match: |
236 | range_groups = range_match.groups() | |
237 | if range_groups[1] and range_groups[2]: | |
238 | return (range_groups[0], int(range_groups[1], 10), | |
eba4da6e RP |
239 | int(range_groups[2], 10), range_groups[3]) |
240 | else: | |
241 | # eg: swp[1-20].100 | |
242 | # return (prefix, range-start, range-end, suffix) | |
243 | # eg return ("swp", 1, 20, ".100") | |
244 | range_match = re.match("^([\w\.]+)\[([\d]+)-([\d]+)\]", name) | |
245 | if range_match: | |
246 | range_groups = range_match.groups() | |
247 | if range_groups[1] and range_groups[2]: | |
248 | return (range_groups[0], int(range_groups[1], 10), | |
249 | int(range_groups[2], 10)) | |
679e6567 RP |
250 | return None |
251 | ||
252 | @classmethod | |
253 | def expand_iface_range(cls, name): | |
254 | ifacenames = [] | |
eba4da6e RP |
255 | irange = cls.parse_iface_range(name) |
256 | if irange: | |
257 | if len(irange) == 3: | |
258 | # eg swp1.[2-4], r = "swp1.", 2, 4) | |
259 | for i in range(irange[1], irange[2]): | |
260 | ifacenames.append('%s%d' %(irange[0], i)) | |
261 | elif len(irange) == 4: | |
262 | for i in range(irange[1], irange[2]): | |
263 | # eg swp[2-4].100, r = ("swp", 2, 4, ".100") | |
264 | ifacenames.append('%s%d%s' %(irange[0], i, irange[3])) | |
679e6567 RP |
265 | return ifacenames |
266 | ||
eba4da6e RP |
267 | @classmethod |
268 | def is_ifname_range(cls, name): | |
269 | if '[' in name or ']' in name: | |
270 | return True | |
271 | return False | |
272 | ||
d3ad131e ST |
273 | @classmethod |
274 | def check_ifname_size_invalid(cls, name=''): | |
275 | """ IFNAMSIZ in include/linux/if.h is 16 so we check this """ | |
276 | IFNAMSIZ = 16 | |
277 | if len(name) > IFNAMSIZ - 1: | |
278 | return True | |
279 | else: | |
280 | return False | |
d40e96ee | 281 | |
a4a53f4b JF |
282 | @classmethod |
283 | def enable_subprocess_signal_forwarding(cls, ps, sig): | |
284 | signal.signal(sig, partial(signal_handler_f, ps)) | |
285 | ||
286 | @classmethod | |
287 | def disable_subprocess_signal_forwarding(cls, sig): | |
288 | signal.signal(sig, signal.SIG_DFL) | |
289 | ||
a193d8d1 JF |
290 | @classmethod |
291 | def _log_command_exec(cls, cmd, stdin): | |
292 | if stdin: | |
293 | cls.logger.info('executing %s [%s]' % (cmd, stdin)) | |
294 | else: | |
295 | cls.logger.info('executing %s' % cmd) | |
296 | ||
297 | @classmethod | |
298 | def _format_error(cls, cmd, cmd_returncode, cmd_output, stdin): | |
299 | if type(cmd) is list: | |
300 | cmd = ' '.join(cmd) | |
301 | if stdin: | |
302 | cmd = '%s [%s]' % (cmd, stdin) | |
303 | if cmd_output: | |
304 | return 'cmd \'%s\' failed: returned %d (%s)' % \ | |
305 | (cmd, cmd_returncode, cmd_output) | |
306 | else: | |
307 | return 'cmd \'%s\' failed: returned %d' % (cmd, cmd_returncode) | |
308 | ||
82908a2d JF |
309 | @classmethod |
310 | def get_normalized_ip_addr(cls, ifacename, ipaddrs): | |
311 | if not ipaddrs: return None | |
312 | if isinstance(ipaddrs, list): | |
313 | addrs = [] | |
314 | for ip in ipaddrs: | |
315 | if not ip: | |
316 | continue | |
ff59bbc3 JF |
317 | try: |
318 | addrs.append(str(IPNetwork(ip)) if '/' in ip else str(IPAddress(ip))) | |
319 | except Exception as e: | |
320 | cls.logger.warning('%s: %s' % (ifacename, e)) | |
82908a2d JF |
321 | return addrs |
322 | else: | |
323 | try: | |
324 | return str(IPNetwork(ipaddrs)) if '/' in ipaddrs else str(IPAddress(ipaddrs)) | |
325 | except Exception as e: | |
326 | cls.logger.warning('%s: %s' % (ifacename, e)) | |
327 | return ipaddrs | |
328 | ||
d486dd0d JF |
329 | @classmethod |
330 | def get_ip_objs(cls, module_name, ifname, addrs_list): | |
331 | addrs_obj_list = [] | |
332 | for a in addrs_list or []: | |
333 | try: | |
334 | addrs_obj_list.append(IPNetwork(a) if '/' in a else IPAddress(a)) | |
335 | except Exception as e: | |
336 | cls.logger.warning('%s: %s: %s' % (module_name, ifname, str(e))) | |
337 | return addrs_obj_list | |
338 | ||
339 | @classmethod | |
340 | def get_ip_obj(cls, module_name, ifname, addr): | |
341 | if addr: | |
342 | try: | |
343 | return IPNetwork(addr) if '/' in addr else IPAddress(addr) | |
344 | except Exception as e: | |
345 | cls.logger.warning('%s: %s: %s' % (module_name, ifname, str(e))) | |
346 | return None | |
347 | ||
22b49c28 JF |
348 | @classmethod |
349 | def is_addr_ip_allowed_on(cls, ifaceobj, syntax_check=False): | |
70a6640c JF |
350 | if cls.vlan_aware_bridge_address_support is None: |
351 | cls.vlan_aware_bridge_address_support = utils.get_boolean_from_string( | |
352 | policymanager.policymanager_api.get_module_globals( | |
353 | module_name='address', | |
354 | attr='vlan_aware_bridge_address_support' | |
355 | ), | |
356 | True | |
357 | ) | |
22b49c28 JF |
358 | msg = ('%s: ignoring ip address. Assigning an IP ' |
359 | 'address is not allowed on' % ifaceobj.name) | |
360 | if (ifaceobj.role & ifaceRole.SLAVE | |
361 | and not (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE)): | |
362 | up = None | |
363 | if ifaceobj.upperifaces: | |
364 | up = ifaceobj.upperifaces[0] | |
365 | msg = ('%s enslaved interfaces. %s' | |
366 | % (msg, ('%s is enslaved to %s' | |
367 | % (ifaceobj.name, up)) if up else '')).strip() | |
368 | if syntax_check: | |
369 | cls.logger.warning(msg) | |
370 | else: | |
371 | cls.logger.info(msg) | |
372 | return False | |
373 | elif (ifaceobj.link_kind & ifaceLinkKind.BRIDGE | |
70a6640c JF |
374 | and ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE |
375 | and not cls.vlan_aware_bridge_address_support | |
376 | ): | |
377 | msg = '%s bridge vlan aware interfaces' % msg | |
22b49c28 JF |
378 | if syntax_check: |
379 | cls.logger.warning(msg) | |
380 | else: | |
381 | cls.logger.info(msg) | |
382 | return False | |
383 | return True | |
384 | ||
a193d8d1 JF |
385 | @classmethod |
386 | def _execute_subprocess(cls, cmd, | |
387 | env=None, | |
388 | shell=False, | |
389 | close_fds=False, | |
390 | stdout=True, | |
391 | stdin=None, | |
bdbe0379 | 392 | stderr=subprocess.STDOUT): |
a193d8d1 JF |
393 | """ |
394 | exec's commands using subprocess Popen | |
395 | Args: | |
396 | cmd, should be shlex.split if not shell | |
397 | returns: output | |
398 | ||
399 | Note: close_fds=True is affecting performance (2~3 times slower) | |
400 | """ | |
401 | if ifupdownflags.flags.DRYRUN: | |
402 | return '' | |
403 | ||
404 | cmd_output = None | |
405 | try: | |
406 | ch = subprocess.Popen(cmd, | |
407 | env=env, | |
408 | shell=shell, | |
409 | close_fds=close_fds, | |
410 | stdin=subprocess.PIPE if stdin else None, | |
bdbe0379 | 411 | stdout=subprocess.PIPE if stdout else cls.DEVNULL, |
a193d8d1 JF |
412 | stderr=stderr) |
413 | utils.enable_subprocess_signal_forwarding(ch, signal.SIGINT) | |
414 | if stdout or stdin: | |
6ab519dd | 415 | cmd_output = ch.communicate(input=stdin.encode() if stdin else stdin)[0] |
a193d8d1 JF |
416 | cmd_returncode = ch.wait() |
417 | except Exception as e: | |
bdbe0379 | 418 | raise Exception('cmd \'%s\' failed (%s)' % (' '.join(cmd), str(e))) |
a193d8d1 JF |
419 | finally: |
420 | utils.disable_subprocess_signal_forwarding(signal.SIGINT) | |
e36ad206 JF |
421 | |
422 | cmd_output_string = cmd_output.decode() if cmd_output else cmd_output | |
423 | ||
a193d8d1 JF |
424 | if cmd_returncode != 0: |
425 | raise Exception(cls._format_error(cmd, | |
426 | cmd_returncode, | |
e36ad206 | 427 | cmd_output_string, |
a193d8d1 | 428 | stdin)) |
e36ad206 | 429 | return cmd_output_string |
a193d8d1 JF |
430 | |
431 | @classmethod | |
432 | def exec_user_command(cls, cmd, close_fds=False, stdout=True, | |
433 | stdin=None, stderr=subprocess.STDOUT): | |
434 | cls._log_command_exec(cmd, stdin) | |
435 | return cls._execute_subprocess(cmd, | |
436 | shell=True, | |
437 | close_fds=close_fds, | |
438 | stdout=stdout, | |
439 | stdin=stdin, | |
bdbe0379 | 440 | stderr=stderr) |
a193d8d1 JF |
441 | |
442 | @classmethod | |
443 | def exec_command(cls, cmd, env=None, close_fds=False, stdout=True, | |
444 | stdin=None, stderr=subprocess.STDOUT): | |
445 | cls._log_command_exec(cmd, stdin) | |
446 | return cls._execute_subprocess(shlex.split(cmd), | |
447 | env=env, | |
448 | close_fds=close_fds, | |
449 | stdout=stdout, | |
450 | stdin=stdin, | |
451 | stderr=stderr) | |
452 | ||
453 | @classmethod | |
454 | def exec_commandl(cls, cmdl, env=None, close_fds=False, stdout=True, | |
455 | stdin=None, stderr=subprocess.STDOUT): | |
456 | cls._log_command_exec(' '.join(cmdl), stdin) | |
457 | return cls._execute_subprocess(cmdl, | |
458 | env=env, | |
459 | close_fds=close_fds, | |
460 | stdout=stdout, | |
461 | stdin=stdin, | |
462 | stderr=stderr) | |
463 | ||
464 | fcntl.fcntl(utils.DEVNULL, fcntl.F_SETFD, fcntl.FD_CLOEXEC) |