]>
Commit | Line | Data |
---|---|---|
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 | 7 | import os |
15ef32ea | 8 | import re |
15ef32ea | 9 | import logging |
15ef32ea | 10 | import traceback |
a4a53f4b | 11 | |
d486dd0d JF |
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 | ||
15ef32ea | 27 | |
b54179d2 RP |
28 | class NotSupported(Exception): |
29 | pass | |
30 | ||
15ef32ea RP |
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): | |
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) |