]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown2/ifupdown/utils.py
Revert "ifupdown2.conf: vlan_aware_bridge_address_support: allow ip on vlan-aware...
[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
24 import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
25except ImportError:
26 from ifupdown.iface import *
27
28 import ifupdown.ifupdownflags as ifupdownflags
29
22b49c28 30
a4a53f4b
JF
31def signal_handler_f(ps, sig, frame):
32 if ps:
33 ps.send_signal(sig)
34 if sig == signal.SIGINT:
35 raise KeyboardInterrupt
14dc390d 36
37class 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
435fcntl.fcntl(utils.DEVNULL, fcntl.F_SETFD, fcntl.FD_CLOEXEC)