]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown2/ifupdownaddons/modulebase.py
Fix error message on ifquery when sysctl bridge-stp-user-space
[mirror_ifupdown2.git] / ifupdown2 / ifupdownaddons / modulebase.py
CommitLineData
15ef32ea
RP
1#!/usr/bin/python
2#
d486dd0d 3# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
15ef32ea
RP
4# Author: Roopa Prabhu, roopa@cumulusnetworks.com
5#
6
19f8bb0b 7import os
15ef32ea 8import re
15ef32ea 9import logging
15ef32ea 10import traceback
a4a53f4b 11
d486dd0d
JF
12try:
13 from ifupdown2.ifupdown.iface import *
14 from ifupdown2.ifupdown.utils import utils
15
16 import ifupdown2.ifupdown.exceptions as exceptions
17 import ifupdown2.ifupdown.policymanager as policymanager
18 import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
19except ImportError:
20 from ifupdown.iface import *
21 from ifupdown.utils import utils
22
23 import ifupdown.exceptions as exceptions
24 import ifupdown.policymanager as policymanager
25 import ifupdown.ifupdownflags as ifupdownflags
26
15ef32ea 27
b54179d2
RP
28class NotSupported(Exception):
29 pass
30
15ef32ea
RP
31class moduleBase(object):
32 """ Base class for ifupdown addon modules
33
34 Provides common infrastructure methods for all addon modules """
35
36 def __init__(self, *args, **kargs):
d486dd0d
JF
37 self.modulename = self.__class__.__name__
38 self.logger = logging.getLogger('ifupdown.' + self.modulename)
15ef32ea 39
717cee31
RP
40 # vrfs are a global concept and a vrf context can be applicable
41 # to all global vrf commands. Get the default vrf-exec-cmd-prefix
42 # here so that all modules can use it
43 self.vrf_exec_cmd_prefix = policymanager.policymanager_api.get_module_globals('vrf', attr='vrf-exec-cmd-prefix')
44
c6370b56
DW
45 # explanations are shown in parse_glob
46 self.glob_regexs = [re.compile(r"([A-Za-z0-9\-]+)\[(\d+)\-(\d+)\]([A-Za-z0-9\-]+)\[(\d+)\-(\d+)\](.*)"),
47 re.compile(r"([A-Za-z0-9\-]+[A-Za-z])(\d+)\-(\d+)(.*)"),
48 re.compile(r"([A-Za-z0-9\-]+)\[(\d+)\-(\d+)\](.*)")]
49
9d3f53c6
JF
50 self._bridge_stp_user_space = None
51
d486dd0d
JF
52 self.merge_modinfo_with_policy_files()
53
54 def merge_modinfo_with_policy_files(self):
55 """
56 update addons modinfo dictionary with system/user defined values in policy files
57 Any value can be updated except the module help "mhelp"
58
59 We also check if the policy attributes really exist to make sure someone is not
60 trying to "inject" new attributes to prevent breakages and security issue
61 """
62 attrs = dict(self.get_modinfo().get('attrs', {}))
63
64 if not attrs:
65 return
66
67 error_msg = 'this attribute doesn\'t exist or isn\'t supported'
68
69 # first check module_defaults
70 for key, value in policymanager.policymanager_api.get_module_defaults(self.modulename).items():
71 if not key in attrs:
72 self.logger.warning('%s: %s: %s' % (self.modulename, key, error_msg))
73 continue
74 attrs[key]['default'] = value
75
76 # then check module_globals (overrides module_defaults)
77 policy_modinfo = policymanager.policymanager_api.get_module_globals(self.modulename, '_modinfo')
78 if policy_modinfo:
79 policy_attrs = policy_modinfo.get('attrs', {})
80 update_attrs = dict()
81
82 for attr_name, attr_description in policy_attrs.items():
83 if attr_name not in attrs:
84 self.logger.warning('%s: %s: %s' % (self.modulename, attr_name, error_msg))
85 else:
86 update_attrs[attr_name] = attr_description
87
88 attrs.update(update_attrs)
89
90 return attrs
c6370b56 91
b3745e05 92 def log_warn(self, str, ifaceobj=None):
15ef32ea 93 """ log a warning if err str is not one of which we should ignore """
3fb52fa3 94 if not self.ignore_error(str) and not ifupdownflags.flags.IGNORE_ERRORS:
15ef32ea
RP
95 if self.logger.getEffectiveLevel() == logging.DEBUG:
96 traceback.print_stack()
d486dd0d 97 traceback.print_exc()
15ef32ea 98 self.logger.warn(str)
b3745e05
RP
99 if ifaceobj:
100 ifaceobj.set_status(ifaceStatus.WARNING)
15ef32ea
RP
101 pass
102
bf3eda91 103 def log_error(self, str, ifaceobj=None, raise_error=True):
15ef32ea 104 """ log an err if err str is not one of which we should ignore and raise an exception """
3fb52fa3 105 if not self.ignore_error(str) and not ifupdownflags.flags.IGNORE_ERRORS:
15ef32ea
RP
106 if self.logger.getEffectiveLevel() == logging.DEBUG:
107 traceback.print_stack()
d486dd0d 108 traceback.print_exc()
bf3eda91 109 if raise_error:
61bf5eef
RP
110 if ifaceobj:
111 ifaceobj.set_status(ifaceStatus.ERROR)
bf3eda91 112 raise Exception(str)
9df4f95b
JF
113 else:
114 self.logger.error(str)
15ef32ea
RP
115 else:
116 pass
117
fff589ea 118 def is_process_running(self, procName):
119 try:
d486dd0d
JF
120 utils.exec_command('%s -x %s' %
121 (utils.pidof_cmd, procName))
fff589ea 122 except:
123 return False
124 else:
125 return True
126
15ef32ea
RP
127 def get_ifaces_from_proc(self):
128 ifacenames = []
129 with open('/proc/net/dev') as f:
130 try:
131 lines = f.readlines()
0c8332bc 132 for line in lines[2:]:
15ef32ea
RP
133 ifacenames.append(line.split()[0].strip(': '))
134 except:
135 raise
136 return ifacenames
137
0c8332bc 138 def parse_regex(self, ifacename, expr, ifacenames=None):
15ef32ea
RP
139 try:
140 proc_ifacenames = self.get_ifaces_from_proc()
141 except:
0c8332bc
RP
142 self.logger.warn('%s: error reading ifaces from proc' %ifacename)
143
15ef32ea 144 for proc_ifacename in proc_ifacenames:
0c8332bc
RP
145 try:
146 if re.search(expr + '$', proc_ifacename):
147 yield proc_ifacename
148 except Exception, e:
149 raise Exception('%s: error searching regex \'%s\' in %s (%s)'
150 %(ifacename, expr, proc_ifacename, str(e)))
15ef32ea
RP
151 if not ifacenames:
152 return
153 for ifacename in ifacenames:
0c8332bc
RP
154 try:
155 if re.search(expr + '$', ifacename):
156 yield ifacename
157 except Exception, e:
158 raise Exception('%s: error searching regex \'%s\' in %s (%s)'
159 %(ifacename, expr, ifacename, str(e)))
15ef32ea 160
c6370b56
DW
161 def ifname_is_glob(self, ifname):
162 """
163 Used by iface where ifname could be swp7 or swp[1-10].300
164 """
165 if (self.glob_regexs[0].match(ifname) or
166 self.glob_regexs[1].match(ifname) or
167 self.glob_regexs[2].match(ifname)):
168 return True
169 return False
170
0c8332bc 171 def parse_glob(self, ifacename, expr):
15ef32ea 172 errmsg = ('error parsing glob expression \'%s\'' %expr +
139662ee
ST
173 ' (supported glob syntax: swp1-10.300 or swp[1-10].300' +
174 ' or swp[1-10]sub[0-4].300')
d486dd0d
JF
175
176 if ',' in expr:
177 self.logger.warn('%s: comma are not supported in glob: %s' % (ifacename, errmsg))
178 yield expr
179 return
180
c6370b56 181 regexs = self.glob_regexs
139662ee
ST
182
183 if regexs[0].match(expr):
184 # the first regex checks for exactly two levels of ranges defined only with square brackets
185 # (e.g. swpxyz[10-23]subqwe[0-4].100) to handle naming with two levels of port names.
186 m = regexs[0].match(expr)
187 mlist = m.groups()
188 if len(mlist) < 7:
189 # we have problems and should not continue
0c8332bc 190 raise Exception('%s: error: unhandled glob expression %s\n%s' % (ifacename, expr,errmsg))
139662ee
ST
191
192 prefix = mlist[0]
193 suffix = mlist[6]
194 start_index = int(mlist[1])
195 end_index = int(mlist[2])
196 sub_string = mlist[3]
197 start_sub = int(mlist[4])
198 end_sub = int(mlist[5])
199 for i in range(start_index, end_index + 1):
200 for j in range(start_sub, end_sub + 1):
201 yield prefix + '%d%s%d' % (i,sub_string,j) + suffix
202
203 elif regexs[1].match(expr) or regexs[2].match(expr):
204 # the second regex for 1 level with a range (e.g. swp10-14.100
205 # the third regex checks for 1 level with [] (e.g. swp[10-14].100)
206 start_index = 0
207 end_index = 0
208 if regexs[1].match(expr):
209 m = regexs[1].match(expr)
210 else:
211 m = regexs[2].match(expr)
212 mlist = m.groups()
213 if len(mlist) != 4:
0c8332bc 214 raise Exception('%s: ' %ifacename + errmsg + '(unexpected len)')
139662ee
ST
215 prefix = mlist[0]
216 suffix = mlist[3]
217 start_index = int(mlist[1])
218 end_index = int(mlist[2])
15ef32ea
RP
219 for i in range(start_index, end_index + 1):
220 yield prefix + '%d' %i + suffix
221
139662ee
ST
222 else:
223 # Could not match anything.
0c8332bc 224 self.logger.warn('%s: %s' %(ifacename, errmsg))
139662ee
ST
225 yield expr
226
0c8332bc 227 def parse_port_list(self, ifacename, port_expr, ifacenames=None):
15ef32ea
RP
228 """ parse port list containing glob and regex
229
230 Args:
231 port_expr (str): expression
232 ifacenames (list): list of interface names. This needs to be specified if the expression has a regular expression
233 """
234 regex = 0
235 glob = 0
236 portlist = []
237
238 if not port_expr:
239 return None
0c8332bc 240 exprs = re.split(r'[\s\t]\s*', port_expr)
77d9d664 241 self.logger.debug('%s: evaluating port expr \'%s\''
0c8332bc
RP
242 %(ifacename, str(exprs)))
243 for expr in exprs:
84ca006f
RP
244 if expr == 'noregex':
245 regex = 0
246 elif expr == 'noglob':
247 glob = 0
248 elif expr == 'regex':
15ef32ea
RP
249 regex = 1
250 elif expr == 'glob':
251 glob = 1
252 elif regex:
0c8332bc 253 for port in self.parse_regex(ifacename, expr, ifacenames):
15ef32ea
RP
254 if port not in portlist:
255 portlist.append(port)
256 regex = 0
257 elif glob:
9a8ad21a 258 for port in self.parse_glob(ifacename, expr):
15ef32ea
RP
259 portlist.append(port)
260 glob = 0
261 else:
262 portlist.append(expr)
263 if not portlist:
264 return None
265 return portlist
266
267 def ignore_error(self, errmsg):
fc5e1735 268 if (ifupdownflags.flags.FORCE or re.search(r'exists', errmsg,
15ef32ea
RP
269 re.IGNORECASE | re.MULTILINE)):
270 return True
271 return False
272
273 def write_file(self, filename, strexpr):
274 """ writes string to a file """
275 try:
276 self.logger.info('writing \'%s\'' %strexpr +
277 ' to file %s' %filename)
fc5e1735 278 if ifupdownflags.flags.DRYRUN:
15ef32ea
RP
279 return 0
280 with open(filename, 'w') as f:
281 f.write(strexpr)
282 except IOError, e:
283 self.logger.warn('error writing to file %s'
284 %filename + '(' + str(e) + ')')
285 return -1
286 return 0
287
288 def read_file(self, filename):
289 """ read file and return lines from the file """
290 try:
291 self.logger.info('reading \'%s\'' %filename)
292 with open(filename, 'r') as f:
293 return f.readlines()
294 except:
295 return None
296 return None
297
298 def read_file_oneline(self, filename):
299 """ reads and returns first line from the file """
300 try:
301 self.logger.info('reading \'%s\'' %filename)
302 with open(filename, 'r') as f:
303 return f.readline().strip('\n')
304 except:
305 return None
306 return None
307
308 def sysctl_set(self, variable, value):
309 """ set sysctl variable to value passed as argument """
d486dd0d
JF
310 utils.exec_command('%s %s=%s' %
311 (utils.sysctl_cmd, variable, value))
15ef32ea
RP
312
313 def sysctl_get(self, variable):
314 """ get value of sysctl variable """
d486dd0d
JF
315 output = utils.exec_command('%s %s' %
316 (utils.sysctl_cmd, variable))
8eb2cf9c
JF
317 split = output.split('=')
318 if len(split) > 1:
319 return split[1].strip()
320 return None
15ef32ea 321
9d3f53c6
JF
322 def systcl_get_net_bridge_stp_user_space(self):
323 if self._bridge_stp_user_space:
324 return self._bridge_stp_user_space
848bf7aa
AD
325 try:
326 self._bridge_stp_user_space = self.sysctl_get('net.bridge.bridge-stp-user-space')
327 except:
328 self._bridge_stp_user_space = 0
329
9d3f53c6
JF
330 return self._bridge_stp_user_space
331
15ef32ea
RP
332 def set_iface_attr(self, ifaceobj, attr_name, attr_valsetfunc,
333 prehook=None, prehookargs=None):
334 ifacename = ifaceobj.name
335 attrvalue = ifaceobj.get_attr_value_first(attr_name)
336 if attrvalue:
337 if prehook:
338 if prehookargs:
339 prehook(prehookargs)
340 else:
341 prehook(ifacename)
342 attr_valsetfunc(ifacename, attrvalue)
343
344 def query_n_update_ifaceobjcurr_attr(self, ifaceobj, ifaceobjcurr,
345 attr_name, attr_valgetfunc,
346 attr_valgetextraarg=None):
347 attrvalue = ifaceobj.get_attr_value_first(attr_name)
348 if not attrvalue:
349 return
350 if attr_valgetextraarg:
351 runningattrvalue = attr_valgetfunc(ifaceobj.name,
352 attr_valgetextraarg)
353 else:
354 runningattrvalue = attr_valgetfunc(ifaceobj.name)
355 if (not runningattrvalue or
356 (runningattrvalue != attrvalue)):
357 ifaceobjcurr.update_config_with_status(attr_name,
358 runningattrvalue, 1)
359 else:
360 ifaceobjcurr.update_config_with_status(attr_name,
361 runningattrvalue, 0)
362
d486dd0d 363 def dict_key_subset(self, a, b):
15ef32ea
RP
364 """ returns a list of differing keys """
365 return [x for x in a if x in b]
366
367 def get_mod_attrs(self):
1553a881
RP
368 """ returns list of all module attrs defined in the module _modinfo
369 dict
370 """
15ef32ea 371 try:
1553a881
RP
372 retattrs = []
373 attrsdict = self._modinfo.get('attrs')
374 for attrname, attrvals in attrsdict.iteritems():
375 if not attrvals or attrvals.get('deprecated'):
376 continue
377 retattrs.append(attrname)
a9633d05
JF
378 if 'aliases' in attrvals:
379 retattrs.extend(attrvals['aliases'])
1553a881 380 return retattrs
15ef32ea
RP
381 except:
382 return None
383
384 def get_mod_attr(self, attrname):
385 """ returns module attr info """
386 try:
387 return self._modinfo.get('attrs', {}).get(attrname)
388 except:
389 return None
390
391 def get_mod_subattr(self, attrname, subattrname):
392 """ returns module attrs defined in the module _modinfo dict"""
393 try:
394 return reduce(lambda d, k: d[k], ['attrs', attrname, subattrname],
395 self._modinfo)
396 except:
397 return None
398
399 def get_modinfo(self):
400 """ return module info """
401 try:
402 return self._modinfo
403 except:
d486dd0d
JF
404 return {}
405
406 def get_attr_default_value(self, attrname):
407 return self.get_modinfo().get('attrs', {}).get(attrname, {}).get('default')
15ef32ea 408
b6b8bd2b
JF
409 def get_overrides_ifupdown_scripts(self):
410 """ return the ifupdown scripts replaced by the current module """
411 try:
412 return self.overrides_ifupdown_scripts
413 except:
414 return []
415
2da58137
RP
416 def _get_reserved_vlan_range(self):
417 start = end = 0
2bba6c89 418 get_resvvlan = '/var/lib/ifupdown2/hooks/get_reserved_vlan_range.sh'
19f8bb0b
RP
419 if not os.path.exists(get_resvvlan):
420 return (start, end)
2da58137 421 try:
a193d8d1 422 (s, e) = utils.exec_command(get_resvvlan).strip('\n').split('-')
ad25e7bb
RP
423 start = int(s)
424 end = int(e)
19f8bb0b
RP
425 except Exception, e:
426 self.logger.debug('%s failed (%s)' %(get_resvvlan, str(e)))
2da58137
RP
427 # ignore errors
428 pass
429 return (start, end)
cd3059b8 430
d486dd0d 431 def _handle_reserved_vlan(self, vlanid, logprefix='', end=-1):
cd3059b8
RP
432 """ Helper function to check and warn if the vlanid falls in the
433 reserved vlan range """
d486dd0d
JF
434 error = False
435 invalid_vlan = vlanid
436
437 if self._resv_vlan_range[0] <= vlanid <= self._resv_vlan_range[1]:
438 error = True
439 elif end > 0:
440 if self._resv_vlan_range[0] <= end <= self._resv_vlan_range[1]:
441 error = True
442 invalid_vlan = end
443 elif vlanid < self._resv_vlan_range[0] and end > self._resv_vlan_range[1]:
444 error = True
445 invalid_vlan = self._resv_vlan_range[0]
446
447 if error:
448 raise exceptions.ReservedVlanException('%s: reserved vlan %d being used (reserved vlan range %d-%d)'
449 % (logprefix, invalid_vlan, self._resv_vlan_range[0], self._resv_vlan_range[1]))
450
451 return error
5828d8c5
RP
452
453 def _valid_ethaddr(self, ethaddr):
454 """ Check if address is 00:00:00:00:00:00 """
455 if not ethaddr or re.match('00:00:00:00:00:00', ethaddr):
456 return False
457 return True
d486dd0d
JF
458
459 def _get_vlan_id_from_ifacename(self, ifacename):
460 if '.' in ifacename:
461 vid_str = ifacename.split('.', 2)
462 vlen = len(vid_str)
463 if vlen == 2:
464 vid_str = vid_str[1]
465 elif vlen == 3:
466 vid_str = vid_str[2]
467 elif ifacename.startswith('vlan'):
468 vid_str = ifacename[4:]
469 else:
470 return -1
471 try:
472 vid = int(vid_str)
473 except:
474 return -1
475 return vid
476
477 def _get_vlan_id(self, ifaceobj):
478 """ Derives vlanid from iface name
479
480 Example:
481 Returns 1 for ifname vlan0001 returns 1
482 Returns 1 for ifname vlan1
483 Returns 1 for ifname eth0.1
484 Returns 100 for ifname eth0.1.100
485 Returns -1 if vlan id cannot be determined
486 """
487 vid_str = ifaceobj.get_attr_value_first('vlan-id')
488 try:
489 if vid_str: return int(vid_str)
490 except:
491 return -1
492
493 return self._get_vlan_id_from_ifacename(ifaceobj.name)