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