]> git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/ifupdownaddons/modulebase.py
ifupdown2 2.0.0 release
[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 self._bridge_stp_user_space = self.sysctl_get('net.bridge.bridge-stp-user-space')
326 return self._bridge_stp_user_space
327
328 def set_iface_attr(self, ifaceobj, attr_name, attr_valsetfunc,
329 prehook=None, prehookargs=None):
330 ifacename = ifaceobj.name
331 attrvalue = ifaceobj.get_attr_value_first(attr_name)
332 if attrvalue:
333 if prehook:
334 if prehookargs:
335 prehook(prehookargs)
336 else:
337 prehook(ifacename)
338 attr_valsetfunc(ifacename, attrvalue)
339
340 def query_n_update_ifaceobjcurr_attr(self, ifaceobj, ifaceobjcurr,
341 attr_name, attr_valgetfunc,
342 attr_valgetextraarg=None):
343 attrvalue = ifaceobj.get_attr_value_first(attr_name)
344 if not attrvalue:
345 return
346 if attr_valgetextraarg:
347 runningattrvalue = attr_valgetfunc(ifaceobj.name,
348 attr_valgetextraarg)
349 else:
350 runningattrvalue = attr_valgetfunc(ifaceobj.name)
351 if (not runningattrvalue or
352 (runningattrvalue != attrvalue)):
353 ifaceobjcurr.update_config_with_status(attr_name,
354 runningattrvalue, 1)
355 else:
356 ifaceobjcurr.update_config_with_status(attr_name,
357 runningattrvalue, 0)
358
359 def dict_key_subset(self, a, b):
360 """ returns a list of differing keys """
361 return [x for x in a if x in b]
362
363 def get_mod_attrs(self):
364 """ returns list of all module attrs defined in the module _modinfo
365 dict
366 """
367 try:
368 retattrs = []
369 attrsdict = self._modinfo.get('attrs')
370 for attrname, attrvals in attrsdict.iteritems():
371 if not attrvals or attrvals.get('deprecated'):
372 continue
373 retattrs.append(attrname)
374 if 'aliases' in attrvals:
375 retattrs.extend(attrvals['aliases'])
376 return retattrs
377 except:
378 return None
379
380 def get_mod_attr(self, attrname):
381 """ returns module attr info """
382 try:
383 return self._modinfo.get('attrs', {}).get(attrname)
384 except:
385 return None
386
387 def get_mod_subattr(self, attrname, subattrname):
388 """ returns module attrs defined in the module _modinfo dict"""
389 try:
390 return reduce(lambda d, k: d[k], ['attrs', attrname, subattrname],
391 self._modinfo)
392 except:
393 return None
394
395 def get_modinfo(self):
396 """ return module info """
397 try:
398 return self._modinfo
399 except:
400 return {}
401
402 def get_attr_default_value(self, attrname):
403 return self.get_modinfo().get('attrs', {}).get(attrname, {}).get('default')
404
405 def get_overrides_ifupdown_scripts(self):
406 """ return the ifupdown scripts replaced by the current module """
407 try:
408 return self.overrides_ifupdown_scripts
409 except:
410 return []
411
412 def _get_reserved_vlan_range(self):
413 start = end = 0
414 get_resvvlan = '/var/lib/ifupdown2/hooks/get_reserved_vlan_range.sh'
415 if not os.path.exists(get_resvvlan):
416 return (start, end)
417 try:
418 (s, e) = utils.exec_command(get_resvvlan).strip('\n').split('-')
419 start = int(s)
420 end = int(e)
421 except Exception, e:
422 self.logger.debug('%s failed (%s)' %(get_resvvlan, str(e)))
423 # ignore errors
424 pass
425 return (start, end)
426
427 def _handle_reserved_vlan(self, vlanid, logprefix='', end=-1):
428 """ Helper function to check and warn if the vlanid falls in the
429 reserved vlan range """
430 error = False
431 invalid_vlan = vlanid
432
433 if self._resv_vlan_range[0] <= vlanid <= self._resv_vlan_range[1]:
434 error = True
435 elif end > 0:
436 if self._resv_vlan_range[0] <= end <= self._resv_vlan_range[1]:
437 error = True
438 invalid_vlan = end
439 elif vlanid < self._resv_vlan_range[0] and end > self._resv_vlan_range[1]:
440 error = True
441 invalid_vlan = self._resv_vlan_range[0]
442
443 if error:
444 raise exceptions.ReservedVlanException('%s: reserved vlan %d being used (reserved vlan range %d-%d)'
445 % (logprefix, invalid_vlan, self._resv_vlan_range[0], self._resv_vlan_range[1]))
446
447 return error
448
449 def _valid_ethaddr(self, ethaddr):
450 """ Check if address is 00:00:00:00:00:00 """
451 if not ethaddr or re.match('00:00:00:00:00:00', ethaddr):
452 return False
453 return True
454
455 def _get_vlan_id_from_ifacename(self, ifacename):
456 if '.' in ifacename:
457 vid_str = ifacename.split('.', 2)
458 vlen = len(vid_str)
459 if vlen == 2:
460 vid_str = vid_str[1]
461 elif vlen == 3:
462 vid_str = vid_str[2]
463 elif ifacename.startswith('vlan'):
464 vid_str = ifacename[4:]
465 else:
466 return -1
467 try:
468 vid = int(vid_str)
469 except:
470 return -1
471 return vid
472
473 def _get_vlan_id(self, ifaceobj):
474 """ Derives vlanid from iface name
475
476 Example:
477 Returns 1 for ifname vlan0001 returns 1
478 Returns 1 for ifname vlan1
479 Returns 1 for ifname eth0.1
480 Returns 100 for ifname eth0.1.100
481 Returns -1 if vlan id cannot be determined
482 """
483 vid_str = ifaceobj.get_attr_value_first('vlan-id')
484 try:
485 if vid_str: return int(vid_str)
486 except:
487 return -1
488
489 return self._get_vlan_id_from_ifacename(ifaceobj.name)