]>
Commit | Line | Data |
---|---|---|
15ef32ea RP |
1 | #!/usr/bin/python |
2 | # | |
3 | # Copyright 2014 Cumulus Networks, Inc. All rights reserved. | |
4 | # Author: Roopa Prabhu, roopa@cumulusnetworks.com | |
5 | # | |
6 | ||
19f8bb0b | 7 | import os |
15ef32ea RP |
8 | import re |
9 | import io | |
10 | import logging | |
15ef32ea | 11 | import traceback |
a4a53f4b JF |
12 | |
13 | from ifupdown.utils import utils | |
15ef32ea | 14 | from ifupdown.iface import * |
717cee31 | 15 | import ifupdown.policymanager as policymanager |
fc5e1735 | 16 | import ifupdown.ifupdownflags as ifupdownflags |
15ef32ea | 17 | |
b54179d2 RP |
18 | class NotSupported(Exception): |
19 | pass | |
20 | ||
15ef32ea RP |
21 | class moduleBase(object): |
22 | """ Base class for ifupdown addon modules | |
23 | ||
24 | Provides common infrastructure methods for all addon modules """ | |
25 | ||
26 | def __init__(self, *args, **kargs): | |
27 | modulename = self.__class__.__name__ | |
28 | self.logger = logging.getLogger('ifupdown.' + modulename) | |
15ef32ea | 29 | |
717cee31 RP |
30 | # vrfs are a global concept and a vrf context can be applicable |
31 | # to all global vrf commands. Get the default vrf-exec-cmd-prefix | |
32 | # here so that all modules can use it | |
33 | self.vrf_exec_cmd_prefix = policymanager.policymanager_api.get_module_globals('vrf', attr='vrf-exec-cmd-prefix') | |
34 | ||
c6370b56 DW |
35 | # explanations are shown in parse_glob |
36 | self.glob_regexs = [re.compile(r"([A-Za-z0-9\-]+)\[(\d+)\-(\d+)\]([A-Za-z0-9\-]+)\[(\d+)\-(\d+)\](.*)"), | |
37 | re.compile(r"([A-Za-z0-9\-]+[A-Za-z])(\d+)\-(\d+)(.*)"), | |
38 | re.compile(r"([A-Za-z0-9\-]+)\[(\d+)\-(\d+)\](.*)")] | |
39 | ||
9d3f53c6 JF |
40 | self._bridge_stp_user_space = None |
41 | ||
c6370b56 | 42 | |
b3745e05 | 43 | def log_warn(self, str, ifaceobj=None): |
15ef32ea | 44 | """ log a warning if err str is not one of which we should ignore """ |
3fb52fa3 | 45 | if not self.ignore_error(str) and not ifupdownflags.flags.IGNORE_ERRORS: |
15ef32ea RP |
46 | if self.logger.getEffectiveLevel() == logging.DEBUG: |
47 | traceback.print_stack() | |
48 | self.logger.warn(str) | |
b3745e05 RP |
49 | if ifaceobj: |
50 | ifaceobj.set_status(ifaceStatus.WARNING) | |
15ef32ea RP |
51 | pass |
52 | ||
bf3eda91 | 53 | def log_error(self, str, ifaceobj=None, raise_error=True): |
15ef32ea | 54 | """ log an err if err str is not one of which we should ignore and raise an exception """ |
3fb52fa3 | 55 | if not self.ignore_error(str) and not ifupdownflags.flags.IGNORE_ERRORS: |
15ef32ea RP |
56 | if self.logger.getEffectiveLevel() == logging.DEBUG: |
57 | traceback.print_stack() | |
bf3eda91 | 58 | if raise_error: |
61bf5eef RP |
59 | if ifaceobj: |
60 | ifaceobj.set_status(ifaceStatus.ERROR) | |
bf3eda91 | 61 | raise Exception(str) |
9df4f95b JF |
62 | else: |
63 | self.logger.error(str) | |
15ef32ea RP |
64 | else: |
65 | pass | |
66 | ||
fff589ea | 67 | def is_process_running(self, procName): |
68 | try: | |
a193d8d1 | 69 | utils.exec_command('/bin/pidof -x %s' % procName) |
fff589ea | 70 | except: |
71 | return False | |
72 | else: | |
73 | return True | |
74 | ||
15ef32ea RP |
75 | def get_ifaces_from_proc(self): |
76 | ifacenames = [] | |
77 | with open('/proc/net/dev') as f: | |
78 | try: | |
79 | lines = f.readlines() | |
0c8332bc | 80 | for line in lines[2:]: |
15ef32ea RP |
81 | ifacenames.append(line.split()[0].strip(': ')) |
82 | except: | |
83 | raise | |
84 | return ifacenames | |
85 | ||
0c8332bc | 86 | def parse_regex(self, ifacename, expr, ifacenames=None): |
15ef32ea RP |
87 | try: |
88 | proc_ifacenames = self.get_ifaces_from_proc() | |
89 | except: | |
0c8332bc RP |
90 | self.logger.warn('%s: error reading ifaces from proc' %ifacename) |
91 | ||
15ef32ea | 92 | for proc_ifacename in proc_ifacenames: |
0c8332bc RP |
93 | try: |
94 | if re.search(expr + '$', proc_ifacename): | |
95 | yield proc_ifacename | |
96 | except Exception, e: | |
97 | raise Exception('%s: error searching regex \'%s\' in %s (%s)' | |
98 | %(ifacename, expr, proc_ifacename, str(e))) | |
15ef32ea RP |
99 | if not ifacenames: |
100 | return | |
101 | for ifacename in ifacenames: | |
0c8332bc RP |
102 | try: |
103 | if re.search(expr + '$', ifacename): | |
104 | yield ifacename | |
105 | except Exception, e: | |
106 | raise Exception('%s: error searching regex \'%s\' in %s (%s)' | |
107 | %(ifacename, expr, ifacename, str(e))) | |
15ef32ea | 108 | |
c6370b56 DW |
109 | def ifname_is_glob(self, ifname): |
110 | """ | |
111 | Used by iface where ifname could be swp7 or swp[1-10].300 | |
112 | """ | |
113 | if (self.glob_regexs[0].match(ifname) or | |
114 | self.glob_regexs[1].match(ifname) or | |
115 | self.glob_regexs[2].match(ifname)): | |
116 | return True | |
117 | return False | |
118 | ||
0c8332bc | 119 | def parse_glob(self, ifacename, expr): |
15ef32ea | 120 | errmsg = ('error parsing glob expression \'%s\'' %expr + |
139662ee ST |
121 | ' (supported glob syntax: swp1-10.300 or swp[1-10].300' + |
122 | ' or swp[1-10]sub[0-4].300') | |
c6370b56 | 123 | regexs = self.glob_regexs |
139662ee ST |
124 | |
125 | if regexs[0].match(expr): | |
126 | # the first regex checks for exactly two levels of ranges defined only with square brackets | |
127 | # (e.g. swpxyz[10-23]subqwe[0-4].100) to handle naming with two levels of port names. | |
128 | m = regexs[0].match(expr) | |
129 | mlist = m.groups() | |
130 | if len(mlist) < 7: | |
131 | # we have problems and should not continue | |
0c8332bc | 132 | raise Exception('%s: error: unhandled glob expression %s\n%s' % (ifacename, expr,errmsg)) |
139662ee ST |
133 | |
134 | prefix = mlist[0] | |
135 | suffix = mlist[6] | |
136 | start_index = int(mlist[1]) | |
137 | end_index = int(mlist[2]) | |
138 | sub_string = mlist[3] | |
139 | start_sub = int(mlist[4]) | |
140 | end_sub = int(mlist[5]) | |
141 | for i in range(start_index, end_index + 1): | |
142 | for j in range(start_sub, end_sub + 1): | |
143 | yield prefix + '%d%s%d' % (i,sub_string,j) + suffix | |
144 | ||
145 | elif regexs[1].match(expr) or regexs[2].match(expr): | |
146 | # the second regex for 1 level with a range (e.g. swp10-14.100 | |
147 | # the third regex checks for 1 level with [] (e.g. swp[10-14].100) | |
148 | start_index = 0 | |
149 | end_index = 0 | |
150 | if regexs[1].match(expr): | |
151 | m = regexs[1].match(expr) | |
152 | else: | |
153 | m = regexs[2].match(expr) | |
154 | mlist = m.groups() | |
155 | if len(mlist) != 4: | |
0c8332bc | 156 | raise Exception('%s: ' %ifacename + errmsg + '(unexpected len)') |
139662ee ST |
157 | prefix = mlist[0] |
158 | suffix = mlist[3] | |
159 | start_index = int(mlist[1]) | |
160 | end_index = int(mlist[2]) | |
15ef32ea RP |
161 | for i in range(start_index, end_index + 1): |
162 | yield prefix + '%d' %i + suffix | |
163 | ||
139662ee ST |
164 | else: |
165 | # Could not match anything. | |
0c8332bc | 166 | self.logger.warn('%s: %s' %(ifacename, errmsg)) |
139662ee ST |
167 | yield expr |
168 | ||
0c8332bc | 169 | def parse_port_list(self, ifacename, port_expr, ifacenames=None): |
15ef32ea RP |
170 | """ parse port list containing glob and regex |
171 | ||
172 | Args: | |
173 | port_expr (str): expression | |
174 | ifacenames (list): list of interface names. This needs to be specified if the expression has a regular expression | |
175 | """ | |
176 | regex = 0 | |
177 | glob = 0 | |
178 | portlist = [] | |
179 | ||
180 | if not port_expr: | |
181 | return None | |
0c8332bc | 182 | exprs = re.split(r'[\s\t]\s*', port_expr) |
77d9d664 | 183 | self.logger.debug('%s: evaluating port expr \'%s\'' |
0c8332bc RP |
184 | %(ifacename, str(exprs))) |
185 | for expr in exprs: | |
84ca006f RP |
186 | if expr == 'noregex': |
187 | regex = 0 | |
188 | elif expr == 'noglob': | |
189 | glob = 0 | |
190 | elif expr == 'regex': | |
15ef32ea RP |
191 | regex = 1 |
192 | elif expr == 'glob': | |
193 | glob = 1 | |
194 | elif regex: | |
0c8332bc | 195 | for port in self.parse_regex(ifacename, expr, ifacenames): |
15ef32ea RP |
196 | if port not in portlist: |
197 | portlist.append(port) | |
198 | regex = 0 | |
199 | elif glob: | |
9a8ad21a | 200 | for port in self.parse_glob(ifacename, expr): |
15ef32ea RP |
201 | portlist.append(port) |
202 | glob = 0 | |
203 | else: | |
204 | portlist.append(expr) | |
205 | if not portlist: | |
206 | return None | |
207 | return portlist | |
208 | ||
209 | def ignore_error(self, errmsg): | |
fc5e1735 | 210 | if (ifupdownflags.flags.FORCE or re.search(r'exists', errmsg, |
15ef32ea RP |
211 | re.IGNORECASE | re.MULTILINE)): |
212 | return True | |
213 | return False | |
214 | ||
215 | def write_file(self, filename, strexpr): | |
216 | """ writes string to a file """ | |
217 | try: | |
218 | self.logger.info('writing \'%s\'' %strexpr + | |
219 | ' to file %s' %filename) | |
fc5e1735 | 220 | if ifupdownflags.flags.DRYRUN: |
15ef32ea RP |
221 | return 0 |
222 | with open(filename, 'w') as f: | |
223 | f.write(strexpr) | |
224 | except IOError, e: | |
225 | self.logger.warn('error writing to file %s' | |
226 | %filename + '(' + str(e) + ')') | |
227 | return -1 | |
228 | return 0 | |
229 | ||
230 | def read_file(self, filename): | |
231 | """ read file and return lines from the file """ | |
232 | try: | |
233 | self.logger.info('reading \'%s\'' %filename) | |
234 | with open(filename, 'r') as f: | |
235 | return f.readlines() | |
236 | except: | |
237 | return None | |
238 | return None | |
239 | ||
240 | def read_file_oneline(self, filename): | |
241 | """ reads and returns first line from the file """ | |
242 | try: | |
243 | self.logger.info('reading \'%s\'' %filename) | |
244 | with open(filename, 'r') as f: | |
245 | return f.readline().strip('\n') | |
246 | except: | |
247 | return None | |
248 | return None | |
249 | ||
250 | def sysctl_set(self, variable, value): | |
251 | """ set sysctl variable to value passed as argument """ | |
a193d8d1 | 252 | utils.exec_command('sysctl %s=%s' % (variable, value)) |
15ef32ea RP |
253 | |
254 | def sysctl_get(self, variable): | |
255 | """ get value of sysctl variable """ | |
8eb2cf9c JF |
256 | output = utils.exec_command('sysctl %s' % variable) |
257 | split = output.split('=') | |
258 | if len(split) > 1: | |
259 | return split[1].strip() | |
260 | return None | |
15ef32ea | 261 | |
9d3f53c6 JF |
262 | def systcl_get_net_bridge_stp_user_space(self): |
263 | if self._bridge_stp_user_space: | |
264 | return self._bridge_stp_user_space | |
265 | self._bridge_stp_user_space = self.sysctl_get('net.bridge.bridge-stp-user-space') | |
266 | return self._bridge_stp_user_space | |
267 | ||
15ef32ea RP |
268 | def set_iface_attr(self, ifaceobj, attr_name, attr_valsetfunc, |
269 | prehook=None, prehookargs=None): | |
270 | ifacename = ifaceobj.name | |
271 | attrvalue = ifaceobj.get_attr_value_first(attr_name) | |
272 | if attrvalue: | |
273 | if prehook: | |
274 | if prehookargs: | |
275 | prehook(prehookargs) | |
276 | else: | |
277 | prehook(ifacename) | |
278 | attr_valsetfunc(ifacename, attrvalue) | |
279 | ||
280 | def query_n_update_ifaceobjcurr_attr(self, ifaceobj, ifaceobjcurr, | |
281 | attr_name, attr_valgetfunc, | |
282 | attr_valgetextraarg=None): | |
283 | attrvalue = ifaceobj.get_attr_value_first(attr_name) | |
284 | if not attrvalue: | |
285 | return | |
286 | if attr_valgetextraarg: | |
287 | runningattrvalue = attr_valgetfunc(ifaceobj.name, | |
288 | attr_valgetextraarg) | |
289 | else: | |
290 | runningattrvalue = attr_valgetfunc(ifaceobj.name) | |
291 | if (not runningattrvalue or | |
292 | (runningattrvalue != attrvalue)): | |
293 | ifaceobjcurr.update_config_with_status(attr_name, | |
294 | runningattrvalue, 1) | |
295 | else: | |
296 | ifaceobjcurr.update_config_with_status(attr_name, | |
297 | runningattrvalue, 0) | |
298 | ||
299 | def dict_key_subset(self, a, b): | |
300 | """ returns a list of differing keys """ | |
301 | return [x for x in a if x in b] | |
302 | ||
303 | def get_mod_attrs(self): | |
1553a881 RP |
304 | """ returns list of all module attrs defined in the module _modinfo |
305 | dict | |
306 | """ | |
15ef32ea | 307 | try: |
1553a881 RP |
308 | retattrs = [] |
309 | attrsdict = self._modinfo.get('attrs') | |
310 | for attrname, attrvals in attrsdict.iteritems(): | |
311 | if not attrvals or attrvals.get('deprecated'): | |
312 | continue | |
313 | retattrs.append(attrname) | |
a9633d05 JF |
314 | if 'aliases' in attrvals: |
315 | retattrs.extend(attrvals['aliases']) | |
1553a881 | 316 | return retattrs |
15ef32ea RP |
317 | except: |
318 | return None | |
319 | ||
320 | def get_mod_attr(self, attrname): | |
321 | """ returns module attr info """ | |
322 | try: | |
323 | return self._modinfo.get('attrs', {}).get(attrname) | |
324 | except: | |
325 | return None | |
326 | ||
327 | def get_mod_subattr(self, attrname, subattrname): | |
328 | """ returns module attrs defined in the module _modinfo dict""" | |
329 | try: | |
330 | return reduce(lambda d, k: d[k], ['attrs', attrname, subattrname], | |
331 | self._modinfo) | |
332 | except: | |
333 | return None | |
334 | ||
335 | def get_modinfo(self): | |
336 | """ return module info """ | |
337 | try: | |
338 | return self._modinfo | |
339 | except: | |
340 | return None | |
341 | ||
b6b8bd2b JF |
342 | def get_overrides_ifupdown_scripts(self): |
343 | """ return the ifupdown scripts replaced by the current module """ | |
344 | try: | |
345 | return self.overrides_ifupdown_scripts | |
346 | except: | |
347 | return [] | |
348 | ||
2da58137 RP |
349 | def _get_reserved_vlan_range(self): |
350 | start = end = 0 | |
2bba6c89 | 351 | get_resvvlan = '/var/lib/ifupdown2/hooks/get_reserved_vlan_range.sh' |
19f8bb0b RP |
352 | if not os.path.exists(get_resvvlan): |
353 | return (start, end) | |
2da58137 | 354 | try: |
a193d8d1 | 355 | (s, e) = utils.exec_command(get_resvvlan).strip('\n').split('-') |
ad25e7bb RP |
356 | start = int(s) |
357 | end = int(e) | |
19f8bb0b RP |
358 | except Exception, e: |
359 | self.logger.debug('%s failed (%s)' %(get_resvvlan, str(e))) | |
2da58137 RP |
360 | # ignore errors |
361 | pass | |
362 | return (start, end) | |
cd3059b8 | 363 | |
2708f915 | 364 | def _handle_reserved_vlan(self, vlanid, logprefix=''): |
cd3059b8 RP |
365 | """ Helper function to check and warn if the vlanid falls in the |
366 | reserved vlan range """ | |
367 | if vlanid in range(self._resv_vlan_range[0], | |
368 | self._resv_vlan_range[1]): | |
2708f915 RP |
369 | self.logger.error('%s: reserved vlan %d being used' |
370 | %(logprefix, vlanid) + ' (reserved vlan range %d-%d)' | |
371 | %(self._resv_vlan_range[0], self._resv_vlan_range[1])) | |
cd3059b8 RP |
372 | return True |
373 | return False | |
5828d8c5 RP |
374 | |
375 | def _valid_ethaddr(self, ethaddr): | |
376 | """ Check if address is 00:00:00:00:00:00 """ | |
377 | if not ethaddr or re.match('00:00:00:00:00:00', ethaddr): | |
378 | return False | |
379 | return True |