]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown2/ifupdownaddons/modulebase.py
.gitignore: pycharm remote execution update
[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
325 self._bridge_stp_user_space = self.sysctl_get('net.bridge.bridge-stp-user-space')
326 return self._bridge_stp_user_space
327
15ef32ea
RP
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
d486dd0d 359 def dict_key_subset(self, a, b):
15ef32ea
RP
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):
1553a881
RP
364 """ returns list of all module attrs defined in the module _modinfo
365 dict
366 """
15ef32ea 367 try:
1553a881
RP
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)
a9633d05
JF
374 if 'aliases' in attrvals:
375 retattrs.extend(attrvals['aliases'])
1553a881 376 return retattrs
15ef32ea
RP
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:
d486dd0d
JF
400 return {}
401
402 def get_attr_default_value(self, attrname):
403 return self.get_modinfo().get('attrs', {}).get(attrname, {}).get('default')
15ef32ea 404
b6b8bd2b
JF
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
2da58137
RP
412 def _get_reserved_vlan_range(self):
413 start = end = 0
2bba6c89 414 get_resvvlan = '/var/lib/ifupdown2/hooks/get_reserved_vlan_range.sh'
19f8bb0b
RP
415 if not os.path.exists(get_resvvlan):
416 return (start, end)
2da58137 417 try:
a193d8d1 418 (s, e) = utils.exec_command(get_resvvlan).strip('\n').split('-')
ad25e7bb
RP
419 start = int(s)
420 end = int(e)
19f8bb0b
RP
421 except Exception, e:
422 self.logger.debug('%s failed (%s)' %(get_resvvlan, str(e)))
2da58137
RP
423 # ignore errors
424 pass
425 return (start, end)
cd3059b8 426
d486dd0d 427 def _handle_reserved_vlan(self, vlanid, logprefix='', end=-1):
cd3059b8
RP
428 """ Helper function to check and warn if the vlanid falls in the
429 reserved vlan range """
d486dd0d
JF
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
5828d8c5
RP
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
d486dd0d
JF
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)