3 # Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
13 from ifupdown2
.ifupdown
.iface
import *
14 from ifupdown2
.ifupdown
.utils
import utils
16 import ifupdown2
.ifupdown
.exceptions
as exceptions
17 import ifupdown2
.ifupdown
.policymanager
as policymanager
18 import ifupdown2
.ifupdown
.ifupdownflags
as ifupdownflags
20 from ifupdown
.iface
import *
21 from ifupdown
.utils
import utils
23 import ifupdown
.exceptions
as exceptions
24 import ifupdown
.policymanager
as policymanager
25 import ifupdown
.ifupdownflags
as ifupdownflags
28 class NotSupported(Exception):
31 class moduleBase(object):
32 """ Base class for ifupdown addon modules
34 Provides common infrastructure methods for all addon modules """
36 def __init__(self
, *args
, **kargs
):
37 self
.modulename
= self
.__class
__.__name
__
38 self
.logger
= logging
.getLogger('ifupdown.' + self
.modulename
)
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')
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+)\](.*)")]
50 self
._bridge
_stp
_user
_space
= None
52 self
.merge_modinfo_with_policy_files()
54 def merge_modinfo_with_policy_files(self
):
56 update addons modinfo dictionary with system/user defined values in policy files
57 Any value can be updated except the module help "mhelp"
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
62 attrs
= dict(self
.get_modinfo().get('attrs', {}))
67 error_msg
= 'this attribute doesn\'t exist or isn\'t supported'
69 # first check module_defaults
70 for key
, value
in policymanager
.policymanager_api
.get_module_defaults(self
.modulename
).items():
72 self
.logger
.warning('%s: %s: %s' % (self
.modulename
, key
, error_msg
))
74 attrs
[key
]['default'] = value
76 # then check module_globals (overrides module_defaults)
77 policy_modinfo
= policymanager
.policymanager_api
.get_module_globals(self
.modulename
, '_modinfo')
79 policy_attrs
= policy_modinfo
.get('attrs', {})
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
))
86 update_attrs
[attr_name
] = attr_description
88 attrs
.update(update_attrs
)
92 def log_warn(self
, str, ifaceobj
=None):
93 """ log a warning if err str is not one of which we should ignore """
94 if not self
.ignore_error(str) and not ifupdownflags
.flags
.IGNORE_ERRORS
:
95 if self
.logger
.getEffectiveLevel() == logging
.DEBUG
:
96 traceback
.print_stack()
100 ifaceobj
.set_status(ifaceStatus
.WARNING
)
103 def log_error(self
, str, ifaceobj
=None, raise_error
=True):
104 """ log an err if err str is not one of which we should ignore and raise an exception """
105 if not self
.ignore_error(str) and not ifupdownflags
.flags
.IGNORE_ERRORS
:
106 if self
.logger
.getEffectiveLevel() == logging
.DEBUG
:
107 traceback
.print_stack()
108 traceback
.print_exc()
111 ifaceobj
.set_status(ifaceStatus
.ERROR
)
114 self
.logger
.error(str)
118 def is_process_running(self
, procName
):
120 utils
.exec_command('%s -x %s' %
121 (utils
.pidof_cmd
, procName
))
127 def get_ifaces_from_proc(self
):
129 with
open('/proc/net/dev') as f
:
131 lines
= f
.readlines()
132 for line
in lines
[2:]:
133 ifacenames
.append(line
.split()[0].strip(': '))
138 def parse_regex(self
, ifacename
, expr
, ifacenames
=None):
140 proc_ifacenames
= self
.get_ifaces_from_proc()
142 self
.logger
.warn('%s: error reading ifaces from proc' %ifacename
)
144 for proc_ifacename
in proc_ifacenames
:
146 if re
.search(expr
+ '$', proc_ifacename
):
149 raise Exception('%s: error searching regex \'%s\' in %s (%s)'
150 %(ifacename
, expr
, proc_ifacename
, str(e
)))
153 for ifacename
in ifacenames
:
155 if re
.search(expr
+ '$', ifacename
):
158 raise Exception('%s: error searching regex \'%s\' in %s (%s)'
159 %(ifacename
, expr
, ifacename
, str(e
)))
161 def ifname_is_glob(self
, ifname
):
163 Used by iface where ifname could be swp7 or swp[1-10].300
165 if (self
.glob_regexs
[0].match(ifname
) or
166 self
.glob_regexs
[1].match(ifname
) or
167 self
.glob_regexs
[2].match(ifname
)):
171 def parse_glob(self
, ifacename
, expr
):
172 errmsg
= ('error parsing glob expression \'%s\'' %expr
+
173 ' (supported glob syntax: swp1-10.300 or swp[1-10].300' +
174 ' or swp[1-10]sub[0-4].300')
177 self
.logger
.warn('%s: comma are not supported in glob: %s' % (ifacename
, errmsg
))
181 regexs
= self
.glob_regexs
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
)
189 # we have problems and should not continue
190 raise Exception('%s: error: unhandled glob expression %s\n%s' % (ifacename
, expr
,errmsg
))
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
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)
208 if regexs
[1].match(expr
):
209 m
= regexs
[1].match(expr
)
211 m
= regexs
[2].match(expr
)
214 raise Exception('%s: ' %ifacename
+ errmsg
+ '(unexpected len)')
217 start_index
= int(mlist
[1])
218 end_index
= int(mlist
[2])
219 for i
in range(start_index
, end_index
+ 1):
220 yield prefix
+ '%d' %i + suffix
223 # Could not match anything.
224 self
.logger
.warn('%s: %s' %(ifacename
, errmsg
))
227 def parse_port_list(self
, ifacename
, port_expr
, ifacenames
=None):
228 """ parse port list containing glob and regex
231 port_expr (str): expression
232 ifacenames (list): list of interface names. This needs to be specified if the expression has a regular expression
240 exprs
= re
.split(r
'[\s\t]\s*', port_expr
)
241 self
.logger
.debug('%s: evaluating port expr \'%s\''
242 %(ifacename
, str(exprs
)))
244 if expr
== 'noregex':
246 elif expr
== 'noglob':
248 elif expr
== 'regex':
253 for port
in self
.parse_regex(ifacename
, expr
, ifacenames
):
254 if port
not in portlist
:
255 portlist
.append(port
)
258 for port
in self
.parse_glob(ifacename
, expr
):
259 portlist
.append(port
)
262 portlist
.append(expr
)
267 def ignore_error(self
, errmsg
):
268 if (ifupdownflags
.flags
.FORCE
or re
.search(r
'exists', errmsg
,
269 re
.IGNORECASE | re
.MULTILINE
)):
273 def write_file(self
, filename
, strexpr
):
274 """ writes string to a file """
276 self
.logger
.info('writing \'%s\'' %strexpr
+
277 ' to file %s' %filename
)
278 if ifupdownflags
.flags
.DRYRUN
:
280 with
open(filename
, 'w') as f
:
283 self
.logger
.warn('error writing to file %s'
284 %filename
+ '(' + str(e
) + ')')
288 def read_file(self
, filename
):
289 """ read file and return lines from the file """
291 self
.logger
.info('reading \'%s\'' %filename
)
292 with
open(filename
, 'r') as f
:
298 def read_file_oneline(self
, filename
):
299 """ reads and returns first line from the file """
301 self
.logger
.info('reading \'%s\'' %filename
)
302 with
open(filename
, 'r') as f
:
303 return f
.readline().strip('\n')
308 def sysctl_set(self
, variable
, value
):
309 """ set sysctl variable to value passed as argument """
310 utils
.exec_command('%s %s=%s' %
311 (utils
.sysctl_cmd
, variable
, value
))
313 def sysctl_get(self
, variable
):
314 """ get value of sysctl variable """
315 output
= utils
.exec_command('%s %s' %
316 (utils
.sysctl_cmd
, variable
))
317 split
= output
.split('=')
319 return split
[1].strip()
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
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
)
338 attr_valsetfunc(ifacename
, attrvalue
)
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
)
346 if attr_valgetextraarg
:
347 runningattrvalue
= attr_valgetfunc(ifaceobj
.name
,
350 runningattrvalue
= attr_valgetfunc(ifaceobj
.name
)
351 if (not runningattrvalue
or
352 (runningattrvalue
!= attrvalue
)):
353 ifaceobjcurr
.update_config_with_status(attr_name
,
356 ifaceobjcurr
.update_config_with_status(attr_name
,
359 def dict_key_subset(self
, a
, b
):
360 """ returns a list of differing keys """
361 return [x
for x
in a
if x
in b
]
363 def get_mod_attrs(self
):
364 """ returns list of all module attrs defined in the module _modinfo
369 attrsdict
= self
._modinfo
.get('attrs')
370 for attrname
, attrvals
in attrsdict
.iteritems():
371 if not attrvals
or attrvals
.get('deprecated'):
373 retattrs
.append(attrname
)
374 if 'aliases' in attrvals
:
375 retattrs
.extend(attrvals
['aliases'])
380 def get_mod_attr(self
, attrname
):
381 """ returns module attr info """
383 return self
._modinfo
.get('attrs', {}).get(attrname
)
387 def get_mod_subattr(self
, attrname
, subattrname
):
388 """ returns module attrs defined in the module _modinfo dict"""
390 return reduce(lambda d
, k
: d
[k
], ['attrs', attrname
, subattrname
],
395 def get_modinfo(self
):
396 """ return module info """
402 def get_attr_default_value(self
, attrname
):
403 return self
.get_modinfo().get('attrs', {}).get(attrname
, {}).get('default')
405 def get_overrides_ifupdown_scripts(self
):
406 """ return the ifupdown scripts replaced by the current module """
408 return self
.overrides_ifupdown_scripts
412 def _get_reserved_vlan_range(self
):
414 get_resvvlan
= '/var/lib/ifupdown2/hooks/get_reserved_vlan_range.sh'
415 if not os
.path
.exists(get_resvvlan
):
418 (s
, e
) = utils
.exec_command(get_resvvlan
).strip('\n').split('-')
422 self
.logger
.debug('%s failed (%s)' %(get_resvvlan
, str(e
)))
427 def _handle_reserved_vlan(self
, vlanid
, logprefix
='', end
=-1):
428 """ Helper function to check and warn if the vlanid falls in the
429 reserved vlan range """
431 invalid_vlan
= vlanid
433 if self
._resv
_vlan
_range
[0] <= vlanid
<= self
._resv
_vlan
_range
[1]:
436 if self
._resv
_vlan
_range
[0] <= end
<= self
._resv
_vlan
_range
[1]:
439 elif vlanid
< self
._resv
_vlan
_range
[0] and end
> self
._resv
_vlan
_range
[1]:
441 invalid_vlan
= self
._resv
_vlan
_range
[0]
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]))
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
):
455 def _get_vlan_id_from_ifacename(self
, ifacename
):
457 vid_str
= ifacename
.split('.', 2)
463 elif ifacename
.startswith('vlan'):
464 vid_str
= ifacename
[4:]
473 def _get_vlan_id(self
, ifaceobj
):
474 """ Derives vlanid from iface name
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
483 vid_str
= ifaceobj
.get_attr_value_first('vlan-id')
485 if vid_str
: return int(vid_str
)
489 return self
._get
_vlan
_id
_from
_ifacename
(ifaceobj
.name
)