]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown2/ifupdown/utils.py
setup.py: bump version to 3.0.0 and update changelog
[mirror_ifupdown2.git] / ifupdown2 / ifupdown / utils.py
CommitLineData
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 10import os
679e6567 11import re
a193d8d1
JF
12import shlex
13import fcntl
a4a53f4b 14import signal
a193d8d1
JF
15import logging
16import subprocess
a4a53f4b
JF
17
18from functools import partial
82908a2d 19from ipaddr import IPNetwork, IPAddress
a4a53f4b 20
d486dd0d
JF
21try:
22 from ifupdown2.ifupdown.iface import *
23
70a6640c 24 import ifupdown2.ifupdown.policymanager as policymanager
d486dd0d
JF
25 import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
26except 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
33def signal_handler_f(ps, sig, frame):
34 if ps:
35 ps.send_signal(sig)
36 if sig == signal.SIGINT:
37 raise KeyboardInterrupt
14dc390d 38
39class 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
464fcntl.fcntl(utils.DEVNULL, fcntl.F_SETFD, fcntl.FD_CLOEXEC)