]> git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/ifupdownaddons/modulebase.py
Fix error message on ifquery when sysctl bridge-stp-user-space
[mirror_ifupdown2.git] / ifupdown2 / ifupdownaddons / modulebase.py
1 #!/usr/bin/python
2 #
3 # Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
5 #
6
7 import os
8 import re
9 import logging
10 import traceback
11
12 try:
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
19 except 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
27
28 class NotSupported(Exception):
29 pass
30
31 class 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):
37 self.modulename = self.__class__.__name__
38 self.logger = logging.getLogger('ifupdown.' + self.modulename)
39
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
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
50 self._bridge_stp_user_space = None
51
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
91
92 def log_warn(self, str, ifaceobj=None):
93 """ log a warning if err str is not one of which we should ignore """
94 if not self.ignore_error(str) and not ifupdownflags.flags.IGNORE_ERRORS:
95 if self.logger.getEffectiveLevel() == logging.DEBUG:
96 traceback.print_stack()
97 traceback.print_exc()
98 self.logger.warn(str)
99 if ifaceobj:
100 ifaceobj.set_status(ifaceStatus.WARNING)
101 pass
102
103 def log_error(self, str, ifaceobj=None, raise_error=True):
104 """ log an err if err str is not one of which we should ignore and raise an exception """
105 if not self.ignore_error(str) and not ifupdownflags.flags.IGNORE_ERRORS:
106 if self.logger.getEffectiveLevel() == logging.DEBUG:
107 traceback.print_stack()
108 traceback.print_exc()
109 if raise_error:
110 if ifaceobj:
111 ifaceobj.set_status(ifaceStatus.ERROR)
112 raise Exception(str)
113 else:
114 self.logger.error(str)
115 else:
116 pass
117
118 def is_process_running(self, procName):
119 try:
120 utils.exec_command('%s -x %s' %
121 (utils.pidof_cmd, procName))
122 except:
123 return False
124 else:
125 return True
126
127 def get_ifaces_from_proc(self):
128 ifacenames = []
129 with open('/proc/net/dev') as f:
130 try:
131 lines = f.readlines()
132 for line in lines[2:]:
133 ifacenames.append(line.split()[0].strip(': '))
134 except:
135 raise
136 return ifacenames
137
138 def parse_regex(self, ifacename, expr, ifacenames=None):
139 try:
140 proc_ifacenames = self.get_ifaces_from_proc()
141 except:
142 self.logger.warn('%s: error reading ifaces from proc' %ifacename)
143
144 for proc_ifacename in proc_ifacenames:
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)))
151 if not ifacenames:
152 return
153 for ifacename in ifacenames:
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)))
160
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
171 def parse_glob(self, ifacename, expr):
172 errmsg = ('error parsing glob expression \'%s\'' %expr +
173 ' (supported glob syntax: swp1-10.300 or swp[1-10].300' +
174 ' or swp[1-10]sub[0-4].300')
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
181 regexs = self.glob_regexs
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
190 raise Exception('%s: error: unhandled glob expression %s\n%s' % (ifacename, expr,errmsg))
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:
214 raise Exception('%s: ' %ifacename + errmsg + '(unexpected len)')
215 prefix = mlist[0]
216 suffix = mlist[3]
217 start_index = int(mlist[1])
218 end_index = int(mlist[2])
219 for i in range(start_index, end_index + 1):
220 yield prefix + '%d' %i + suffix
221
222 else:
223 # Could not match anything.
224 self.logger.warn('%s: %s' %(ifacename, errmsg))
225 yield expr
226
227 def parse_port_list(self, ifacename, port_expr, ifacenames=None):
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
240 exprs = re.split(r'[\s\t]\s*', port_expr)
241 self.logger.debug('%s: evaluating port expr \'%s\''
242 %(ifacename, str(exprs)))
243 for expr in exprs:
244 if expr == 'noregex':
245 regex = 0
246 elif expr == 'noglob':
247 glob = 0
248 elif expr == 'regex':
249 regex = 1
250 elif expr == 'glob':
251 glob = 1
252 elif regex:
253 for port in self.parse_regex(ifacename, expr, ifacenames):
254 if port not in portlist:
255 portlist.append(port)
256 regex = 0
257 elif glob:
258 for port in self.parse_glob(ifacename, expr):
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):
268 if (ifupdownflags.flags.FORCE or re.search(r'exists', errmsg,
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)
278 if ifupdownflags.flags.DRYRUN:
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 """
310 utils.exec_command('%s %s=%s' %
311 (utils.sysctl_cmd, variable, value))
312
313 def sysctl_get(self, variable):
314 """ get value of sysctl variable """
315 output = utils.exec_command('%s %s' %
316 (utils.sysctl_cmd, variable))
317 split = output.split('=')
318 if len(split) > 1:
319 return split[1].strip()
320 return None
321
322 def systcl_get_net_bridge_stp_user_space(self):
323 if self._bridge_stp_user_space:
324 return self._bridge_stp_user_space
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
330 return self._bridge_stp_user_space
331
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
363 def dict_key_subset(self, a, b):
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):
368 """ returns list of all module attrs defined in the module _modinfo
369 dict
370 """
371 try:
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)
378 if 'aliases' in attrvals:
379 retattrs.extend(attrvals['aliases'])
380 return retattrs
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:
404 return {}
405
406 def get_attr_default_value(self, attrname):
407 return self.get_modinfo().get('attrs', {}).get(attrname, {}).get('default')
408
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
416 def _get_reserved_vlan_range(self):
417 start = end = 0
418 get_resvvlan = '/var/lib/ifupdown2/hooks/get_reserved_vlan_range.sh'
419 if not os.path.exists(get_resvvlan):
420 return (start, end)
421 try:
422 (s, e) = utils.exec_command(get_resvvlan).strip('\n').split('-')
423 start = int(s)
424 end = int(e)
425 except Exception, e:
426 self.logger.debug('%s failed (%s)' %(get_resvvlan, str(e)))
427 # ignore errors
428 pass
429 return (start, end)
430
431 def _handle_reserved_vlan(self, vlanid, logprefix='', end=-1):
432 """ Helper function to check and warn if the vlanid falls in the
433 reserved vlan range """
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
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
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)