1
0
mirror of https://github.com/CumulusNetworks/ifupdown2.git synced 2024-05-06 15:54:50 +00:00
Sam Tannous 6cb589dfdf Allow customer set bond defaults for CL with ifupdown2
Ticket: CM-6723
Reviewed By: roopa
Testing Done: unit tested and testifupdown2 test suite

This patch installs bond interface defaults in

     /etc/network/ifupdown2/policy.d/bond_defaults.json

and allows users to modify this file.   Users can then leave out these
bond attributes in their configs to save typing and space.

It also changes the ifenslave and ifenslaveutil module to bond and
bondutil, respectively to be consistent with other modules
(and also because customers think of "bond" interfaces not
"ifenslave" interfaces.)

For example, the default file installed looks like the following:

{
    "README": "This file is user generated and modifiable.",
    "bond": {
        "defaults": {
                "bond-mode": "802.3ad",
                "bond-miimon": "100",
                "bond-use-carrier": "1",
                "bond-lacp-rate": "0",
                "bond-min-links": "1",
                "bond-xmic-hash-policy": "layer3+4"
         }
    }
}
Please enter the commit message for your changes. Lines starting
2015-07-22 18:38:07 -04:00

389 lines
14 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 subprocess
import traceback
from ifupdown.iface import *
#from ifupdownaddons.iproute2 import *
#from ifupdownaddons.dhclient import *
#from ifupdownaddons.bridgeutils import *
#from ifupdownaddons.mstpctlutil import *
#from ifupdownaddons.bondutil import *
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)
self.FORCE = kargs.get('force', False)
"""force interface configuration"""
self.DRYRUN = kargs.get('dryrun', False)
"""only predend you are applying configuration, dont really do it"""
self.NOWAIT = kargs.get('nowait', False)
self.PERFMODE = kargs.get('perfmode', False)
self.CACHE = kargs.get('cache', False)
self.CACHE_FLAGS = kargs.get('cacheflags', 0x0)
def log_warn(self, str):
""" log a warning if err str is not one of which we should ignore """
if not self.ignore_error(str):
if self.logger.getEffectiveLevel() == logging.DEBUG:
traceback.print_stack()
self.logger.warn(str)
pass
def log_error(self, str):
""" log an err if err str is not one of which we should ignore and raise an exception """
if not self.ignore_error(str):
if self.logger.getEffectiveLevel() == logging.DEBUG:
traceback.print_stack()
raise Exception(str)
else:
pass
def is_process_running(self, procName):
try:
self.exec_command('/bin/pidof -x %s' % procName)
except:
return False
else:
return True
def exec_command(self, cmd, cmdenv=None):
""" execute command passed as argument.
Args:
cmd (str): command to execute
Kwargs:
cmdenv (dict): environment variable name value pairs
"""
cmd_returncode = 0
cmdout = ''
try:
self.logger.info('Executing ' + cmd)
if self.DRYRUN:
return cmdout
ch = subprocess.Popen(cmd.split(),
stdout=subprocess.PIPE,
shell=False, env=cmdenv,
stderr=subprocess.STDOUT,
close_fds=True)
cmdout = ch.communicate()[0]
cmd_returncode = ch.wait()
except OSError, e:
raise Exception('could not execute ' + cmd +
'(' + str(e) + ')')
if cmd_returncode != 0:
raise Exception('error executing cmd \'%s\'' %cmd +
'(' + cmdout.strip('\n ') + ')')
return cmdout
def exec_command_talk_stdin(self, cmd, stdinbuf):
""" execute command passed as argument and write contents of stdinbuf
into stdin of the cmd
Args:
cmd (str): command to execute
stdinbuf (str): string to write to stdin of the cmd process
"""
cmd_returncode = 0
cmdout = ''
try:
self.logger.info('Executing %s (stdin=%s)' %(cmd, stdinbuf))
if self.DRYRUN:
return cmdout
ch = subprocess.Popen(cmd.split(),
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
shell=False, env=cmdenv,
stderr=subprocess.STDOUT,
close_fds=True)
cmdout = ch.communicate(input=stdinbuf)[0]
cmd_returncode = ch.wait()
except OSError, e:
raise Exception('could not execute ' + cmd +
'(' + str(e) + ')')
if cmd_returncode != 0:
raise Exception('error executing cmd \'%s (%s)\''
%(cmd, stdinbuf) + '(' + cmdout.strip('\n ') + ')')
return cmdout
def get_ifaces_from_proc(self):
ifacenames = []
with open('/proc/net/dev') as f:
try:
lines = f.readlines()
for line in lines:
ifacenames.append(line.split()[0].strip(': '))
except:
raise
return ifacenames
def parse_regex(self, expr, ifacenames=None):
try:
proc_ifacenames = self.get_ifaces_from_proc()
except:
self.logger.warn('error reading ifaces from proc')
for proc_ifacename in proc_ifacenames:
if re.search(expr + '$', proc_ifacename):
yield proc_ifacename
if not ifacenames:
return
for ifacename in ifacenames:
if re.search(expr + '$', ifacename):
yield ifacename
def parse_glob(self, 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')
# explanations are shown below in each if clause
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+)\](.*)")]
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('Error: unhandled glob expression %s\n%s' % (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(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' %(errmsg))
yield expr
def parse_port_list(self, 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
for expr in re.split(r'[\s\t]\s*', port_expr):
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(expr, ifacenames):
if port not in portlist:
portlist.append(port)
regex = 0
elif glob:
for port in self.parse_glob(expr):
portlist.append(port)
glob = 0
else:
portlist.append(expr)
if not portlist:
return None
return portlist
def ignore_error(self, errmsg):
if (self.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 self.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 """
self.exec_command('sysctl %s=' %variable + '%s' %value)
def sysctl_get(self, variable):
""" get value of sysctl variable """
return self.exec_command('sysctl %s' %variable).split('=')[1].strip()
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:
return self._modinfo.get('attrs').keys()
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_flags(self):
return dict(force=self.FORCE, dryrun=self.DRYRUN, nowait=self.NOWAIT,
perfmode=self.PERFMODE, cache=self.CACHE,
cacheflags=self.CACHE_FLAGS)
def _get_reserved_vlan_range(self):
start = end = 0
get_resvvlan = '/usr/share/python-ifupdown2/get_reserved_vlan_range.sh'
if not os.path.exists(get_resvvlan):
return (start, end)
try:
(s, e) = self.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