mirror of
https://github.com/CumulusNetworks/ifupdown2.git
synced 2024-05-06 15:54:50 +00:00
Ticket: None
Reviewed By: CCR-4692
Testing Done: smoke + scale tests
If called with close_fds=True the subprocess module will try to close every fd
from 3 to MAXFD before executing the specified command. This is done in Python
not even with a C-implementation which truly affecting performances.
This patch aims to better handle the file descriptor used by ifupdown2. Either
by closing them after use or by setting the close-on-exec flag for the file
descriptor, which causes the file descriptor to be automatically
(and atomically) closed when any of the exec-family functions succeed.
With the actual patch all tests are passing, I can't think of any future issue
but if any a possible future modification might be to use the parameter
'preexec_fn', which allows us to set function which will be executed in the
child process before executing the command line. We can always manually close
any remaining open file descriptors with something like:
>>> os.listdir('/proc/self/fd/')
['0', '1', '2', ‘3’, etc..]
>>> for fd in os.listdir('/proc/self/fd/')
>>> if int(fd) > 2:
>>> os.close(fd)
This patch is also totally re-organising the use of subprocesses. By removing
all subprocess code redundancy.
506 lines
20 KiB
Python
506 lines
20 KiB
Python
#!/usr/bin/python
|
|
#
|
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
|
#
|
|
|
|
from ifupdown.iface import *
|
|
from utilsbase import *
|
|
import os
|
|
import re
|
|
import logging
|
|
from ifupdown.utils import utils
|
|
import ifupdown.ifupdownflags as ifupdownflags
|
|
from cache import *
|
|
|
|
class brctl(utilsBase):
|
|
""" This class contains helper functions to interact with the bridgeutils
|
|
commands """
|
|
|
|
_cache_fill_done = False
|
|
|
|
def __init__(self, *args, **kargs):
|
|
utilsBase.__init__(self, *args, **kargs)
|
|
if ifupdownflags.flags.CACHE and not brctl._cache_fill_done:
|
|
self._bridge_fill()
|
|
brctl._cache_fill_done = True
|
|
|
|
|
|
def _bridge_get_mcattrs_from_sysfs(self, bridgename):
|
|
mcattrs = {}
|
|
mcattrmap = {'mclmc': 'multicast_last_member_count',
|
|
'mcrouter': 'multicast_router',
|
|
'mcsnoop' : 'multicast_snooping',
|
|
'mcsqc' : 'multicast_startup_query_count',
|
|
'mcqifaddr' : 'multicast_query_use_ifaddr',
|
|
'mcquerier' : 'multicast_querier',
|
|
'hashel' : 'hash_elasticity',
|
|
'hashmax' : 'hash_max',
|
|
'mclmi' : 'multicast_last_member_interval',
|
|
'mcmi' : 'multicast_membership_interval',
|
|
'mcqpi' : 'multicast_querier_interval',
|
|
'mcqi' : 'multicast_query_interval',
|
|
'mcqri' : 'multicast_query_response_interval',
|
|
'mcsqi' : 'multicast_startup_query_interval'}
|
|
|
|
mcattrsdivby100 = ['mclmi', 'mcmi', 'mcqpi', 'mcqi', 'mcqri', 'mcsqi']
|
|
|
|
for m, s in mcattrmap.items():
|
|
n = self.read_file_oneline('/sys/class/net/%s/bridge/%s'
|
|
%(bridgename, s))
|
|
if m in mcattrsdivby100:
|
|
try:
|
|
v = int(n) / 100
|
|
mcattrs[m] = str(v)
|
|
except Exception, e:
|
|
self.logger.warn('error getting mc attr %s (%s)'
|
|
%(m, str(e)))
|
|
pass
|
|
else:
|
|
mcattrs[m] = n
|
|
return mcattrs
|
|
|
|
def _bridge_attrs_fill(self, bridgename):
|
|
battrs = {}
|
|
bports = {}
|
|
|
|
brout = utils.exec_command('/sbin/brctl showstp %s' % bridgename)
|
|
chunks = re.split(r'\n\n', brout, maxsplit=0, flags=re.MULTILINE)
|
|
|
|
try:
|
|
# Get all bridge attributes
|
|
broutlines = chunks[0].splitlines()
|
|
#battrs['pathcost'] = broutlines[3].split('path cost')[1].strip()
|
|
battrs['maxage'] = broutlines[4].split(
|
|
'bridge max age')[1].strip().replace('.00', '')
|
|
battrs['hello'] = broutlines[5].split(
|
|
'bridge hello time')[1].strip().replace('.00',
|
|
'')
|
|
battrs['fd'] = broutlines[6].split(
|
|
'bridge forward delay')[1].strip(
|
|
).replace('.00', '')
|
|
battrs['ageing'] = broutlines[7].split(
|
|
'ageing time')[1].strip().replace('.00', '')
|
|
battrs['mcrouter'] = broutlines[12].split(
|
|
'mc router')[1].strip().split('\t\t\t')[0]
|
|
battrs['bridgeprio'] = self.read_file_oneline(
|
|
'/sys/class/net/%s/bridge/priority' %bridgename)
|
|
battrs.update(self._bridge_get_mcattrs_from_sysfs(bridgename))
|
|
|
|
# XXX: comment this out until mc attributes become available
|
|
# with brctl again
|
|
#battrs['hashel'] = broutlines[10].split('hash elasticity')[1].split()[0].strip()
|
|
#battrs['hashmax'] = broutlines[10].split('hash max')[1].strip()
|
|
#battrs['mclmc'] = broutlines[11].split('mc last member count')[1].split()[0].strip()
|
|
#battrs['mciqc'] = broutlines[11].split('mc init query count')[1].strip()
|
|
#battrs['mcrouter'] = broutlines[12].split('mc router')[1].split()[0].strip()
|
|
##battrs['mcsnoop'] = broutlines[12].split('mc snooping')[1].strip()
|
|
#battrs['mclmt'] = broutlines[13].split('mc last member timer')[1].split()[0].strip()
|
|
except Exception, e:
|
|
self.logger.warn('%s: error while processing bridge attributes: %s' % (bridgename, str(e)))
|
|
pass
|
|
|
|
linkCache.update_attrdict([bridgename, 'linkinfo'], battrs)
|
|
for cidx in range(1, len(chunks)):
|
|
bpout = chunks[cidx].lstrip('\n')
|
|
if not bpout or bpout[0] == ' ':
|
|
continue
|
|
bplines = bpout.splitlines()
|
|
pname = bplines[0].split()[0]
|
|
bportattrs = {}
|
|
try:
|
|
bportattrs['pathcost'] = bplines[2].split(
|
|
'path cost')[1].strip()
|
|
bportattrs['fdelay'] = bplines[4].split(
|
|
'forward delay timer')[1].strip()
|
|
bportattrs['portmcrouter'] = self.read_file_oneline(
|
|
'/sys/class/net/%s/brport/multicast_router' %pname)
|
|
bportattrs['portmcfl'] = self.read_file_oneline(
|
|
'/sys/class/net/%s/brport/multicast_fast_leave' %pname)
|
|
bportattrs['portprio'] = self.read_file_oneline(
|
|
'/sys/class/net/%s/brport/priority' %pname)
|
|
#bportattrs['mcrouters'] = bplines[6].split('mc router')[1].split()[0].strip()
|
|
#bportattrs['mc fast leave'] = bplines[6].split('mc fast leave')[1].strip()
|
|
except Exception, e:
|
|
self.logger.warn('%s: error while processing bridge attributes: %s' % (bridgename, str(e)))
|
|
bports[pname] = bportattrs
|
|
linkCache.update_attrdict([bridgename, 'linkinfo', 'ports'], bports)
|
|
|
|
def _bridge_fill(self, bridgename=None, refresh=False):
|
|
try:
|
|
# if cache is already filled, return
|
|
linkCache.get_attr([bridgename, 'linkinfo', 'fd'])
|
|
return
|
|
except:
|
|
pass
|
|
if not bridgename:
|
|
brctlout = utils.exec_command('/sbin/brctl show')
|
|
else:
|
|
brctlout = utils.exec_command('/sbin/brctl show %s' % bridgename)
|
|
if not brctlout:
|
|
return
|
|
|
|
for bline in brctlout.splitlines()[1:]:
|
|
bitems = bline.split()
|
|
if len(bitems) < 2:
|
|
continue
|
|
try:
|
|
linkCache.update_attrdict([bitems[0], 'linkinfo'],
|
|
{'stp' : bitems[2]})
|
|
except KeyError:
|
|
linkCache.update_attrdict([bitems[0]],
|
|
{'linkinfo' : {'stp' : bitems[2]}})
|
|
self._bridge_attrs_fill(bitems[0])
|
|
|
|
def _cache_get(self, attrlist, refresh=False):
|
|
try:
|
|
if ifupdownflags.flags.DRYRUN:
|
|
return None
|
|
if ifupdownflags.flags.CACHE:
|
|
if not self._cache_fill_done:
|
|
self._bridge_fill()
|
|
self._cache_fill_done = True
|
|
return linkCache.get_attr(attrlist)
|
|
if not refresh:
|
|
return linkCache.get_attr(attrlist)
|
|
self._bridge_fill(attrlist[0], refresh)
|
|
return linkCache.get_attr(attrlist)
|
|
except Exception, e:
|
|
self.logger.debug('_cache_get(%s) : [%s]'
|
|
%(str(attrlist), str(e)))
|
|
pass
|
|
return None
|
|
|
|
def _cache_check(self, attrlist, value, refresh=False):
|
|
try:
|
|
attrvalue = self._cache_get(attrlist, refresh)
|
|
if attrvalue and attrvalue == value:
|
|
return True
|
|
except Exception, e:
|
|
self.logger.debug('_cache_check(%s) : [%s]'
|
|
%(str(attrlist), str(e)))
|
|
pass
|
|
return False
|
|
|
|
def _cache_update(self, attrlist, value):
|
|
if ifupdownflags.flags.DRYRUN: return
|
|
try:
|
|
linkCache.add_attr(attrlist, value)
|
|
except:
|
|
pass
|
|
|
|
def _cache_delete(self, attrlist):
|
|
if ifupdownflags.flags.DRYRUN: return
|
|
try:
|
|
linkCache.del_attr(attrlist)
|
|
except:
|
|
pass
|
|
|
|
def _cache_invalidate(self):
|
|
if ifupdownflags.flags.DRYRUN: return
|
|
linkCache.invalidate()
|
|
|
|
def create_bridge(self, bridgename):
|
|
if self.bridge_exists(bridgename):
|
|
return
|
|
utils.exec_command('/sbin/brctl addbr %s' % bridgename)
|
|
self._cache_update([bridgename], {})
|
|
|
|
def delete_bridge(self, bridgename):
|
|
if not self.bridge_exists(bridgename):
|
|
return
|
|
utils.exec_command('/sbin/brctl delbr %s' % bridgename)
|
|
self._cache_invalidate()
|
|
|
|
def add_bridge_port(self, bridgename, bridgeportname):
|
|
""" Add port to bridge """
|
|
ports = self._cache_get([bridgename, 'linkinfo', 'ports'])
|
|
if ports and ports.get(bridgeportname):
|
|
return
|
|
utils.exec_command('/sbin/brctl addif %s %s' %
|
|
(bridgename, bridgeportname))
|
|
self._cache_update([bridgename, 'linkinfo', 'ports',
|
|
bridgeportname], {})
|
|
|
|
def delete_bridge_port(self, bridgename, bridgeportname):
|
|
""" Delete port from bridge """
|
|
ports = self._cache_get([bridgename, 'linkinfo', 'ports'])
|
|
if not ports or not ports.get(bridgeportname):
|
|
return
|
|
utils.exec_command('/sbin/brctl delif %s %s' %
|
|
(bridgename, bridgeportname))
|
|
self._cache_delete([bridgename, 'linkinfo', 'ports',
|
|
'bridgeportname'])
|
|
|
|
def set_bridgeport_attrs(self, bridgename, bridgeportname, attrdict):
|
|
portattrs = self._cache_get([bridgename, 'linkinfo',
|
|
'ports', bridgeportname])
|
|
if portattrs == None: portattrs = {}
|
|
for k, v in attrdict.iteritems():
|
|
if ifupdownflags.flags.CACHE:
|
|
curval = portattrs.get(k)
|
|
if curval and curval == v:
|
|
continue
|
|
utils.exec_command('/sbin/brctl set%s %s %s %s' %
|
|
(k, bridgename, bridgeportname, v))
|
|
|
|
def set_bridgeport_attr(self, bridgename, bridgeportname,
|
|
attrname, attrval):
|
|
if self._cache_check([bridgename, 'linkinfo', 'ports',
|
|
bridgeportname, attrname], attrval):
|
|
return
|
|
utils.exec_command('/sbin/brctl set%s %s %s %s' %
|
|
(attrname,
|
|
bridgename,
|
|
bridgeportname,
|
|
attrval))
|
|
|
|
def set_bridge_attrs(self, bridgename, attrdict):
|
|
for k, v in attrdict.iteritems():
|
|
if not v:
|
|
continue
|
|
if self._cache_check([bridgename, 'linkinfo', k], v):
|
|
continue
|
|
try:
|
|
cmd = '/sbin/brctl set%s %s %s' % (k, bridgename, v)
|
|
utils.exec_command(cmd)
|
|
except Exception, e:
|
|
self.logger.warn('%s: %s' %(bridgename, str(e)))
|
|
pass
|
|
|
|
def set_bridge_attr(self, bridgename, attrname, attrval):
|
|
if self._cache_check([bridgename, 'linkinfo', attrname], attrval):
|
|
return
|
|
utils.exec_command('/sbin/brctl set%s %s %s' %
|
|
(attrname, bridgename, attrval))
|
|
|
|
def get_bridge_attrs(self, bridgename):
|
|
return self._cache_get([bridgename, 'linkinfo'])
|
|
|
|
def get_bridgeport_attrs(self, bridgename, bridgeportname):
|
|
return self._cache_get([bridgename, 'linkinfo', 'ports',
|
|
bridgeportname])
|
|
|
|
def get_bridgeport_attr(self, bridgename, bridgeportname, attrname):
|
|
return self._cache_get([bridgename, 'linkinfo', 'ports',
|
|
bridgeportname, attrname])
|
|
|
|
def set_stp(self, bridge, stp_state):
|
|
utils.exec_command('/sbin/brctl stp %s %s' % (bridge, stp_state))
|
|
|
|
def get_stp(self, bridge):
|
|
sysfs_stpstate = '/sys/class/net/%s/bridge/stp_state' %bridge
|
|
if not os.path.exists(sysfs_stpstate):
|
|
return 'error'
|
|
stpstate = self.read_file_oneline(sysfs_stpstate)
|
|
if not stpstate:
|
|
return 'error'
|
|
try:
|
|
if int(stpstate) > 0:
|
|
return 'yes'
|
|
elif int(stpstate) == 0:
|
|
return 'no'
|
|
except:
|
|
return 'unknown'
|
|
|
|
def conv_value_to_user(self, str):
|
|
try:
|
|
ret = int(str) / 100
|
|
except:
|
|
return None
|
|
finally:
|
|
return '%d' %ret
|
|
|
|
def read_value_from_sysfs(self, filename, preprocess_func):
|
|
value = self.read_file_oneline(filename)
|
|
if not value:
|
|
return None
|
|
return preprocess_func(value)
|
|
|
|
def set_ageing(self, bridge, ageing):
|
|
utils.exec_command('/sbin/brctl setageing %s %s' % (bridge, ageing))
|
|
|
|
def get_ageing(self, bridge):
|
|
return self.read_value_from_sysfs('/sys/class/net/%s/bridge/ageing_time'
|
|
%bridge, self.conv_value_to_user)
|
|
|
|
def set_bridgeprio(self, bridge, prio):
|
|
utils.exec_command('/sbin/brctl setbridgeprio %s %s' % (bridge, prio))
|
|
|
|
def get_bridgeprio(self, bridge):
|
|
return self.read_file_oneline(
|
|
'/sys/class/net/%s/bridge/priority' %bridge)
|
|
|
|
def set_fd(self, bridge, fd):
|
|
utils.exec_command('/sbin/brctl setfd %s %s' % (bridge, fd))
|
|
|
|
def get_fd(self, bridge):
|
|
return self.read_value_from_sysfs(
|
|
'/sys/class/net/%s/bridge/forward_delay'
|
|
%bridge, self.conv_value_to_user)
|
|
|
|
def set_gcint(self, bridge, gcint):
|
|
#cmd = '/sbin/brctl setgcint ' + bridge + ' ' + gcint
|
|
raise Exception('set_gcint not implemented')
|
|
|
|
def set_hello(self, bridge, hello):
|
|
utils.exec_command('/sbin/brctl sethello %s %s' % (bridge, hello))
|
|
|
|
def get_hello(self, bridge):
|
|
return self.read_value_from_sysfs('/sys/class/net/%s/bridge/hello_time'
|
|
%bridge, self.conv_value_to_user)
|
|
|
|
def set_maxage(self, bridge, maxage):
|
|
utils.exec_command('/sbin/brctl setmaxage %s %s' % (bridge, maxage))
|
|
|
|
def get_maxage(self, bridge):
|
|
return self.read_value_from_sysfs('/sys/class/net/%s/bridge/max_age'
|
|
%bridge, self.conv_value_to_user)
|
|
|
|
def set_pathcost(self, bridge, port, pathcost):
|
|
utils.exec_command('/sbin/brctl setpathcost %s %s %s' %
|
|
(bridge, port, pathcost))
|
|
|
|
def get_pathcost(self, bridge, port):
|
|
return self.read_file_oneline('/sys/class/net/%s/brport/path_cost'
|
|
%port)
|
|
|
|
def set_portprio(self, bridge, port, prio):
|
|
utils.exec_command('/sbin/brctl setportprio %s %s %s' %
|
|
(bridge, port, prio))
|
|
|
|
def get_portprio(self, bridge, port):
|
|
return self.read_file_oneline('/sys/class/net/%s/brport/priority'
|
|
%port)
|
|
|
|
def set_hashmax(self, bridge, hashmax):
|
|
utils.exec_command('/sbin/brctl sethashmax %s %s' % (bridge, hashmax))
|
|
|
|
def get_hashmax(self, bridge):
|
|
return self.read_file_oneline('/sys/class/net/%s/bridge/hash_max'
|
|
%bridge)
|
|
|
|
def set_hashel(self, bridge, hashel):
|
|
utils.exec_command('/sbin/brctl sethashel %s %s' % (bridge, hashel))
|
|
|
|
def get_hashel(self, bridge):
|
|
return self.read_file_oneline('/sys/class/net/%s/bridge/hash_elasticity'
|
|
%bridge)
|
|
|
|
def set_mclmc(self, bridge, mclmc):
|
|
utils.exec_command('/sbin/brctl setmclmc %s %s' % (bridge, mclmc))
|
|
|
|
def get_mclmc(self, bridge):
|
|
return self.read_file_oneline(
|
|
'/sys/class/net/%s/bridge/multicast_last_member_count'
|
|
%bridge)
|
|
|
|
def set_mcrouter(self, bridge, mcrouter):
|
|
utils.exec_command('/sbin/brctl setmcrouter %s %s' % (bridge, mcrouter))
|
|
|
|
def get_mcrouter(self, bridge):
|
|
return self.read_file_oneline(
|
|
'/sys/class/net/%s/bridge/multicast_router' %bridge)
|
|
|
|
def set_mcsnoop(self, bridge, mcsnoop):
|
|
utils.exec_command('/sbin/brctl setmcsnoop %s %s' % (bridge, mcsnoop))
|
|
|
|
def get_mcsnoop(self, bridge):
|
|
return self.read_file_oneline(
|
|
'/sys/class/net/%s/bridge/multicast_snooping' %bridge)
|
|
|
|
def set_mcsqc(self, bridge, mcsqc):
|
|
utils.exec_command('/sbin/brctl setmcsqc %s %s' % (bridge, mcsqc))
|
|
|
|
def get_mcsqc(self, bridge):
|
|
return self.read_file_oneline(
|
|
'/sys/class/net/%s/bridge/multicast_startup_query_count'
|
|
%bridge)
|
|
|
|
def set_mcqifaddr(self, bridge, mcqifaddr):
|
|
utils.exec_command('/sbin/brctl setmcqifaddr %s %s' %
|
|
(bridge, mcqifaddr))
|
|
|
|
def get_mcqifaddr(self, bridge):
|
|
return self.read_file_oneline(
|
|
'/sys/class/net/%s/bridge/multicast_startup_query_use_ifaddr'
|
|
%bridge)
|
|
|
|
def set_mcquerier(self, bridge, mcquerier):
|
|
utils.exec_command('/sbin/brctl setmcquerier %s %s' %
|
|
(bridge, mcquerier))
|
|
|
|
def get_mcquerier(self, bridge):
|
|
return self.read_file_oneline(
|
|
'/sys/class/net/%s/bridge/multicast_querier' %bridge)
|
|
|
|
def set_mcqv4src(self, bridge, vlan, mcquerier):
|
|
if vlan == 0 or vlan > 4095:
|
|
self.logger.warn('mcqv4src vlan \'%d\' invalid range' %vlan)
|
|
return
|
|
|
|
ip = mcquerier.split('.')
|
|
if len(ip) != 4:
|
|
self.logger.warn('mcqv4src \'%s\' invalid IPv4 address' %mcquerier)
|
|
return
|
|
for k in ip:
|
|
if not k.isdigit() or int(k, 10) < 0 or int(k, 10) > 255:
|
|
self.logger.warn('mcqv4src \'%s\' invalid IPv4 address' %mcquerier)
|
|
return
|
|
|
|
utils.exec_command('/sbin/brctl setmcqv4src %s %d %s' %
|
|
(bridge, vlan, mcquerier))
|
|
|
|
def del_mcqv4src(self, bridge, vlan):
|
|
utils.exec_command('/sbin/brctl delmcqv4src %s %d' % (bridge, vlan))
|
|
|
|
def get_mcqv4src(self, bridge, vlan=None):
|
|
mcqv4src = {}
|
|
mcqout = utils.exec_command('/sbin/brctl showmcqv4src %s' % bridge)
|
|
if not mcqout: return None
|
|
mcqlines = mcqout.splitlines()
|
|
for l in mcqlines[1:]:
|
|
l=l.strip()
|
|
k, d, v = l.split('\t')
|
|
if not k or not v:
|
|
continue
|
|
mcqv4src[k] = v
|
|
if vlan:
|
|
return mcqv4src.get(vlan)
|
|
return mcqv4src
|
|
|
|
def set_mclmi(self, bridge, mclmi):
|
|
utils.exec_command('/sbin/brctl setmclmi %s %s' % (bridge, mclmi))
|
|
|
|
def get_mclmi(self, bridge):
|
|
return self.read_file_oneline(
|
|
'/sys/class/net/%s/bridge/multicast_last_member_interval'
|
|
%bridge)
|
|
|
|
def set_mcmi(self, bridge, mcmi):
|
|
utils.exec_command('/sbin/brctl setmcmi %s %s' % (bridge, mcmi))
|
|
|
|
def get_mcmi(self, bridge):
|
|
return self.read_file_oneline(
|
|
'/sys/class/net/%s/bridge/multicast_membership_interval'
|
|
%bridge)
|
|
|
|
def bridge_exists(self, bridge):
|
|
return os.path.exists('/sys/class/net/%s/bridge' %bridge)
|
|
|
|
def is_bridge_port(self, ifacename):
|
|
return os.path.exists('/sys/class/net/%s/brport' %ifacename)
|
|
|
|
def bridge_port_exists(self, bridge, bridgeportname):
|
|
try:
|
|
return os.path.exists('/sys/class/net/%s/brif/%s'
|
|
%(bridge, bridgeportname))
|
|
except Exception:
|
|
return False
|
|
|
|
def get_bridge_ports(self, bridgename):
|
|
try:
|
|
return os.listdir('/sys/class/net/%s/brif/' %bridgename)
|
|
except:
|
|
return []
|