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