]> git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/ifupdownaddons/modulebase.py
python3: logging: the 'warn' method is deprecated, use warning instead
[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 *
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:
21 from ifupdown.iface import *
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 if self.logger.getEffectiveLevel() == logging.DEBUG:
97 traceback.print_stack()
98 traceback.print_exc()
99 self.logger.warning(str)
100 if ifaceobj:
101 ifaceobj.set_status(ifaceStatus.WARNING)
102 pass
103
104 def log_error(self, str, ifaceobj=None, raise_error=True):
105 """ log an err if err str is not one of which we should ignore and raise an exception """
106 if not self.ignore_error(str) and not ifupdownflags.flags.IGNORE_ERRORS:
107 if self.logger.getEffectiveLevel() == logging.DEBUG:
108 traceback.print_stack()
109 traceback.print_exc()
110 if raise_error:
111 if ifaceobj:
112 ifaceobj.set_status(ifaceStatus.ERROR)
113 raise Exception(str)
114 else:
115 self.logger.error(str)
116 else:
117 pass
118
119 def is_process_running(self, procName):
120 try:
121 utils.exec_command('%s -x %s' %
122 (utils.pidof_cmd, procName))
123 except:
124 return False
125 else:
126 return True
127
128 def get_ifaces_from_proc(self):
129 ifacenames = []
130 with open('/proc/net/dev') as f:
131 try:
132 lines = f.readlines()
133 for line in lines[2:]:
134 ifacenames.append(line.split()[0].strip(': '))
135 except:
136 raise
137 return ifacenames
138
139 def parse_regex(self, ifacename, expr, ifacenames=None):
140 try:
141 proc_ifacenames = self.get_ifaces_from_proc()
142 except:
143 self.logger.warning('%s: error reading ifaces from proc' %ifacename)
144
145 for proc_ifacename in proc_ifacenames:
146 try:
147 if re.search(expr + '$', proc_ifacename):
148 yield proc_ifacename
149 except Exception as e:
150 raise Exception('%s: error searching regex \'%s\' in %s (%s)'
151 %(ifacename, expr, proc_ifacename, str(e)))
152 if not ifacenames:
153 return
154 for ifacename in ifacenames:
155 try:
156 if re.search(expr + '$', ifacename):
157 yield ifacename
158 except Exception as e:
159 raise Exception('%s: error searching regex \'%s\' in %s (%s)'
160 %(ifacename, expr, ifacename, str(e)))
161
162 def ifname_is_glob(self, ifname):
163 """
164 Used by iface where ifname could be swp7 or swp[1-10].300
165 """
166 if (self.glob_regexs[0].match(ifname) or
167 self.glob_regexs[1].match(ifname) or
168 self.glob_regexs[2].match(ifname)):
169 return True
170 return False
171
172 def parse_glob(self, ifacename, expr):
173 errmsg = ('error parsing glob expression \'%s\'' %expr +
174 ' (supported glob syntax: swp1-10.300 or swp[1-10].300' +
175 ' or swp[1-10]sub[0-4].300')
176
177 if ',' in expr:
178 self.logger.warning('%s: comma are not supported in glob: %s' % (ifacename, errmsg))
179 yield expr
180 return
181
182 regexs = self.glob_regexs
183
184 if regexs[0].match(expr):
185 # the first regex checks for exactly two levels of ranges defined only with square brackets
186 # (e.g. swpxyz[10-23]subqwe[0-4].100) to handle naming with two levels of port names.
187 m = regexs[0].match(expr)
188 mlist = m.groups()
189 if len(mlist) < 7:
190 # we have problems and should not continue
191 raise Exception('%s: error: unhandled glob expression %s\n%s' % (ifacename, expr,errmsg))
192
193 prefix = mlist[0]
194 suffix = mlist[6]
195 start_index = int(mlist[1])
196 end_index = int(mlist[2])
197 sub_string = mlist[3]
198 start_sub = int(mlist[4])
199 end_sub = int(mlist[5])
200 for i in range(start_index, end_index + 1):
201 for j in range(start_sub, end_sub + 1):
202 yield prefix + '%d%s%d' % (i,sub_string,j) + suffix
203
204 elif regexs[1].match(expr) or regexs[2].match(expr):
205 # the second regex for 1 level with a range (e.g. swp10-14.100
206 # the third regex checks for 1 level with [] (e.g. swp[10-14].100)
207 start_index = 0
208 end_index = 0
209 if regexs[1].match(expr):
210 m = regexs[1].match(expr)
211 else:
212 m = regexs[2].match(expr)
213 mlist = m.groups()
214 if len(mlist) != 4:
215 raise Exception('%s: ' %ifacename + errmsg + '(unexpected len)')
216 prefix = mlist[0]
217 suffix = mlist[3]
218 start_index = int(mlist[1])
219 end_index = int(mlist[2])
220 for i in range(start_index, end_index + 1):
221 yield prefix + '%d' %i + suffix
222
223 else:
224 # Could not match anything.
225 self.logger.warning('%s: %s' %(ifacename, errmsg))
226 yield expr
227
228 def parse_port_list(self, ifacename, port_expr, ifacenames=None):
229 """ parse port list containing glob and regex
230
231 Args:
232 port_expr (str): expression
233 ifacenames (list): list of interface names. This needs to be specified if the expression has a regular expression
234 """
235 regex = 0
236 glob = 0
237 portlist = []
238
239 if not port_expr:
240 return None
241 exprs = re.split(r'[\s\t]\s*', port_expr)
242 self.logger.debug('%s: evaluating port expr \'%s\''
243 %(ifacename, str(exprs)))
244 for expr in exprs:
245 if expr == 'noregex':
246 regex = 0
247 elif expr == 'noglob':
248 glob = 0
249 elif expr == 'regex':
250 regex = 1
251 elif expr == 'glob':
252 glob = 1
253 elif regex:
254 for port in self.parse_regex(ifacename, expr, ifacenames):
255 if port not in portlist:
256 portlist.append(port)
257 regex = 0
258 elif glob:
259 for port in self.parse_glob(ifacename, expr):
260 portlist.append(port)
261 glob = 0
262 else:
263 portlist.append(expr)
264 if not portlist:
265 return None
266 return portlist
267
268 def ignore_error(self, errmsg):
269 if (ifupdownflags.flags.FORCE or re.search(r'exists', errmsg,
270 re.IGNORECASE | re.MULTILINE)):
271 return True
272 return False
273
274 def write_file(self, filename, strexpr):
275 """ writes string to a file """
276 try:
277 self.logger.info('writing \'%s\'' %strexpr +
278 ' to file %s' %filename)
279 if ifupdownflags.flags.DRYRUN:
280 return 0
281 with open(filename, 'w') as f:
282 f.write(strexpr)
283 except IOError as e:
284 self.logger.warning('error writing to file %s'
285 %filename + '(' + str(e) + ')')
286 return -1
287 return 0
288
289 def read_file(self, filename):
290 """ read file and return lines from the file """
291 try:
292 self.logger.info('reading \'%s\'' %filename)
293 with open(filename, 'r') as f:
294 return f.readlines()
295 except:
296 return None
297 return None
298
299 def read_file_oneline(self, filename):
300 """ reads and returns first line from the file """
301 try:
302 self.logger.info('reading \'%s\'' %filename)
303 with open(filename, 'r') as f:
304 return f.readline().strip('\n')
305 except:
306 return None
307 return None
308
309 def sysctl_set(self, variable, value):
310 """ set sysctl variable to value passed as argument """
311 utils.exec_command('%s %s=%s' %
312 (utils.sysctl_cmd, variable, value))
313
314 def sysctl_get(self, variable):
315 """ get value of sysctl variable """
316 output = utils.exec_command('%s %s' %
317 (utils.sysctl_cmd, variable))
318 split = output.split(b'=')
319 if len(split) > 1:
320 return split[1].strip()
321 return None
322
323 def systcl_get_net_bridge_stp_user_space(self):
324 if self._bridge_stp_user_space:
325 return self._bridge_stp_user_space
326 try:
327 self._bridge_stp_user_space = self.sysctl_get('net.bridge.bridge-stp-user-space')
328 except:
329 self._bridge_stp_user_space = 0
330
331 return self._bridge_stp_user_space
332
333 def set_iface_attr(self, ifaceobj, attr_name, attr_valsetfunc,
334 prehook=None, prehookargs=None):
335 ifacename = ifaceobj.name
336 attrvalue = ifaceobj.get_attr_value_first(attr_name)
337 if attrvalue:
338 if prehook:
339 if prehookargs:
340 prehook(prehookargs)
341 else:
342 prehook(ifacename)
343 attr_valsetfunc(ifacename, attrvalue)
344
345 def query_n_update_ifaceobjcurr_attr(self, ifaceobj, ifaceobjcurr,
346 attr_name, attr_valgetfunc,
347 attr_valgetextraarg=None):
348 attrvalue = ifaceobj.get_attr_value_first(attr_name)
349 if not attrvalue:
350 return
351 if attr_valgetextraarg:
352 runningattrvalue = attr_valgetfunc(ifaceobj.name,
353 attr_valgetextraarg)
354 else:
355 runningattrvalue = attr_valgetfunc(ifaceobj.name)
356 if (not runningattrvalue or
357 (runningattrvalue != attrvalue)):
358 ifaceobjcurr.update_config_with_status(attr_name,
359 runningattrvalue, 1)
360 else:
361 ifaceobjcurr.update_config_with_status(attr_name,
362 runningattrvalue, 0)
363
364 def dict_key_subset(self, a, b):
365 """ returns a list of differing keys """
366 return [x for x in a if x in b]
367
368 def get_mod_attrs(self):
369 """ returns list of all module attrs defined in the module _modinfo
370 dict
371 """
372 try:
373 retattrs = []
374 attrsdict = self._modinfo.get('attrs')
375 for attrname, attrvals in attrsdict.items():
376 if not attrvals or attrvals.get('deprecated'):
377 continue
378 retattrs.append(attrname)
379 if 'aliases' in attrvals:
380 retattrs.extend(attrvals['aliases'])
381 return retattrs
382 except:
383 return None
384
385 def get_mod_attr(self, attrname):
386 """ returns module attr info """
387 try:
388 return self._modinfo.get('attrs', {}).get(attrname)
389 except:
390 return None
391
392 def get_mod_subattr(self, attrname, subattrname):
393 """ returns module attrs defined in the module _modinfo dict"""
394 try:
395 return reduce(lambda d, k: d[k], ['attrs', attrname, subattrname],
396 self._modinfo)
397 except:
398 return None
399
400 def get_modinfo(self):
401 """ return module info """
402 try:
403 return self._modinfo
404 except:
405 return {}
406
407 def get_attr_default_value(self, attrname):
408 return self.get_modinfo().get('attrs', {}).get(attrname, {}).get('default')
409
410 def get_overrides_ifupdown_scripts(self):
411 """ return the ifupdown scripts replaced by the current module """
412 try:
413 return self.overrides_ifupdown_scripts
414 except:
415 return []
416
417 def _get_reserved_vlan_range(self):
418 start = end = 0
419 get_resvvlan = '/var/lib/ifupdown2/hooks/get_reserved_vlan_range.sh'
420 if not os.path.exists(get_resvvlan):
421 return (start, end)
422 try:
423 (s, e) = utils.exec_command(get_resvvlan).strip('\n').split('-')
424 start = int(s)
425 end = int(e)
426 except Exception as e:
427 self.logger.debug('%s failed (%s)' %(get_resvvlan, str(e)))
428 # ignore errors
429 pass
430 return (start, end)
431
432 def _handle_reserved_vlan(self, vlanid, logprefix='', end=-1):
433 """ Helper function to check and warn if the vlanid falls in the
434 reserved vlan range """
435 error = False
436 invalid_vlan = vlanid
437
438 if self._resv_vlan_range[0] <= vlanid <= self._resv_vlan_range[1]:
439 error = True
440 elif end > 0:
441 if self._resv_vlan_range[0] <= end <= self._resv_vlan_range[1]:
442 error = True
443 invalid_vlan = end
444 elif vlanid < self._resv_vlan_range[0] and end > self._resv_vlan_range[1]:
445 error = True
446 invalid_vlan = self._resv_vlan_range[0]
447
448 if error:
449 raise exceptions.ReservedVlanException('%s: reserved vlan %d being used (reserved vlan range %d-%d)'
450 % (logprefix, invalid_vlan, self._resv_vlan_range[0], self._resv_vlan_range[1]))
451
452 return error
453
454 def _valid_ethaddr(self, ethaddr):
455 """ Check if address is 00:00:00:00:00:00 """
456 if not ethaddr or re.match('00:00:00:00:00:00', ethaddr):
457 return False
458 return True
459
460 def _get_vlan_id_from_ifacename(self, ifacename):
461 if '.' in ifacename:
462 vid_str = ifacename.split('.', 2)
463 vlen = len(vid_str)
464 if vlen == 2:
465 vid_str = vid_str[1]
466 elif vlen == 3:
467 vid_str = vid_str[2]
468 elif ifacename.startswith('vlan'):
469 vid_str = ifacename[4:]
470 else:
471 return -1
472 try:
473 vid = int(vid_str)
474 except:
475 return -1
476 return vid
477
478 def _get_vlan_id(self, ifaceobj):
479 """ Derives vlanid from iface name
480
481 Example:
482 Returns 1 for ifname vlan0001 returns 1
483 Returns 1 for ifname vlan1
484 Returns 1 for ifname eth0.1
485 Returns 100 for ifname eth0.1.100
486 Returns -1 if vlan id cannot be determined
487 """
488 vid_str = ifaceobj.get_attr_value_first('vlan-id')
489 try:
490 if vid_str: return int(vid_str)
491 except:
492 return -1
493
494 return self._get_vlan_id_from_ifacename(ifaceobj.name)