]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown2/ifupdown/utils.py
addons: addressvirtual: keep macvlan down if link-down specified on lower device
[mirror_ifupdown2.git] / ifupdown2 / ifupdown / utils.py
CommitLineData
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 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
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
448fcntl.fcntl(utils.DEVNULL, fcntl.F_SETFD, fcntl.FD_CLOEXEC)