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