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 ifaceStatus
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
20 except (ImportError, ModuleNotFoundError
):
21 from ifupdown
.iface
import ifaceStatus
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
:
98 ifaceobj
.set_status(ifaceStatus
.WARNING
)
100 # we can't use logger.getEffectiveLevel or logger.level because
101 # the root logger has level NOTSET, and each logging handler logs
102 # at different level.
103 stack
= traceback
.format_stack()
104 format
= traceback
.format_exc()
106 self
.logger
.debug("%s" % " ".join(stack
)[:-1])
107 self
.logger
.debug("%s" % format
[:-1])
109 self
.logger
.warning(str)
111 def log_error(self
, msg
, ifaceobj
=None, raise_error
=True):
112 """ log an err if err str is not one of which we should ignore and raise an exception """
113 if not self
.ignore_error(msg
) and not ifupdownflags
.flags
.IGNORE_ERRORS
:
116 ifaceobj
.set_status(ifaceStatus
.ERROR
)
118 # we can't use logger.getEffectiveLevel or logger.level because
119 # we have the root logger has level NOTSET, and each logging handler
120 # logs at different level.
121 stack
= traceback
.format_stack()
122 format
= traceback
.format_exc()
124 self
.logger
.debug("%s" % " ".join(stack
)[:-1])
125 self
.logger
.debug("%s" % format
[:-1])
130 self
.logger
.error(msg
)
134 def is_process_running(self
, procName
):
136 utils
.exec_command('%s -x %s' %
137 (utils
.pidof_cmd
, procName
))
143 def get_ifaces_from_proc(self
):
145 with
open('/proc/net/dev') as f
:
147 lines
= f
.readlines()
148 for line
in lines
[2:]:
149 ifacenames
.append(line
.split()[0].strip(': '))
154 def parse_regex(self
, ifacename
, expr
, ifacenames
=None):
156 proc_ifacenames
= self
.get_ifaces_from_proc()
158 self
.logger
.warning('%s: error reading ifaces from proc' %ifacename
)
160 for proc_ifacename
in proc_ifacenames
:
162 if re
.search(expr
+ '$', proc_ifacename
):
164 except Exception as e
:
165 raise Exception('%s: error searching regex \'%s\' in %s (%s)'
166 %(ifacename
, expr
, proc_ifacename
, str(e
)))
169 for ifacename
in ifacenames
:
171 if re
.search(expr
+ '$', ifacename
):
173 except Exception as e
:
174 raise Exception('%s: error searching regex \'%s\' in %s (%s)'
175 %(ifacename
, expr
, ifacename
, str(e
)))
177 def ifname_is_glob(self
, ifname
):
179 Used by iface where ifname could be swp7 or swp[1-10].300
181 if (self
.glob_regexs
[0].match(ifname
) or
182 self
.glob_regexs
[1].match(ifname
) or
183 self
.glob_regexs
[2].match(ifname
)):
187 def parse_glob(self
, ifacename
, expr
):
188 errmsg
= ('error parsing glob expression \'%s\'' %expr
+
189 ' (supported glob syntax: swp1-10.300 or swp[1-10].300' +
190 ' or swp[1-10]sub[0-4].300')
193 self
.logger
.warning('%s: comma are not supported in glob: %s' % (ifacename
, errmsg
))
197 regexs
= self
.glob_regexs
199 if regexs
[0].match(expr
):
200 # the first regex checks for exactly two levels of ranges defined only with square brackets
201 # (e.g. swpxyz[10-23]subqwe[0-4].100) to handle naming with two levels of port names.
202 m
= regexs
[0].match(expr
)
205 # we have problems and should not continue
206 raise Exception('%s: error: unhandled glob expression %s\n%s' % (ifacename
, expr
,errmsg
))
210 start_index
= int(mlist
[1])
211 end_index
= int(mlist
[2])
212 sub_string
= mlist
[3]
213 start_sub
= int(mlist
[4])
214 end_sub
= int(mlist
[5])
215 for i
in range(start_index
, end_index
+ 1):
216 for j
in range(start_sub
, end_sub
+ 1):
217 yield prefix
+ '%d%s%d' % (i
,sub_string
,j
) + suffix
219 elif regexs
[1].match(expr
) or regexs
[2].match(expr
):
220 # the second regex for 1 level with a range (e.g. swp10-14.100
221 # the third regex checks for 1 level with [] (e.g. swp[10-14].100)
224 if regexs
[1].match(expr
):
225 m
= regexs
[1].match(expr
)
227 m
= regexs
[2].match(expr
)
230 raise Exception('%s: ' %ifacename
+ errmsg
+ '(unexpected len)')
233 start_index
= int(mlist
[1])
234 end_index
= int(mlist
[2])
235 for i
in range(start_index
, end_index
+ 1):
236 yield prefix
+ '%d' %i + suffix
239 # Could not match anything.
240 self
.logger
.warning('%s: %s' %(ifacename
, errmsg
))
243 def parse_port_list(self
, ifacename
, port_expr
, ifacenames
=None):
244 """ parse port list containing glob and regex
247 port_expr (str): expression
248 ifacenames (list): list of interface names. This needs to be specified if the expression has a regular expression
256 exprs
= re
.split(r
'[\s\t]\s*', port_expr
)
257 self
.logger
.debug('%s: evaluating port expr \'%s\''
258 %(ifacename
, str(exprs
)))
260 if expr
== 'noregex':
262 elif expr
== 'noglob':
264 elif expr
== 'regex':
269 for port
in self
.parse_regex(ifacename
, expr
, ifacenames
):
270 if port
not in portlist
:
271 portlist
.append(port
)
274 for port
in self
.parse_glob(ifacename
, expr
):
275 portlist
.append(port
)
278 portlist
.append(expr
)
283 def ignore_error(self
, errmsg
):
284 if (ifupdownflags
.flags
.FORCE
or re
.search(r
'exists', errmsg
,
285 re
.IGNORECASE | re
.MULTILINE
)):
289 def write_file(self
, filename
, strexpr
):
290 """ writes string to a file """
292 self
.logger
.info('writing \'%s\'' %strexpr
+
293 ' to file %s' %filename
)
294 if ifupdownflags
.flags
.DRYRUN
:
296 with
open(filename
, 'w') as f
:
299 self
.logger
.warning('error writing to file %s'
300 %filename
+ '(' + str(e
) + ')')
304 def read_file(self
, filename
):
305 """ read file and return lines from the file """
307 self
.logger
.info('reading \'%s\'' %filename
)
308 with
open(filename
, 'r') as f
:
314 def read_file_oneline(self
, filename
):
315 """ reads and returns first line from the file """
317 self
.logger
.info('reading \'%s\'' %filename
)
318 with
open(filename
, 'r') as f
:
319 return f
.readline().strip('\n')
324 def sysctl_set(self
, variable
, value
):
325 """ set sysctl variable to value passed as argument """
326 utils
.exec_command('%s %s=%s' %
327 (utils
.sysctl_cmd
, variable
, value
))
329 def sysctl_get(self
, variable
):
330 """ get value of sysctl variable """
331 output
= utils
.exec_command('%s %s' %
332 (utils
.sysctl_cmd
, variable
))
333 split
= output
.split('=')
335 return split
[1].strip()
338 def systcl_get_net_bridge_stp_user_space(self
):
339 if self
._bridge
_stp
_user
_space
:
340 return self
._bridge
_stp
_user
_space
342 self
._bridge
_stp
_user
_space
= self
.sysctl_get('net.bridge.bridge-stp-user-space')
344 self
._bridge
_stp
_user
_space
= 0
346 return self
._bridge
_stp
_user
_space
348 def set_iface_attr(self
, ifaceobj
, attr_name
, attr_valsetfunc
,
349 prehook
=None, prehookargs
=None):
350 ifacename
= ifaceobj
.name
351 attrvalue
= ifaceobj
.get_attr_value_first(attr_name
)
358 attr_valsetfunc(ifacename
, attrvalue
)
360 def query_n_update_ifaceobjcurr_attr(self
, ifaceobj
, ifaceobjcurr
,
361 attr_name
, attr_valgetfunc
,
362 attr_valgetextraarg
=None):
363 attrvalue
= ifaceobj
.get_attr_value_first(attr_name
)
366 if attr_valgetextraarg
:
367 runningattrvalue
= attr_valgetfunc(ifaceobj
.name
,
370 runningattrvalue
= attr_valgetfunc(ifaceobj
.name
)
371 if (not runningattrvalue
or
372 (runningattrvalue
!= attrvalue
)):
373 ifaceobjcurr
.update_config_with_status(attr_name
,
376 ifaceobjcurr
.update_config_with_status(attr_name
,
379 def dict_key_subset(self
, a
, b
):
380 """ returns a list of differing keys """
381 return [x
for x
in a
if x
in b
]
383 def get_mod_attrs(self
):
384 """ returns list of all module attrs defined in the module _modinfo
389 attrsdict
= self
._modinfo
.get('attrs')
390 for attrname
, attrvals
in attrsdict
.items():
391 if not attrvals
or attrvals
.get('deprecated'):
393 retattrs
.append(attrname
)
394 if 'aliases' in attrvals
:
395 retattrs
.extend(attrvals
['aliases'])
400 def get_mod_attr(self
, attrname
):
401 """ returns module attr info """
403 return self
._modinfo
.get('attrs', {}).get(attrname
)
407 def get_mod_subattr(self
, attrname
, subattrname
):
408 """ returns module attrs defined in the module _modinfo dict"""
410 return reduce(lambda d
, k
: d
[k
], ['attrs', attrname
, subattrname
],
415 def get_modinfo(self
):
416 """ return module info """
422 def get_attr_default_value(self
, attrname
):
423 return self
.get_modinfo().get('attrs', {}).get(attrname
, {}).get('default')
425 def get_overrides_ifupdown_scripts(self
):
426 """ return the ifupdown scripts replaced by the current module """
428 return self
.overrides_ifupdown_scripts
432 def _get_reserved_vlan_range(self
):
434 get_resvvlan
= '/var/lib/ifupdown2/hooks/get_reserved_vlan_range.sh'
435 if not os
.path
.exists(get_resvvlan
):
438 (s
, e
) = utils
.exec_command(get_resvvlan
).strip('\n').split('-')
441 except Exception as e
:
442 self
.logger
.debug('%s failed (%s)' %(get_resvvlan
, str(e
)))
447 def _get_vrf_context(self
):
450 vrfid
= utils
.exec_command('/usr/sbin/ip vrf id').strip()
451 except Exception as e
:
452 self
.logger
.debug('failed to get vrf id (%s)' %str
(e
))
458 def _handle_reserved_vlan(self
, vlanid
, logprefix
='', end
=-1):
459 """ Helper function to check and warn if the vlanid falls in the
460 reserved vlan range """
462 invalid_vlan
= vlanid
464 if self
._resv
_vlan
_range
[0] <= vlanid
<= self
._resv
_vlan
_range
[1]:
467 if self
._resv
_vlan
_range
[0] <= end
<= self
._resv
_vlan
_range
[1]:
470 elif vlanid
< self
._resv
_vlan
_range
[0] and end
> self
._resv
_vlan
_range
[1]:
472 invalid_vlan
= self
._resv
_vlan
_range
[0]
475 raise exceptions
.ReservedVlanException('%s: reserved vlan %d being used (reserved vlan range %d-%d)'
476 % (logprefix
, invalid_vlan
, self
._resv
_vlan
_range
[0], self
._resv
_vlan
_range
[1]))
480 def _valid_ethaddr(self
, ethaddr
):
481 """ Check if address is 00:00:00:00:00:00 """
482 if not ethaddr
or re
.match('00:00:00:00:00:00', ethaddr
):
486 def _get_vlan_id_from_ifacename(self
, ifacename
):
488 vid_str
= ifacename
.split('.', 2)
494 elif ifacename
.startswith('vlan'):
495 vid_str
= ifacename
[4:]
504 def _get_vlan_id(self
, ifaceobj
):
505 """ Derives vlanid from iface name
508 Returns 1 for ifname vlan0001 returns 1
509 Returns 1 for ifname vlan1
510 Returns 1 for ifname eth0.1
511 Returns 100 for ifname eth0.1.100
512 Returns -1 if vlan id cannot be determined
514 vid_str
= ifaceobj
.get_attr_value_first('vlan-id')
516 if vid_str
: return int(vid_str
)
520 return self
._get
_vlan
_id
_from
_ifacename
(ifaceobj
.name
)