3 # Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
11 from functools
import reduce
14 from ifupdown2
.ifupdown
.iface
import *
15 from ifupdown2
.ifupdown
.utils
import utils
17 import ifupdown2
.ifupdown
.exceptions
as exceptions
18 import ifupdown2
.ifupdown
.policymanager
as policymanager
19 import ifupdown2
.ifupdown
.ifupdownflags
as ifupdownflags
21 from ifupdown
.iface
import *
22 from ifupdown
.utils
import utils
24 import ifupdown
.exceptions
as exceptions
25 import ifupdown
.policymanager
as policymanager
26 import ifupdown
.ifupdownflags
as ifupdownflags
29 class NotSupported(Exception):
32 class moduleBase(object):
33 """ Base class for ifupdown addon modules
35 Provides common infrastructure methods for all addon modules """
37 def __init__(self
, *args
, **kargs
):
38 self
.modulename
= self
.__class
__.__name
__
39 self
.logger
= logging
.getLogger('ifupdown.' + self
.modulename
)
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')
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+)\](.*)")]
51 self
._bridge
_stp
_user
_space
= None
53 self
.merge_modinfo_with_policy_files()
55 def merge_modinfo_with_policy_files(self
):
57 update addons modinfo dictionary with system/user defined values in policy files
58 Any value can be updated except the module help "mhelp"
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
63 attrs
= dict(self
.get_modinfo().get('attrs', {}))
68 error_msg
= 'this attribute doesn\'t exist or isn\'t supported'
70 # first check module_defaults
71 for key
, value
in list(policymanager
.policymanager_api
.get_module_defaults(self
.modulename
).items()):
73 self
.logger
.warning('%s: %s: %s' % (self
.modulename
, key
, error_msg
))
75 attrs
[key
]['default'] = value
77 # then check module_globals (overrides module_defaults)
78 policy_modinfo
= policymanager
.policymanager_api
.get_module_globals(self
.modulename
, '_modinfo')
80 policy_attrs
= policy_modinfo
.get('attrs', {})
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
))
87 update_attrs
[attr_name
] = attr_description
89 attrs
.update(update_attrs
)
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()
99 self
.logger
.warning(str)
101 ifaceobj
.set_status(ifaceStatus
.WARNING
)
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()
112 ifaceobj
.set_status(ifaceStatus
.ERROR
)
115 self
.logger
.error(str)
119 def is_process_running(self
, procName
):
121 utils
.exec_command('%s -x %s' %
122 (utils
.pidof_cmd
, procName
))
128 def get_ifaces_from_proc(self
):
130 with
open('/proc/net/dev') as f
:
132 lines
= f
.readlines()
133 for line
in lines
[2:]:
134 ifacenames
.append(line
.split()[0].strip(': '))
139 def parse_regex(self
, ifacename
, expr
, ifacenames
=None):
141 proc_ifacenames
= self
.get_ifaces_from_proc()
143 self
.logger
.warning('%s: error reading ifaces from proc' %ifacename
)
145 for proc_ifacename
in proc_ifacenames
:
147 if re
.search(expr
+ '$', 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
)))
154 for ifacename
in ifacenames
:
156 if re
.search(expr
+ '$', ifacename
):
158 except Exception as e
:
159 raise Exception('%s: error searching regex \'%s\' in %s (%s)'
160 %(ifacename
, expr
, ifacename
, str(e
)))
162 def ifname_is_glob(self
, ifname
):
164 Used by iface where ifname could be swp7 or swp[1-10].300
166 if (self
.glob_regexs
[0].match(ifname
) or
167 self
.glob_regexs
[1].match(ifname
) or
168 self
.glob_regexs
[2].match(ifname
)):
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')
178 self
.logger
.warning('%s: comma are not supported in glob: %s' % (ifacename
, errmsg
))
182 regexs
= self
.glob_regexs
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
)
190 # we have problems and should not continue
191 raise Exception('%s: error: unhandled glob expression %s\n%s' % (ifacename
, expr
,errmsg
))
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
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)
209 if regexs
[1].match(expr
):
210 m
= regexs
[1].match(expr
)
212 m
= regexs
[2].match(expr
)
215 raise Exception('%s: ' %ifacename
+ errmsg
+ '(unexpected len)')
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
224 # Could not match anything.
225 self
.logger
.warning('%s: %s' %(ifacename
, errmsg
))
228 def parse_port_list(self
, ifacename
, port_expr
, ifacenames
=None):
229 """ parse port list containing glob and regex
232 port_expr (str): expression
233 ifacenames (list): list of interface names. This needs to be specified if the expression has a regular expression
241 exprs
= re
.split(r
'[\s\t]\s*', port_expr
)
242 self
.logger
.debug('%s: evaluating port expr \'%s\''
243 %(ifacename
, str(exprs
)))
245 if expr
== 'noregex':
247 elif expr
== 'noglob':
249 elif expr
== 'regex':
254 for port
in self
.parse_regex(ifacename
, expr
, ifacenames
):
255 if port
not in portlist
:
256 portlist
.append(port
)
259 for port
in self
.parse_glob(ifacename
, expr
):
260 portlist
.append(port
)
263 portlist
.append(expr
)
268 def ignore_error(self
, errmsg
):
269 if (ifupdownflags
.flags
.FORCE
or re
.search(r
'exists', errmsg
,
270 re
.IGNORECASE | re
.MULTILINE
)):
274 def write_file(self
, filename
, strexpr
):
275 """ writes string to a file """
277 self
.logger
.info('writing \'%s\'' %strexpr
+
278 ' to file %s' %filename
)
279 if ifupdownflags
.flags
.DRYRUN
:
281 with
open(filename
, 'w') as f
:
284 self
.logger
.warning('error writing to file %s'
285 %filename
+ '(' + str(e
) + ')')
289 def read_file(self
, filename
):
290 """ read file and return lines from the file """
292 self
.logger
.info('reading \'%s\'' %filename
)
293 with
open(filename
, 'r') as f
:
299 def read_file_oneline(self
, filename
):
300 """ reads and returns first line from the file """
302 self
.logger
.info('reading \'%s\'' %filename
)
303 with
open(filename
, 'r') as f
:
304 return f
.readline().strip('\n')
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
))
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
'=')
320 return split
[1].strip()
323 def systcl_get_net_bridge_stp_user_space(self
):
324 if self
._bridge
_stp
_user
_space
:
325 return self
._bridge
_stp
_user
_space
327 self
._bridge
_stp
_user
_space
= self
.sysctl_get('net.bridge.bridge-stp-user-space')
329 self
._bridge
_stp
_user
_space
= 0
331 return self
._bridge
_stp
_user
_space
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
)
343 attr_valsetfunc(ifacename
, attrvalue
)
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
)
351 if attr_valgetextraarg
:
352 runningattrvalue
= attr_valgetfunc(ifaceobj
.name
,
355 runningattrvalue
= attr_valgetfunc(ifaceobj
.name
)
356 if (not runningattrvalue
or
357 (runningattrvalue
!= attrvalue
)):
358 ifaceobjcurr
.update_config_with_status(attr_name
,
361 ifaceobjcurr
.update_config_with_status(attr_name
,
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
]
368 def get_mod_attrs(self
):
369 """ returns list of all module attrs defined in the module _modinfo
374 attrsdict
= self
._modinfo
.get('attrs')
375 for attrname
, attrvals
in attrsdict
.items():
376 if not attrvals
or attrvals
.get('deprecated'):
378 retattrs
.append(attrname
)
379 if 'aliases' in attrvals
:
380 retattrs
.extend(attrvals
['aliases'])
385 def get_mod_attr(self
, attrname
):
386 """ returns module attr info """
388 return self
._modinfo
.get('attrs', {}).get(attrname
)
392 def get_mod_subattr(self
, attrname
, subattrname
):
393 """ returns module attrs defined in the module _modinfo dict"""
395 return reduce(lambda d
, k
: d
[k
], ['attrs', attrname
, subattrname
],
400 def get_modinfo(self
):
401 """ return module info """
407 def get_attr_default_value(self
, attrname
):
408 return self
.get_modinfo().get('attrs', {}).get(attrname
, {}).get('default')
410 def get_overrides_ifupdown_scripts(self
):
411 """ return the ifupdown scripts replaced by the current module """
413 return self
.overrides_ifupdown_scripts
417 def _get_reserved_vlan_range(self
):
419 get_resvvlan
= '/var/lib/ifupdown2/hooks/get_reserved_vlan_range.sh'
420 if not os
.path
.exists(get_resvvlan
):
423 (s
, e
) = utils
.exec_command(get_resvvlan
).strip('\n').split('-')
426 except Exception as e
:
427 self
.logger
.debug('%s failed (%s)' %(get_resvvlan
, str(e
)))
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 """
436 invalid_vlan
= vlanid
438 if self
._resv
_vlan
_range
[0] <= vlanid
<= self
._resv
_vlan
_range
[1]:
441 if self
._resv
_vlan
_range
[0] <= end
<= self
._resv
_vlan
_range
[1]:
444 elif vlanid
< self
._resv
_vlan
_range
[0] and end
> self
._resv
_vlan
_range
[1]:
446 invalid_vlan
= self
._resv
_vlan
_range
[0]
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]))
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
):
460 def _get_vlan_id_from_ifacename(self
, ifacename
):
462 vid_str
= ifacename
.split('.', 2)
468 elif ifacename
.startswith('vlan'):
469 vid_str
= ifacename
[4:]
478 def _get_vlan_id(self
, ifaceobj
):
479 """ Derives vlanid from iface name
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
488 vid_str
= ifaceobj
.get_attr_value_first('vlan-id')
490 if vid_str
: return int(vid_str
)
494 return self
._get
_vlan
_id
_from
_ifacename
(ifaceobj
.name
)