mirror of
https://github.com/CumulusNetworks/ifupdown2.git
synced 2024-05-06 15:54:50 +00:00
Ticket: Reviewed By: Roopa, Nikhil G Testing Done: ifupdown2's python addons are replacing some/most of the old ifupdown shell scripts. When addon_scripts_support is set to 1, ifupdown2 will execute every scripts present in /etc/network/if-$ACTION.d/ if a script exists with a name identical to an ifupdown2 addon, this script won't be executed. Sometimes an ifupdown2 addons doesn't have the same name as the ifupdown script it's supposed to replace. Or maybe one addon is replacing several scripts. For example: bond.py is replacing /etc/network/if-*.d/ifenslave Now each addon is able to provide a list of script to ignore. [16:54:40] root:ifupdown2 # ifquery bond0 iface bond0 bond-slaves tap0 tap1 [16:54:49] root:ifupdown2 # ifreload -a -d &> /tmp/ifreload.log [16:54:54] root:ifupdown2 # cat /tmp/ifreload.log | grep ifenslave debug: tap1: pre-up : running script /etc/network/if-pre-up.d/ifenslave info: executing /etc/network/if-pre-up.d/ifenslave debug: tap1: up : running script /etc/network/if-up.d/ifenslave info: executing /etc/network/if-up.d/ifenslave debug: lo: pre-up : running script /etc/network/if-pre-up.d/ifenslave info: executing /etc/network/if-pre-up.d/ifenslave debug: lo: up : running script /etc/network/if-up.d/ifenslave info: executing /etc/network/if-up.d/ifenslave debug: eth0: pre-up : running script /etc/network/if-pre-up.d/ifenslave info: executing /etc/network/if-pre-up.d/ifenslave debug: eth0: up : running script /etc/network/if-up.d/ifenslave info: executing /etc/network/if-up.d/ifenslave debug: tap0: pre-up : running script /etc/network/if-pre-up.d/ifenslave info: executing /etc/network/if-pre-up.d/ifenslave debug: tap0: up : running script /etc/network/if-up.d/ifenslave info: executing /etc/network/if-up.d/ifenslave [16:54:55] root:ifupdown2 # ... apply patch ... [16:55:16] root:ifupdown2 # ifreload -a -d &> /tmp/ifreload.log [16:55:18] root:ifupdown2 # cat /tmp/ifreload.log | grep ifenslave [16:55:19] root:ifupdown2 # Signed-off-by: Julien Fortin <julien@cumulusnetworks.com>
374 lines
13 KiB
Python
374 lines
13 KiB
Python
#!/usr/bin/python
|
|
#
|
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
|
#
|
|
|
|
import os
|
|
import re
|
|
import io
|
|
import logging
|
|
import traceback
|
|
|
|
from ifupdown.utils import utils
|
|
from ifupdown.iface import *
|
|
import ifupdown.policymanager as policymanager
|
|
import ifupdown.ifupdownflags as ifupdownflags
|
|
|
|
class NotSupported(Exception):
|
|
pass
|
|
|
|
class moduleBase(object):
|
|
""" Base class for ifupdown addon modules
|
|
|
|
Provides common infrastructure methods for all addon modules """
|
|
|
|
def __init__(self, *args, **kargs):
|
|
modulename = self.__class__.__name__
|
|
self.logger = logging.getLogger('ifupdown.' + modulename)
|
|
|
|
# vrfs are a global concept and a vrf context can be applicable
|
|
# to all global vrf commands. Get the default vrf-exec-cmd-prefix
|
|
# here so that all modules can use it
|
|
self.vrf_exec_cmd_prefix = policymanager.policymanager_api.get_module_globals('vrf', attr='vrf-exec-cmd-prefix')
|
|
|
|
# explanations are shown in parse_glob
|
|
self.glob_regexs = [re.compile(r"([A-Za-z0-9\-]+)\[(\d+)\-(\d+)\]([A-Za-z0-9\-]+)\[(\d+)\-(\d+)\](.*)"),
|
|
re.compile(r"([A-Za-z0-9\-]+[A-Za-z])(\d+)\-(\d+)(.*)"),
|
|
re.compile(r"([A-Za-z0-9\-]+)\[(\d+)\-(\d+)\](.*)")]
|
|
|
|
self._bridge_stp_user_space = None
|
|
|
|
|
|
def log_warn(self, str, ifaceobj=None):
|
|
""" log a warning if err str is not one of which we should ignore """
|
|
if not self.ignore_error(str) and not ifupdownflags.flags.IGNORE_ERRORS:
|
|
if self.logger.getEffectiveLevel() == logging.DEBUG:
|
|
traceback.print_stack()
|
|
self.logger.warn(str)
|
|
if ifaceobj:
|
|
ifaceobj.set_status(ifaceStatus.WARNING)
|
|
pass
|
|
|
|
def log_error(self, str, ifaceobj=None, raise_error=True):
|
|
""" log an err if err str is not one of which we should ignore and raise an exception """
|
|
if not self.ignore_error(str) and not ifupdownflags.flags.IGNORE_ERRORS:
|
|
if self.logger.getEffectiveLevel() == logging.DEBUG:
|
|
traceback.print_stack()
|
|
if raise_error:
|
|
if ifaceobj:
|
|
ifaceobj.set_status(ifaceStatus.ERROR)
|
|
raise Exception(str)
|
|
else:
|
|
self.logger.error(str)
|
|
else:
|
|
pass
|
|
|
|
def is_process_running(self, procName):
|
|
try:
|
|
utils.exec_command('/bin/pidof -x %s' % procName)
|
|
except:
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def get_ifaces_from_proc(self):
|
|
ifacenames = []
|
|
with open('/proc/net/dev') as f:
|
|
try:
|
|
lines = f.readlines()
|
|
for line in lines[2:]:
|
|
ifacenames.append(line.split()[0].strip(': '))
|
|
except:
|
|
raise
|
|
return ifacenames
|
|
|
|
def parse_regex(self, ifacename, expr, ifacenames=None):
|
|
try:
|
|
proc_ifacenames = self.get_ifaces_from_proc()
|
|
except:
|
|
self.logger.warn('%s: error reading ifaces from proc' %ifacename)
|
|
|
|
for proc_ifacename in proc_ifacenames:
|
|
try:
|
|
if re.search(expr + '$', proc_ifacename):
|
|
yield proc_ifacename
|
|
except Exception, e:
|
|
raise Exception('%s: error searching regex \'%s\' in %s (%s)'
|
|
%(ifacename, expr, proc_ifacename, str(e)))
|
|
if not ifacenames:
|
|
return
|
|
for ifacename in ifacenames:
|
|
try:
|
|
if re.search(expr + '$', ifacename):
|
|
yield ifacename
|
|
except Exception, e:
|
|
raise Exception('%s: error searching regex \'%s\' in %s (%s)'
|
|
%(ifacename, expr, ifacename, str(e)))
|
|
|
|
def ifname_is_glob(self, ifname):
|
|
"""
|
|
Used by iface where ifname could be swp7 or swp[1-10].300
|
|
"""
|
|
if (self.glob_regexs[0].match(ifname) or
|
|
self.glob_regexs[1].match(ifname) or
|
|
self.glob_regexs[2].match(ifname)):
|
|
return True
|
|
return False
|
|
|
|
def parse_glob(self, ifacename, expr):
|
|
errmsg = ('error parsing glob expression \'%s\'' %expr +
|
|
' (supported glob syntax: swp1-10.300 or swp[1-10].300' +
|
|
' or swp[1-10]sub[0-4].300')
|
|
regexs = self.glob_regexs
|
|
|
|
if regexs[0].match(expr):
|
|
# the first regex checks for exactly two levels of ranges defined only with square brackets
|
|
# (e.g. swpxyz[10-23]subqwe[0-4].100) to handle naming with two levels of port names.
|
|
m = regexs[0].match(expr)
|
|
mlist = m.groups()
|
|
if len(mlist) < 7:
|
|
# we have problems and should not continue
|
|
raise Exception('%s: error: unhandled glob expression %s\n%s' % (ifacename, expr,errmsg))
|
|
|
|
prefix = mlist[0]
|
|
suffix = mlist[6]
|
|
start_index = int(mlist[1])
|
|
end_index = int(mlist[2])
|
|
sub_string = mlist[3]
|
|
start_sub = int(mlist[4])
|
|
end_sub = int(mlist[5])
|
|
for i in range(start_index, end_index + 1):
|
|
for j in range(start_sub, end_sub + 1):
|
|
yield prefix + '%d%s%d' % (i,sub_string,j) + suffix
|
|
|
|
elif regexs[1].match(expr) or regexs[2].match(expr):
|
|
# the second regex for 1 level with a range (e.g. swp10-14.100
|
|
# the third regex checks for 1 level with [] (e.g. swp[10-14].100)
|
|
start_index = 0
|
|
end_index = 0
|
|
if regexs[1].match(expr):
|
|
m = regexs[1].match(expr)
|
|
else:
|
|
m = regexs[2].match(expr)
|
|
mlist = m.groups()
|
|
if len(mlist) != 4:
|
|
raise Exception('%s: ' %ifacename + errmsg + '(unexpected len)')
|
|
prefix = mlist[0]
|
|
suffix = mlist[3]
|
|
start_index = int(mlist[1])
|
|
end_index = int(mlist[2])
|
|
for i in range(start_index, end_index + 1):
|
|
yield prefix + '%d' %i + suffix
|
|
|
|
else:
|
|
# Could not match anything.
|
|
self.logger.warn('%s: %s' %(ifacename, errmsg))
|
|
yield expr
|
|
|
|
def parse_port_list(self, ifacename, port_expr, ifacenames=None):
|
|
""" parse port list containing glob and regex
|
|
|
|
Args:
|
|
port_expr (str): expression
|
|
ifacenames (list): list of interface names. This needs to be specified if the expression has a regular expression
|
|
"""
|
|
regex = 0
|
|
glob = 0
|
|
portlist = []
|
|
|
|
if not port_expr:
|
|
return None
|
|
exprs = re.split(r'[\s\t]\s*', port_expr)
|
|
self.logger.debug('%s: evaluating port expr \'%s\''
|
|
%(ifacename, str(exprs)))
|
|
for expr in exprs:
|
|
if expr == 'noregex':
|
|
regex = 0
|
|
elif expr == 'noglob':
|
|
glob = 0
|
|
elif expr == 'regex':
|
|
regex = 1
|
|
elif expr == 'glob':
|
|
glob = 1
|
|
elif regex:
|
|
for port in self.parse_regex(ifacename, expr, ifacenames):
|
|
if port not in portlist:
|
|
portlist.append(port)
|
|
regex = 0
|
|
elif glob:
|
|
for port in self.parse_glob(ifacename, expr):
|
|
portlist.append(port)
|
|
glob = 0
|
|
else:
|
|
portlist.append(expr)
|
|
if not portlist:
|
|
return None
|
|
return portlist
|
|
|
|
def ignore_error(self, errmsg):
|
|
if (ifupdownflags.flags.FORCE or re.search(r'exists', errmsg,
|
|
re.IGNORECASE | re.MULTILINE)):
|
|
return True
|
|
return False
|
|
|
|
def write_file(self, filename, strexpr):
|
|
""" writes string to a file """
|
|
try:
|
|
self.logger.info('writing \'%s\'' %strexpr +
|
|
' to file %s' %filename)
|
|
if ifupdownflags.flags.DRYRUN:
|
|
return 0
|
|
with open(filename, 'w') as f:
|
|
f.write(strexpr)
|
|
except IOError, e:
|
|
self.logger.warn('error writing to file %s'
|
|
%filename + '(' + str(e) + ')')
|
|
return -1
|
|
return 0
|
|
|
|
def read_file(self, filename):
|
|
""" read file and return lines from the file """
|
|
try:
|
|
self.logger.info('reading \'%s\'' %filename)
|
|
with open(filename, 'r') as f:
|
|
return f.readlines()
|
|
except:
|
|
return None
|
|
return None
|
|
|
|
def read_file_oneline(self, filename):
|
|
""" reads and returns first line from the file """
|
|
try:
|
|
self.logger.info('reading \'%s\'' %filename)
|
|
with open(filename, 'r') as f:
|
|
return f.readline().strip('\n')
|
|
except:
|
|
return None
|
|
return None
|
|
|
|
def sysctl_set(self, variable, value):
|
|
""" set sysctl variable to value passed as argument """
|
|
utils.exec_command('sysctl %s=%s' % (variable, value))
|
|
|
|
def sysctl_get(self, variable):
|
|
""" get value of sysctl variable """
|
|
return utils.exec_command('sysctl %s' % variable).split('=')[1].strip()
|
|
|
|
def systcl_get_net_bridge_stp_user_space(self):
|
|
if self._bridge_stp_user_space:
|
|
return self._bridge_stp_user_space
|
|
self._bridge_stp_user_space = self.sysctl_get('net.bridge.bridge-stp-user-space')
|
|
return self._bridge_stp_user_space
|
|
|
|
def set_iface_attr(self, ifaceobj, attr_name, attr_valsetfunc,
|
|
prehook=None, prehookargs=None):
|
|
ifacename = ifaceobj.name
|
|
attrvalue = ifaceobj.get_attr_value_first(attr_name)
|
|
if attrvalue:
|
|
if prehook:
|
|
if prehookargs:
|
|
prehook(prehookargs)
|
|
else:
|
|
prehook(ifacename)
|
|
attr_valsetfunc(ifacename, attrvalue)
|
|
|
|
def query_n_update_ifaceobjcurr_attr(self, ifaceobj, ifaceobjcurr,
|
|
attr_name, attr_valgetfunc,
|
|
attr_valgetextraarg=None):
|
|
attrvalue = ifaceobj.get_attr_value_first(attr_name)
|
|
if not attrvalue:
|
|
return
|
|
if attr_valgetextraarg:
|
|
runningattrvalue = attr_valgetfunc(ifaceobj.name,
|
|
attr_valgetextraarg)
|
|
else:
|
|
runningattrvalue = attr_valgetfunc(ifaceobj.name)
|
|
if (not runningattrvalue or
|
|
(runningattrvalue != attrvalue)):
|
|
ifaceobjcurr.update_config_with_status(attr_name,
|
|
runningattrvalue, 1)
|
|
else:
|
|
ifaceobjcurr.update_config_with_status(attr_name,
|
|
runningattrvalue, 0)
|
|
|
|
def dict_key_subset(self, a, b):
|
|
""" returns a list of differing keys """
|
|
return [x for x in a if x in b]
|
|
|
|
def get_mod_attrs(self):
|
|
""" returns list of all module attrs defined in the module _modinfo
|
|
dict
|
|
"""
|
|
try:
|
|
retattrs = []
|
|
attrsdict = self._modinfo.get('attrs')
|
|
for attrname, attrvals in attrsdict.iteritems():
|
|
if not attrvals or attrvals.get('deprecated'):
|
|
continue
|
|
retattrs.append(attrname)
|
|
return retattrs
|
|
except:
|
|
return None
|
|
|
|
def get_mod_attr(self, attrname):
|
|
""" returns module attr info """
|
|
try:
|
|
return self._modinfo.get('attrs', {}).get(attrname)
|
|
except:
|
|
return None
|
|
|
|
def get_mod_subattr(self, attrname, subattrname):
|
|
""" returns module attrs defined in the module _modinfo dict"""
|
|
try:
|
|
return reduce(lambda d, k: d[k], ['attrs', attrname, subattrname],
|
|
self._modinfo)
|
|
except:
|
|
return None
|
|
|
|
def get_modinfo(self):
|
|
""" return module info """
|
|
try:
|
|
return self._modinfo
|
|
except:
|
|
return None
|
|
|
|
def get_overrides_ifupdown_scripts(self):
|
|
""" return the ifupdown scripts replaced by the current module """
|
|
try:
|
|
return self.overrides_ifupdown_scripts
|
|
except:
|
|
return []
|
|
|
|
def _get_reserved_vlan_range(self):
|
|
start = end = 0
|
|
get_resvvlan = '/var/lib/ifupdown2/hooks/get_reserved_vlan_range.sh'
|
|
if not os.path.exists(get_resvvlan):
|
|
return (start, end)
|
|
try:
|
|
(s, e) = utils.exec_command(get_resvvlan).strip('\n').split('-')
|
|
start = int(s)
|
|
end = int(e)
|
|
except Exception, e:
|
|
self.logger.debug('%s failed (%s)' %(get_resvvlan, str(e)))
|
|
# ignore errors
|
|
pass
|
|
return (start, end)
|
|
|
|
def _handle_reserved_vlan(self, vlanid, logprefix=''):
|
|
""" Helper function to check and warn if the vlanid falls in the
|
|
reserved vlan range """
|
|
if vlanid in range(self._resv_vlan_range[0],
|
|
self._resv_vlan_range[1]):
|
|
self.logger.error('%s: reserved vlan %d being used'
|
|
%(logprefix, vlanid) + ' (reserved vlan range %d-%d)'
|
|
%(self._resv_vlan_range[0], self._resv_vlan_range[1]))
|
|
return True
|
|
return False
|
|
|
|
def _valid_ethaddr(self, ethaddr):
|
|
""" Check if address is 00:00:00:00:00:00 """
|
|
if not ethaddr or re.match('00:00:00:00:00:00', ethaddr):
|
|
return False
|
|
return True
|