mirror of
https://github.com/CumulusNetworks/ifupdown2.git
synced 2024-05-06 15:54:50 +00:00
Move ifupdown2addons into ifupdown2 pacakge
Ticket: CM-3864 Reviewed By: Testing Done: Tested build and install open item: - cleanup stale ifupdown2-addons package files
This commit is contained in:
13
TODO.addons
Normal file
13
TODO.addons
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
TODO:
|
||||||
|
====
|
||||||
|
- run python code guideline checker
|
||||||
|
- more code documentation
|
||||||
|
- move all cache handling to decorators in ifupdownaddons package
|
||||||
|
- input validation (present in some cases not all)
|
||||||
|
- support the vlan0004 style vlans
|
||||||
|
- ifquery coverage. currently it is at 80%.
|
||||||
|
- vxlan module
|
||||||
|
- fix and release ifaddon utility to manage module priorities
|
||||||
|
- Deep compare in query for address module (does not compare address attributes like scope)
|
||||||
|
- Maybe a pure netlink backend
|
||||||
|
- improve caching
|
300
addons/address.py
Normal file
300
addons/address.py
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ipaddr import IPNetwork
|
||||||
|
from sets import Set
|
||||||
|
from ifupdown.iface import *
|
||||||
|
from ifupdownaddons.modulebase import moduleBase
|
||||||
|
from ifupdownaddons.iproute2 import iproute2
|
||||||
|
from ifupdownaddons.dhclient import dhclient
|
||||||
|
except ImportError, e:
|
||||||
|
raise ImportError (str(e) + "- required module not found")
|
||||||
|
|
||||||
|
class address(moduleBase):
|
||||||
|
""" ifupdown2 addon module to configure address, mtu, hwaddress, alias
|
||||||
|
(description) on an interface """
|
||||||
|
|
||||||
|
_modinfo = {'mhelp' : 'address configuration module for interfaces',
|
||||||
|
'attrs': {
|
||||||
|
'address' :
|
||||||
|
{'help' : 'ipv4 or ipv6 addresses',
|
||||||
|
'example' : ['address 10.0.12.3/24',
|
||||||
|
'address 2000:1000:1000:1000:3::5/128']},
|
||||||
|
'netmask' :
|
||||||
|
{'help': 'netmask',
|
||||||
|
'example' : ['netmask 255.255.255.0'],
|
||||||
|
'compat' : True},
|
||||||
|
'broadcast' :
|
||||||
|
{'help': 'broadcast address',
|
||||||
|
'example' : ['broadcast 10.0.1.255']},
|
||||||
|
'scope' :
|
||||||
|
{'help': 'scope',
|
||||||
|
'example' : ['scope host']},
|
||||||
|
'preferred-lifetime' :
|
||||||
|
{'help': 'preferred lifetime',
|
||||||
|
'example' : ['preferred-lifetime forever',
|
||||||
|
'preferred-lifetime 10']},
|
||||||
|
'gateway' :
|
||||||
|
{'help': 'default gateway',
|
||||||
|
'example' : ['gateway 255.255.255.0']},
|
||||||
|
'mtu' :
|
||||||
|
{ 'help': 'interface mtu',
|
||||||
|
'example' : ['mtu 1600'],
|
||||||
|
'default' : '1500'},
|
||||||
|
'hwaddress' :
|
||||||
|
{'help' : 'hw address',
|
||||||
|
'example': ['hwaddress 44:38:39:00:27:b8']},
|
||||||
|
'alias' :
|
||||||
|
{ 'help': 'description/alias',
|
||||||
|
'example' : ['alias testnetwork']}}}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
moduleBase.__init__(self, *args, **kargs)
|
||||||
|
self.ipcmd = None
|
||||||
|
|
||||||
|
def _inet_address_config(self, ifaceobj):
|
||||||
|
newaddrs = []
|
||||||
|
addrs = ifaceobj.get_attr_value('address')
|
||||||
|
if addrs:
|
||||||
|
# If user address is not in CIDR notation, convert them to CIDR
|
||||||
|
for addr_index in range(0, len(addrs)):
|
||||||
|
addr = addrs[addr_index]
|
||||||
|
if '/' in addr:
|
||||||
|
newaddrs.append(addr)
|
||||||
|
continue
|
||||||
|
netmask = ifaceobj.get_attr_value_n('netmask', addr_index)
|
||||||
|
if netmask:
|
||||||
|
prefixlen = IPNetwork('%s' %addr +
|
||||||
|
'/%s' %netmask).prefixlen
|
||||||
|
newaddrs.append(addr + '/%s' %prefixlen)
|
||||||
|
else:
|
||||||
|
newaddrs.append(addr)
|
||||||
|
|
||||||
|
if not self.PERFMODE and not (ifaceobj.flags & iface.HAS_SIBLINGS):
|
||||||
|
# if perfmode is not set and also if iface has no sibling
|
||||||
|
# objects, purge addresses that are not present in the new
|
||||||
|
# config
|
||||||
|
runningaddrs = self.ipcmd.addr_get(ifaceobj.name, details=False)
|
||||||
|
if newaddrs == runningaddrs:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
# if primary address is not same, there is no need to keep any.
|
||||||
|
# reset all addresses
|
||||||
|
if (newaddrs and runningaddrs and
|
||||||
|
(newaddrs[0] != runningaddrs[0])):
|
||||||
|
self.ipcmd.del_addr_all(ifaceobj.name)
|
||||||
|
else:
|
||||||
|
self.ipcmd.del_addr_all(ifaceobj.name, newaddrs)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
if not newaddrs:
|
||||||
|
return
|
||||||
|
for addr_index in range(0, len(newaddrs)):
|
||||||
|
try:
|
||||||
|
self.ipcmd.addr_add(ifaceobj.name, newaddrs[addr_index],
|
||||||
|
ifaceobj.get_attr_value_n('broadcast', addr_index),
|
||||||
|
ifaceobj.get_attr_value_n('pointopoint',addr_index),
|
||||||
|
ifaceobj.get_attr_value_n('scope', addr_index),
|
||||||
|
ifaceobj.get_attr_value_n('preferred-lifetime', addr_index))
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
|
||||||
|
def _up(self, ifaceobj):
|
||||||
|
if not self.ipcmd.link_exists(ifaceobj.name):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
# release any stale dhcp addresses if present
|
||||||
|
if (not self.PERFMODE and
|
||||||
|
not (ifaceobj.flags & iface.HAS_SIBLINGS)):
|
||||||
|
# if not running in perf mode and ifaceobj does not have
|
||||||
|
# any sibling iface objects, kill any stale dhclient
|
||||||
|
# processes
|
||||||
|
dhclientcmd = self.dhclient()
|
||||||
|
if dhclient.is_running(ifaceobj.name):
|
||||||
|
# release any dhcp leases
|
||||||
|
dhclientcmd.release(ifaceobj.name)
|
||||||
|
elif dhclient.is_running6(ifaceobj.name):
|
||||||
|
dhclientcmd.release6(ifaceobj.name)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.ipcmd.batch_start()
|
||||||
|
self._inet_address_config(ifaceobj)
|
||||||
|
mtu = ifaceobj.get_attr_value_first('mtu')
|
||||||
|
if mtu:
|
||||||
|
self.ipcmd.link_set(ifaceobj.name, 'mtu', mtu)
|
||||||
|
hwaddress = ifaceobj.get_attr_value_first('hwaddress')
|
||||||
|
if hwaddress:
|
||||||
|
self.ipcmd.link_set(ifaceobj.name, 'address', hwaddress)
|
||||||
|
alias = ifaceobj.get_attr_value_first('alias')
|
||||||
|
if alias:
|
||||||
|
self.ipcmd.link_set_alias(ifaceobj.name, alias)
|
||||||
|
self.ipcmd.batch_commit()
|
||||||
|
self.ipcmd.route_add_gateway(ifaceobj.name,
|
||||||
|
ifaceobj.get_attr_value_first('gateway'))
|
||||||
|
|
||||||
|
def _down(self, ifaceobj):
|
||||||
|
try:
|
||||||
|
if not self.ipcmd.link_exists(ifaceobj.name):
|
||||||
|
return
|
||||||
|
self.ipcmd.route_del_gateway(ifaceobj.name,
|
||||||
|
ifaceobj.get_attr_value_first('gateway'),
|
||||||
|
ifaceobj.get_attr_value_first('metric'))
|
||||||
|
self.ipcmd.del_addr_all(ifaceobj.name)
|
||||||
|
mtu = ifaceobj.get_attr_value_first('mtu')
|
||||||
|
if mtu:
|
||||||
|
self.ipcmd.link_set(ifaceobj.name, 'mtu',
|
||||||
|
self.get_mod_subattr('mtu', 'default'))
|
||||||
|
alias = ifaceobj.get_attr_value_first('alias')
|
||||||
|
if alias:
|
||||||
|
self.ipcmd.link_set(ifaceobj.name, 'alias', "\'\'")
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
|
||||||
|
def _get_iface_addresses(self, ifaceobj):
|
||||||
|
addrlist = ifaceobj.get_attr_value('address')
|
||||||
|
outaddrlist = []
|
||||||
|
|
||||||
|
if not addrlist: return None
|
||||||
|
for addrindex in range(0, len(addrlist)):
|
||||||
|
addr = addrlist[addrindex]
|
||||||
|
netmask = ifaceobj.get_attr_value_n('netmask', addrindex)
|
||||||
|
if netmask:
|
||||||
|
prefixlen = IPNetwork('%s' %addr +
|
||||||
|
'/%s' %netmask).prefixlen
|
||||||
|
addr = addr + '/%s' %prefixlen
|
||||||
|
outaddrlist.append(addr)
|
||||||
|
return outaddrlist
|
||||||
|
|
||||||
|
def _query_check(self, ifaceobj, ifaceobjcurr):
|
||||||
|
runningaddrsdict = None
|
||||||
|
if not self.ipcmd.link_exists(ifaceobj.name):
|
||||||
|
self.logger.debug('iface %s not found' %ifaceobj.name)
|
||||||
|
return
|
||||||
|
self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr,
|
||||||
|
'mtu', self.ipcmd.link_get_mtu)
|
||||||
|
self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr,
|
||||||
|
'hwaddress', self.ipcmd.link_get_hwaddress)
|
||||||
|
self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr,
|
||||||
|
'alias', self.ipcmd.link_get_alias)
|
||||||
|
# compare addresses
|
||||||
|
addrs = self._get_iface_addresses(ifaceobj)
|
||||||
|
runningaddrsdict = self.ipcmd.addr_get(ifaceobj.name)
|
||||||
|
|
||||||
|
# Set ifaceobjcurr method and family
|
||||||
|
ifaceobjcurr.addr_method = ifaceobj.addr_method
|
||||||
|
ifaceobjcurr.addr_family = ifaceobj.addr_family
|
||||||
|
if not runningaddrsdict and not addrs:
|
||||||
|
return
|
||||||
|
runningaddrs = runningaddrsdict.keys() if runningaddrsdict else []
|
||||||
|
if runningaddrs != addrs:
|
||||||
|
runningaddrsset = set(runningaddrs) if runningaddrs else set([])
|
||||||
|
addrsset = set(addrs) if addrs else set([])
|
||||||
|
if (ifaceobj.flags & iface.HAS_SIBLINGS):
|
||||||
|
if not addrsset:
|
||||||
|
return
|
||||||
|
# only check for addresses present in running config
|
||||||
|
addrsdiff = addrsset.difference(runningaddrsset)
|
||||||
|
for addr in addrs:
|
||||||
|
if addr in addrsdiff:
|
||||||
|
ifaceobjcurr.update_config_with_status('address',
|
||||||
|
addr, 1)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('address',
|
||||||
|
addr, 0)
|
||||||
|
else:
|
||||||
|
addrsdiff = addrsset.symmetric_difference(runningaddrsset)
|
||||||
|
for addr in addrsset.union(runningaddrsset):
|
||||||
|
if addr in addrsdiff:
|
||||||
|
ifaceobjcurr.update_config_with_status('address',
|
||||||
|
addr, 1)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('address',
|
||||||
|
addr, 0)
|
||||||
|
elif addrs:
|
||||||
|
[ifaceobjcurr.update_config_with_status('address',
|
||||||
|
addr, 0) for addr in addrs]
|
||||||
|
#XXXX Check broadcast address, scope, etc
|
||||||
|
return
|
||||||
|
|
||||||
|
def _query_running(self, ifaceobjrunning):
|
||||||
|
if not self.ipcmd.link_exists(ifaceobjrunning.name):
|
||||||
|
self.logger.debug('iface %s not found' %ifaceobjrunning.name)
|
||||||
|
ifaceobjrunning.status = ifaceStatus.NOTFOUND
|
||||||
|
return
|
||||||
|
dhclientcmd = dhclient()
|
||||||
|
if (dhclientcmd.is_running(ifaceobjrunning.name) or
|
||||||
|
dhclientcmd.is_running6(ifaceobjrunning.name)):
|
||||||
|
# If dhcp is configured on the interface, we skip it
|
||||||
|
return
|
||||||
|
isloopback = self.ipcmd.link_isloopback(ifaceobjrunning.name)
|
||||||
|
if isloopback:
|
||||||
|
default_addrs = ['127.0.0.1/8', '::1/128']
|
||||||
|
ifaceobjrunning.addr_family = 'inet'
|
||||||
|
ifaceobjrunning.addr_method = 'loopback'
|
||||||
|
else:
|
||||||
|
default_addrs = []
|
||||||
|
runningaddrsdict = self.ipcmd.addr_get(ifaceobjrunning.name)
|
||||||
|
if runningaddrsdict:
|
||||||
|
[ifaceobjrunning.update_config('address', addr)
|
||||||
|
for addr, addrattrs in runningaddrsdict.items()
|
||||||
|
if addr not in default_addrs]
|
||||||
|
mtu = self.ipcmd.link_get_mtu(ifaceobjrunning.name)
|
||||||
|
if (mtu and
|
||||||
|
(ifaceobjrunning.name == 'lo' and mtu != '16436') or
|
||||||
|
(ifaceobjrunning.name != 'lo' and
|
||||||
|
mtu != self.get_mod_subattr('mtu', 'default'))):
|
||||||
|
ifaceobjrunning.update_config('mtu', mtu)
|
||||||
|
alias = self.ipcmd.link_get_alias(ifaceobjrunning.name)
|
||||||
|
if alias:
|
||||||
|
ifaceobjrunning.update_config('alias', alias)
|
||||||
|
|
||||||
|
_run_ops = {'up' : _up,
|
||||||
|
'down' : _down,
|
||||||
|
'query-checkcurr' : _query_check,
|
||||||
|
'query-running' : _query_running }
|
||||||
|
|
||||||
|
def get_ops(self):
|
||||||
|
""" returns list of ops supported by this module """
|
||||||
|
return self._run_ops.keys()
|
||||||
|
|
||||||
|
def _init_command_handlers(self):
|
||||||
|
if not self.ipcmd:
|
||||||
|
self.ipcmd = iproute2(**self.get_flags())
|
||||||
|
|
||||||
|
def run(self, ifaceobj, operation, query_ifaceobj=None):
|
||||||
|
""" run address configuration on the interface object passed as argument
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**ifaceobj** (object): iface object
|
||||||
|
|
||||||
|
**operation** (str): any of 'up', 'down', 'query-checkcurr',
|
||||||
|
'query-running'
|
||||||
|
Kwargs:
|
||||||
|
query_ifaceobj (object): query check ifaceobject. This is only
|
||||||
|
valid when op is 'query-checkcurr'. It is an object same as
|
||||||
|
ifaceobj, but contains running attribute values and its config
|
||||||
|
status. The modules can use it to return queried running state
|
||||||
|
of interfaces. status is success if the running state is same
|
||||||
|
as user required state in ifaceobj. error otherwise.
|
||||||
|
"""
|
||||||
|
|
||||||
|
op_handler = self._run_ops.get(operation)
|
||||||
|
if not op_handler:
|
||||||
|
return
|
||||||
|
if (operation != 'query-running' and ifaceobj.addr_family and
|
||||||
|
ifaceobj.addr_family != 'inet' and
|
||||||
|
ifaceobj.addr_family != 'inet6'):
|
||||||
|
return
|
||||||
|
if (operation != 'query-running' and ifaceobj.addr_method and
|
||||||
|
ifaceobj.addr_method != 'static' and
|
||||||
|
ifaceobj.addr_method != 'loopback'):
|
||||||
|
return
|
||||||
|
self._init_command_handlers()
|
||||||
|
if operation == 'query-checkcurr':
|
||||||
|
op_handler(self, ifaceobj, query_ifaceobj)
|
||||||
|
else:
|
||||||
|
op_handler(self, ifaceobj)
|
169
addons/addressvirtual.py
Normal file
169
addons/addressvirtual.py
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
from ifupdown.iface import *
|
||||||
|
from ifupdownaddons.modulebase import moduleBase
|
||||||
|
from ifupdownaddons.iproute2 import iproute2
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
|
||||||
|
class addressvirtual(moduleBase):
|
||||||
|
""" ifupdown2 addon module to configure virtual addresses """
|
||||||
|
|
||||||
|
_modinfo = {'mhelp' : 'address module configures virtual addresses for ' +
|
||||||
|
'interfaces. It creates a macvlan interface for ' +
|
||||||
|
'every mac ip address-virtual line',
|
||||||
|
'attrs' : {
|
||||||
|
'address-virtual' :
|
||||||
|
{ 'help' : 'bridge router virtual mac and ip',
|
||||||
|
'example' : ['address-virtual 00:11:22:33:44:01 11.0.1.254/24 11.0.1.254/24']}
|
||||||
|
}}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
moduleBase.__init__(self, *args, **kargs)
|
||||||
|
self.ipcmd = None
|
||||||
|
|
||||||
|
def _is_supported(self, ifaceobj):
|
||||||
|
if ifaceobj.get_attr_value_first('address-virtual'):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _apply_address_config(self, ifaceobj, realifacename, address_virtual_list):
|
||||||
|
purge_existing = False if self.PERFMODE else True
|
||||||
|
|
||||||
|
self.ipcmd.batch_start()
|
||||||
|
av_idx = 0
|
||||||
|
macvlan_prefix = '%s-virt' %ifaceobj.name.replace('.', '-')
|
||||||
|
for av in address_virtual_list:
|
||||||
|
av_attrs = av.split()
|
||||||
|
if len(av_attrs) < 2:
|
||||||
|
self.logger.warn("%s: incorrect address-virtual attrs '%s'"
|
||||||
|
%(ifaceobj.name, av))
|
||||||
|
av_idx += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Create a macvlan device on this device and set the virtual
|
||||||
|
# router mac and ip on it
|
||||||
|
macvlan_ifacename = '%s-%d' %(macvlan_prefix, av_idx)
|
||||||
|
self.ipcmd.link_create_macvlan(macvlan_ifacename, realifacename)
|
||||||
|
self.ipcmd.link_set_hwaddress(macvlan_ifacename, av_attrs[0])
|
||||||
|
self.ipcmd.addr_add_multiple(macvlan_ifacename, av_attrs[1:],
|
||||||
|
purge_existing)
|
||||||
|
av_idx += 1
|
||||||
|
self.ipcmd.batch_commit()
|
||||||
|
|
||||||
|
def _remove_address_config(self, ifaceobj, ifacename):
|
||||||
|
if not self.ipcmd.link_exists(ifacename):
|
||||||
|
return
|
||||||
|
self.ipcmd.batch_start()
|
||||||
|
macvlan_prefix = '%s-virt' %ifacename.replace('.', '-')
|
||||||
|
for macvlan_ifacename in glob.glob("/sys/class/net/%s-*" %macvlan_prefix):
|
||||||
|
self.ipcmd.link_delete(os.path.basename(macvlan_ifacename))
|
||||||
|
self.ipcmd.batch_commit()
|
||||||
|
|
||||||
|
def _get_real_ifacename(self, ifaceobj):
|
||||||
|
realifacename = ifaceobj.name
|
||||||
|
if ifaceobj.type == ifaceType.BRIDGE_VLAN:
|
||||||
|
bridgename = ifaceobj.get_attr_value_first('bridge')
|
||||||
|
if bridgename:
|
||||||
|
realifacename = '%s.%s' %(bridgename, ifaceobj.priv_data)
|
||||||
|
return realifacename
|
||||||
|
|
||||||
|
def _up(self, ifaceobj):
|
||||||
|
realifacename = self._get_real_ifacename(ifaceobj)
|
||||||
|
address_virtual_list = ifaceobj.get_attr_value('address-virtual')
|
||||||
|
if not address_virtual_list:
|
||||||
|
# XXX: address virtual is not present. In which case,
|
||||||
|
# delete stale any macvlan devices.
|
||||||
|
self._remove_address_config(ifaceobj, realifacename)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.ipcmd.link_exists(realifacename):
|
||||||
|
self.log_warn('%s: target link %s does not exist'
|
||||||
|
%(ifaceobj.name, realifacename))
|
||||||
|
return
|
||||||
|
self._apply_address_config(ifaceobj, realifacename, address_virtual_list)
|
||||||
|
|
||||||
|
def _down(self, ifaceobj):
|
||||||
|
realifacename = self._get_real_ifacename(ifaceobj)
|
||||||
|
try:
|
||||||
|
self._remove_address_config(ifaceobj, realifacename)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
|
||||||
|
def _query_check(self, ifaceobj, ifaceobjcurr):
|
||||||
|
address_virtual_list = ifaceobj.get_attr_value('address-virtual')
|
||||||
|
if not address_virtual_list:
|
||||||
|
return
|
||||||
|
realifacename = self._get_real_ifacename(ifaceobj)
|
||||||
|
av_idx = 0
|
||||||
|
macvlan_prefix = '%s-virt' %realifacename.replace('.', '-')
|
||||||
|
for address_virtual in address_virtual_list:
|
||||||
|
av_attrs = address_virtual.split()
|
||||||
|
if len(av_attrs) < 2:
|
||||||
|
self.logger.warn("%s: incorrect address-virtual attrs '%s'"
|
||||||
|
%(ifaceobj.name, address_virtual))
|
||||||
|
av_idx += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if the macvlan device on this interface
|
||||||
|
macvlan_ifacename = '%s-%d' %(macvlan_prefix, av_idx)
|
||||||
|
if self.ipcmd.link_exists(macvlan_ifacename):
|
||||||
|
# XXX Check mac and ip address
|
||||||
|
rhwaddress = self.ipcmd.link_get_hwaddress(macvlan_ifacename)
|
||||||
|
raddrs = self.ipcmd.addr_get(macvlan_ifacename)
|
||||||
|
if rhwaddress == av_attrs[0] and raddrs == av_attrs[1:]:
|
||||||
|
ifaceobjcurr.update_config_with_status('address-virtual',
|
||||||
|
address_virtual, 0)
|
||||||
|
else:
|
||||||
|
raddress_virtual = '%s %s' %(rhwaddress, ' '.join(raddrs))
|
||||||
|
ifaceobjcurr.update_config_with_status('address-virtual',
|
||||||
|
raddress_virtual, 1)
|
||||||
|
av_idx += 1
|
||||||
|
return
|
||||||
|
|
||||||
|
def _query_running(self, ifaceobjrunning):
|
||||||
|
# Not implemented
|
||||||
|
return
|
||||||
|
|
||||||
|
_run_ops = {'up' : _up,
|
||||||
|
'down' : _down,
|
||||||
|
'query-checkcurr' : _query_check,
|
||||||
|
'query-running' : _query_running}
|
||||||
|
|
||||||
|
def get_ops(self):
|
||||||
|
""" returns list of ops supported by this module """
|
||||||
|
return self._run_ops.keys()
|
||||||
|
|
||||||
|
def _init_command_handlers(self):
|
||||||
|
if not self.ipcmd:
|
||||||
|
self.ipcmd = iproute2(**self.get_flags())
|
||||||
|
|
||||||
|
def run(self, ifaceobj, operation, query_ifaceobj=None):
|
||||||
|
""" run vlan configuration on the interface object passed as argument
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**ifaceobj** (object): iface object
|
||||||
|
|
||||||
|
**operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
|
||||||
|
'query-running'
|
||||||
|
Kwargs:
|
||||||
|
**query_ifaceobj** (object): query check ifaceobject. This is only
|
||||||
|
valid when op is 'query-checkcurr'. It is an object same as
|
||||||
|
ifaceobj, but contains running attribute values and its config
|
||||||
|
status. The modules can use it to return queried running state
|
||||||
|
of interfaces. status is success if the running state is same
|
||||||
|
as user required state in ifaceobj. error otherwise.
|
||||||
|
"""
|
||||||
|
op_handler = self._run_ops.get(operation)
|
||||||
|
if not op_handler:
|
||||||
|
return
|
||||||
|
self._init_command_handlers()
|
||||||
|
if operation == 'query-checkcurr':
|
||||||
|
op_handler(self, ifaceobj, query_ifaceobj)
|
||||||
|
else:
|
||||||
|
op_handler(self, ifaceobj)
|
895
addons/bridge.py
Normal file
895
addons/bridge.py
Normal file
@ -0,0 +1,895 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
from sets import Set
|
||||||
|
from ifupdown.iface import *
|
||||||
|
from ifupdownaddons.modulebase import moduleBase
|
||||||
|
from ifupdownaddons.bridgeutils import brctl
|
||||||
|
from ifupdownaddons.iproute2 import iproute2
|
||||||
|
import itertools
|
||||||
|
import re
|
||||||
|
|
||||||
|
class bridge(moduleBase):
|
||||||
|
""" ifupdown2 addon module to configure linux bridges """
|
||||||
|
|
||||||
|
_modinfo = { 'mhelp' : 'bridge configuration module',
|
||||||
|
'attrs' : {
|
||||||
|
'bridge-ports' :
|
||||||
|
{'help' : 'bridge ports',
|
||||||
|
'required' : True,
|
||||||
|
'example' : ['bridge-ports swp1.100 swp2.100 swp3.100',
|
||||||
|
'bridge-ports glob swp1-3.100',
|
||||||
|
'bridge-ports regex (swp[1|2|3].100)']},
|
||||||
|
'bridge-stp' :
|
||||||
|
{'help': 'bridge-stp yes/no',
|
||||||
|
'example' : ['bridge-stp no'],
|
||||||
|
'validvals' : ['yes', 'on', 'off', 'no'],
|
||||||
|
'default' : 'no'},
|
||||||
|
'bridge-bridgeprio' :
|
||||||
|
{'help': 'bridge priority',
|
||||||
|
'example' : ['bridge-bridgeprio 32768'],
|
||||||
|
'default' : '32768'},
|
||||||
|
'bridge-ageing' :
|
||||||
|
{'help': 'bridge ageing',
|
||||||
|
'example' : ['bridge-ageing 300'],
|
||||||
|
'default' : '300'},
|
||||||
|
'bridge-fd' :
|
||||||
|
{ 'help' : 'bridge forward delay',
|
||||||
|
'example' : ['bridge-fd 15'],
|
||||||
|
'default' : '15'},
|
||||||
|
'bridge-gcint' :
|
||||||
|
# XXX: recheck values
|
||||||
|
{ 'help' : 'bridge garbage collection interval in secs',
|
||||||
|
'example' : ['bridge-gcint 4'],
|
||||||
|
'default' : '4'},
|
||||||
|
'bridge-hello' :
|
||||||
|
{ 'help' : 'bridge set hello time',
|
||||||
|
'example' : ['bridge-hello 2'],
|
||||||
|
'default' : '2'},
|
||||||
|
'bridge-maxage' :
|
||||||
|
{ 'help' : 'bridge set maxage',
|
||||||
|
'example' : ['bridge-maxage 20'],
|
||||||
|
'default' : '20'},
|
||||||
|
'bridge-pathcosts' :
|
||||||
|
{ 'help' : 'bridge set port path costs',
|
||||||
|
'example' : ['bridge-pathcosts swp1=100 swp2=100'],
|
||||||
|
'default' : '100'},
|
||||||
|
'bridge-portprios' :
|
||||||
|
{ 'help' : 'bridge port prios',
|
||||||
|
'example' : ['bridge-portprios swp1=32 swp2=32'],
|
||||||
|
'default' : '32'},
|
||||||
|
'bridge-mclmc' :
|
||||||
|
{ 'help' : 'set multicast last member count',
|
||||||
|
'example' : ['bridge-mclmc 2'],
|
||||||
|
'default' : '2'},
|
||||||
|
'bridge-mcrouter' :
|
||||||
|
{ 'help' : 'set multicast router',
|
||||||
|
'default' : '1',
|
||||||
|
'example' : ['bridge-mcrouter 1']},
|
||||||
|
'bridge-mcsnoop' :
|
||||||
|
{ 'help' : 'set multicast snooping',
|
||||||
|
'default' : '1',
|
||||||
|
'example' : ['bridge-mcsnoop 1']},
|
||||||
|
'bridge-mcsqc' :
|
||||||
|
{ 'help' : 'set multicast startup query count',
|
||||||
|
'default' : '2',
|
||||||
|
'example' : ['bridge-mcsqc 2']},
|
||||||
|
'bridge-mcqifaddr' :
|
||||||
|
{ 'help' : 'set multicast query to use ifaddr',
|
||||||
|
'default' : '0',
|
||||||
|
'example' : ['bridge-mcqifaddr 0']},
|
||||||
|
'bridge-mcquerier' :
|
||||||
|
{ 'help' : 'set multicast querier',
|
||||||
|
'default' : '0',
|
||||||
|
'example' : ['bridge-mcquerier 0']},
|
||||||
|
'bridge-hashel' :
|
||||||
|
{ 'help' : 'set hash elasticity',
|
||||||
|
'default' : '4096',
|
||||||
|
'example' : ['bridge-hashel 4096']},
|
||||||
|
'bridge-hashmax' :
|
||||||
|
{ 'help' : 'set hash max',
|
||||||
|
'default' : '4096',
|
||||||
|
'example' : ['bridge-hashmax 4096']},
|
||||||
|
'bridge-mclmi' :
|
||||||
|
{ 'help' : 'set multicast last member interval (in secs)',
|
||||||
|
'default' : '1',
|
||||||
|
'example' : ['bridge-mclmi 1']},
|
||||||
|
'bridge-mcmi' :
|
||||||
|
{ 'help' : 'set multicast membership interval (in secs)',
|
||||||
|
'default' : '260',
|
||||||
|
'example' : ['bridge-mcmi 260']},
|
||||||
|
'bridge-mcqpi' :
|
||||||
|
{ 'help' : 'set multicast querier interval (in secs)',
|
||||||
|
'default' : '255',
|
||||||
|
'example' : ['bridge-mcqpi 255']},
|
||||||
|
'bridge-mcqi' :
|
||||||
|
{ 'help' : 'set multicast query interval (in secs)',
|
||||||
|
'default' : '125',
|
||||||
|
'example' : ['bridge-mcqi 125']},
|
||||||
|
'bridge-mcqri' :
|
||||||
|
{ 'help' : 'set multicast query response interval (in secs)',
|
||||||
|
'default' : '10',
|
||||||
|
'example' : ['bridge-mcqri 10']},
|
||||||
|
'bridge-mcsqi' :
|
||||||
|
{ 'help' : 'set multicast startup query interval (in secs)',
|
||||||
|
'default' : '31',
|
||||||
|
'example' : ['bridge-mcsqi 31']},
|
||||||
|
'bridge-mcqv4src' :
|
||||||
|
{ 'help' : 'set per VLAN v4 multicast querier source address',
|
||||||
|
'example' : ['bridge-mcqv4src 100=172.16.100.1 101=172.16.101.1']},
|
||||||
|
'bridge-portmcrouter' :
|
||||||
|
{ 'help' : 'set port multicast routers',
|
||||||
|
'default' : '1',
|
||||||
|
'example' : ['bridge-portmcrouter swp1=1 swp2=1']},
|
||||||
|
'bridge-portmcfl' :
|
||||||
|
{ 'help' : 'port multicast fast leave',
|
||||||
|
'default' : '0',
|
||||||
|
'example' : ['bridge-portmcfl swp1=0 swp2=0']},
|
||||||
|
'bridge-waitport' :
|
||||||
|
{ 'help' : 'wait for a max of time secs for the' +
|
||||||
|
' specified ports to become available,' +
|
||||||
|
'if no ports are specified then those' +
|
||||||
|
' specified on bridge-ports will be' +
|
||||||
|
' used here. Specifying no ports here ' +
|
||||||
|
'should not be used if we are using ' +
|
||||||
|
'regex or \"all\" on bridge_ports,' +
|
||||||
|
'as it wouldnt work.',
|
||||||
|
'default' : '0',
|
||||||
|
'example' : ['bridge-waitport 4 swp1 swp2']},
|
||||||
|
'bridge-maxwait' :
|
||||||
|
{ 'help' : 'forces to time seconds the maximum time ' +
|
||||||
|
'that the Debian bridge setup scripts will ' +
|
||||||
|
'wait for the bridge ports to get to the ' +
|
||||||
|
'forwarding status, doesn\'t allow factional ' +
|
||||||
|
'part. If it is equal to 0 then no waiting' +
|
||||||
|
' is done',
|
||||||
|
'default' : '0',
|
||||||
|
'example' : ['bridge-maxwait 3']},
|
||||||
|
'bridge-vids' :
|
||||||
|
{ 'help' : 'bridge vlans',
|
||||||
|
'example' : ['bridge-vids 4000']},
|
||||||
|
'bridge-port-vids' :
|
||||||
|
{ 'help' : 'bridge vlans',
|
||||||
|
'example' : ['bridge-port-vids bond0=1-1000,1010-1020']},
|
||||||
|
'bridge-port-pvids' :
|
||||||
|
{ 'help' : 'bridge port vlans',
|
||||||
|
'example' : ['bridge-port-pvids bond0=100 bond1=200']},
|
||||||
|
}}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
moduleBase.__init__(self, *args, **kargs)
|
||||||
|
self.ipcmd = None
|
||||||
|
self.brctlcmd = None
|
||||||
|
|
||||||
|
def _is_bridge(self, ifaceobj):
|
||||||
|
if ifaceobj.get_attr_value_first('bridge-ports'):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None):
|
||||||
|
if not self._is_bridge(ifaceobj):
|
||||||
|
return None
|
||||||
|
return self.parse_port_list(ifaceobj.get_attr_value_first(
|
||||||
|
'bridge-ports'), ifacenames_all)
|
||||||
|
|
||||||
|
def get_dependent_ifacenames_running(self, ifaceobj):
|
||||||
|
self._init_command_handlers()
|
||||||
|
if not self.brctlcmd.bridge_exists(ifaceobj.name):
|
||||||
|
return None
|
||||||
|
return self.brctlcmd.get_bridge_ports(ifaceobj.name)
|
||||||
|
|
||||||
|
def _get_bridge_port_list(self, ifaceobj):
|
||||||
|
|
||||||
|
# port list is also available in the previously
|
||||||
|
# parsed dependent list. Use that if available, instead
|
||||||
|
# of parsing port expr again
|
||||||
|
port_list = ifaceobj.lowerifaces
|
||||||
|
if port_list:
|
||||||
|
return port_list
|
||||||
|
ports = ifaceobj.get_attr_value_first('bridge-ports')
|
||||||
|
if ports:
|
||||||
|
return self.parse_port_list(ports)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _process_bridge_waitport(self, ifaceobj, portlist):
|
||||||
|
waitport_value = ifaceobj.get_attr_value_first('bridge-waitport')
|
||||||
|
if not waitport_value: return
|
||||||
|
try:
|
||||||
|
waitportvals = re.split(r'[\s\t]\s*', waitport_value, 1)
|
||||||
|
if not waitportvals: return
|
||||||
|
try:
|
||||||
|
waitporttime = int(waitportvals[0])
|
||||||
|
except:
|
||||||
|
self.log_warn('%s: invalid waitport value \'%s\''
|
||||||
|
%(ifaceobj.name, waitporttime))
|
||||||
|
return
|
||||||
|
if waitporttime <= 0: return
|
||||||
|
try:
|
||||||
|
waitportlist = self.parse_port_list(waitportvals[1])
|
||||||
|
except IndexError, e:
|
||||||
|
# ignore error and use all bridge ports
|
||||||
|
waitportlist = portlist
|
||||||
|
pass
|
||||||
|
if not waitportlist: return
|
||||||
|
self.logger.info('%s: waiting for ports %s to exist ...'
|
||||||
|
%(ifaceobj.name, str(waitportlist)))
|
||||||
|
starttime = time.time()
|
||||||
|
while ((time.time() - starttime) < waitporttime):
|
||||||
|
if all([False for p in waitportlist
|
||||||
|
if not self.ipcmd.link_exists(p)]):
|
||||||
|
break;
|
||||||
|
time.sleep(1)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: unable to process waitport: %s'
|
||||||
|
%(ifaceobj.name, str(e)))
|
||||||
|
|
||||||
|
def _add_ports(self, ifaceobj):
|
||||||
|
bridgeports = self._get_bridge_port_list(ifaceobj)
|
||||||
|
runningbridgeports = []
|
||||||
|
|
||||||
|
self._process_bridge_waitport(ifaceobj, bridgeports)
|
||||||
|
# Delete active ports not in the new port list
|
||||||
|
if not self.PERFMODE:
|
||||||
|
runningbridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name)
|
||||||
|
if runningbridgeports:
|
||||||
|
[self.ipcmd.link_set(bport, 'nomaster')
|
||||||
|
for bport in runningbridgeports
|
||||||
|
if not bridgeports or bport not in bridgeports]
|
||||||
|
else:
|
||||||
|
runningbridgeports = []
|
||||||
|
if not bridgeports:
|
||||||
|
return
|
||||||
|
err = 0
|
||||||
|
for bridgeport in Set(bridgeports).difference(Set(runningbridgeports)):
|
||||||
|
try:
|
||||||
|
if not self.DRYRUN and not self.ipcmd.link_exists(bridgeport):
|
||||||
|
self.log_warn('%s: bridge port %s does not exist'
|
||||||
|
%(ifaceobj.name, bridgeport))
|
||||||
|
err += 1
|
||||||
|
continue
|
||||||
|
self.ipcmd.link_set(bridgeport, 'master', ifaceobj.name)
|
||||||
|
self.write_file('/proc/sys/net/ipv6/conf/%s' %bridgeport +
|
||||||
|
'/disable_ipv6', '1')
|
||||||
|
self.ipcmd.addr_flush(bridgeport)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
if err:
|
||||||
|
self.log_error('bridge configuration failed (missing ports)')
|
||||||
|
|
||||||
|
def _process_bridge_maxwait(self, ifaceobj, portlist):
|
||||||
|
maxwait = ifaceobj.get_attr_value_first('bridge-maxwait')
|
||||||
|
if not maxwait: return
|
||||||
|
try:
|
||||||
|
maxwait = int(maxwait)
|
||||||
|
except:
|
||||||
|
self.log_warn('%s: invalid maxwait value \'%s\'' %(ifaceobj.name,
|
||||||
|
maxwait))
|
||||||
|
return
|
||||||
|
if not maxwait: return
|
||||||
|
self.logger.info('%s: waiting for ports to go to fowarding state ..'
|
||||||
|
%ifaceobj.name)
|
||||||
|
try:
|
||||||
|
starttime = time.time()
|
||||||
|
while ((time.time() - starttime) < maxwait):
|
||||||
|
if all([False for p in portlist
|
||||||
|
if self.read_file_oneline(
|
||||||
|
'/sys/class/net/%s/brif/%s/state'
|
||||||
|
%(ifaceobj.name, p)) != '3']):
|
||||||
|
break;
|
||||||
|
time.sleep(1)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: unable to process maxwait: %s'
|
||||||
|
%(ifaceobj.name, str(e)))
|
||||||
|
|
||||||
|
def _ints_to_ranges(self, ints):
|
||||||
|
for a, b in itertools.groupby(enumerate(ints), lambda (x, y): y - x):
|
||||||
|
b = list(b)
|
||||||
|
yield b[0][1], b[-1][1]
|
||||||
|
|
||||||
|
def _ranges_to_ints(self, rangelist):
|
||||||
|
""" returns expanded list of integers given set of string ranges
|
||||||
|
example: ['1', '2-4', '6'] returns [1, 2, 3, 4, 6]
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
for part in rangelist:
|
||||||
|
if '-' in part:
|
||||||
|
a, b = part.split('-')
|
||||||
|
a, b = int(a), int(b)
|
||||||
|
result.extend(range(a, b + 1))
|
||||||
|
else:
|
||||||
|
a = int(part)
|
||||||
|
result.append(a)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _diff_vids(self, vids1, vids2):
|
||||||
|
vids_to_add = None
|
||||||
|
vids_to_del = None
|
||||||
|
|
||||||
|
vids1_ints = self._ranges_to_ints(vids1)
|
||||||
|
vids2_ints = self._ranges_to_ints(vids2)
|
||||||
|
vids1_diff = Set(vids1_ints).difference(vids2_ints)
|
||||||
|
vids2_diff = Set(vids2_ints).difference(vids1_ints)
|
||||||
|
if vids1_diff:
|
||||||
|
vids_to_add = ['%d' %start if start == end else '%d-%d' %(start, end)
|
||||||
|
for start, end in self._ints_to_ranges(vids1_diff)]
|
||||||
|
if vids2_diff:
|
||||||
|
vids_to_del = ['%d' %start if start == end else '%d-%d' %(start, end)
|
||||||
|
for start, end in self._ints_to_ranges(vids2_diff)]
|
||||||
|
return (vids_to_del, vids_to_add)
|
||||||
|
|
||||||
|
def _compare_vids(self, vids1, vids2):
|
||||||
|
""" Returns true if the vids are same else return false """
|
||||||
|
|
||||||
|
vids1_ints = self._ranges_to_ints(vids1)
|
||||||
|
vids2_ints = self._ranges_to_ints(vids2)
|
||||||
|
if Set(vids1_ints).symmetric_difference(vids2_ints):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _set_bridge_mcqv4src(self, ifaceobj):
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src')
|
||||||
|
if attrval:
|
||||||
|
running_mcqv4src = {}
|
||||||
|
if not self.PERFMODE:
|
||||||
|
running_mcqv4src = self.brctlcmd.get_mcqv4src(ifaceobj.name)
|
||||||
|
mcqs = {}
|
||||||
|
srclist = attrval.split()
|
||||||
|
for s in srclist:
|
||||||
|
k, v = s.split('=')
|
||||||
|
mcqs[k] = v
|
||||||
|
|
||||||
|
k_to_del = Set(running_mcqv4src.keys()).difference(mcqs.keys())
|
||||||
|
for v in k_to_del:
|
||||||
|
self.brctlcmd.del_mcqv4src(ifaceobj.name, v)
|
||||||
|
for v in mcqs.keys():
|
||||||
|
self.brctlcmd.set_mcqv4src(ifaceobj.name, v, mcqs[v])
|
||||||
|
|
||||||
|
def _set_bridge_vidinfo(self, ifaceobj):
|
||||||
|
# Handle bridge vlan attrs
|
||||||
|
running_vidinfo = {}
|
||||||
|
if not self.PERFMODE:
|
||||||
|
running_vidinfo = self.ipcmd.bridge_port_vids_get_all()
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bridge-port-vids')
|
||||||
|
if attrval:
|
||||||
|
portlist = self.parse_port_list(attrval)
|
||||||
|
if not portlist:
|
||||||
|
self.log_warn('%s: could not parse \'%s %s\''
|
||||||
|
%(ifaceobj.name, attrname, attrval))
|
||||||
|
return
|
||||||
|
for p in portlist:
|
||||||
|
try:
|
||||||
|
(port, val) = p.split('=')
|
||||||
|
vids = val.split(',')
|
||||||
|
if running_vidinfo.get(port):
|
||||||
|
(vids_to_del, vids_to_add) = \
|
||||||
|
self._diff_vids(vids,
|
||||||
|
running_vidinfo.get(port).get('vlan'))
|
||||||
|
if vids_to_del:
|
||||||
|
self.ipcmd.bridge_port_vids_del(port, vids_to_del)
|
||||||
|
if vids_to_add:
|
||||||
|
self.ipcmd.bridge_port_vids_add(port, vids_to_add)
|
||||||
|
else:
|
||||||
|
self.ipcmd.bridge_port_vids_add(port, vids)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: failed to set vid `%s` (%s)'
|
||||||
|
%(ifaceobj.name, p, str(e)))
|
||||||
|
|
||||||
|
# Install pvids
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bridge-port-pvids')
|
||||||
|
if attrval:
|
||||||
|
portlist = self.parse_port_list(attrval)
|
||||||
|
if not portlist:
|
||||||
|
self.log_warn('%s: could not parse \'%s %s\''
|
||||||
|
%(ifaceobj.name, attrname, attrval))
|
||||||
|
return
|
||||||
|
for p in portlist:
|
||||||
|
try:
|
||||||
|
(port, pvid) = p.split('=')
|
||||||
|
running_pvid = running_vidinfo.get(port, {}).get('pvid')
|
||||||
|
if running_pvid:
|
||||||
|
if running_pvid == pvid:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
self.ipcmd.bridge_port_pvid_del(port, running_pvid)
|
||||||
|
self.ipcmd.bridge_port_pvid_add(port, pvid)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: failed to set pvid `%s` (%s)'
|
||||||
|
%(ifaceobj.name, p, str(e)))
|
||||||
|
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bridge-vids')
|
||||||
|
if attrval:
|
||||||
|
vids = re.split(r'[\s\t]\s*', attrval)
|
||||||
|
if running_vidinfo.get(ifaceobj.name):
|
||||||
|
(vids_to_del, vids_to_add) = \
|
||||||
|
self._diff_vids(vids,
|
||||||
|
running_vidinfo.get(ifaceobj.name).get('vlan'))
|
||||||
|
if vids_to_del:
|
||||||
|
self.ipcmd.bridge_vids_del(ifaceobj.name, vids_to_del)
|
||||||
|
if vids_to_add:
|
||||||
|
self.ipcmd.bridge_vids_add(ifaceobj.name, vids_to_add)
|
||||||
|
else:
|
||||||
|
self.ipcmd.bridge_vids_add(ifaceobj.name, vids)
|
||||||
|
else:
|
||||||
|
running_vids = running_vidinfo.get(ifaceobj.name)
|
||||||
|
if running_vids:
|
||||||
|
self.ipcmd.bridge_vids_del(ifaceobj.name, running_vids)
|
||||||
|
|
||||||
|
def _apply_bridge_settings(self, ifaceobj):
|
||||||
|
try:
|
||||||
|
stp = ifaceobj.get_attr_value_first('bridge-stp')
|
||||||
|
if stp:
|
||||||
|
self.brctlcmd.set_stp(ifaceobj.name, stp)
|
||||||
|
# Use the brctlcmd bulk set method: first build a dictionary
|
||||||
|
# and then call set
|
||||||
|
bridgeattrs = { k:v for k,v in
|
||||||
|
{'ageing' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-ageing'),
|
||||||
|
'bridgeprio' :
|
||||||
|
ifaceobj.get_attr_value_first(
|
||||||
|
'bridge-bridgeprio'),
|
||||||
|
'fd' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-fd'),
|
||||||
|
'gcint' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-gcint'),
|
||||||
|
'hello' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-hello'),
|
||||||
|
'maxage' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-maxage'),
|
||||||
|
'mclmc' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mclmc'),
|
||||||
|
'mcrouter' :
|
||||||
|
ifaceobj.get_attr_value_first(
|
||||||
|
'bridge-mcrouter'),
|
||||||
|
'mcsnoop' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mcsnoop'),
|
||||||
|
'mcsqc' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mcsqc'),
|
||||||
|
'mcqifaddr' :
|
||||||
|
ifaceobj.get_attr_value_first(
|
||||||
|
'bridge-mcqifaddr'),
|
||||||
|
'mcquerier' :
|
||||||
|
ifaceobj.get_attr_value_first(
|
||||||
|
'bridge-mcquerier'),
|
||||||
|
'hashel' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-hashel'),
|
||||||
|
'hashmax' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-hashmax'),
|
||||||
|
'mclmi' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mclmi'),
|
||||||
|
'mcmi' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mcmi'),
|
||||||
|
'mcqpi' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mcqpi'),
|
||||||
|
'mcqi' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mcqi'),
|
||||||
|
'mcqri' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mcqri'),
|
||||||
|
'mcsqi' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mcsqi')
|
||||||
|
}.items()
|
||||||
|
if v }
|
||||||
|
if bridgeattrs:
|
||||||
|
self.brctlcmd.set_bridge_attrs(ifaceobj.name, bridgeattrs)
|
||||||
|
portattrs = {}
|
||||||
|
for attrname, dstattrname in {'bridge-pathcosts' : 'pathcost',
|
||||||
|
'bridge-portprios' : 'portprio',
|
||||||
|
'bridge-portmcrouter' : 'portmcrouter',
|
||||||
|
'bridge-portmcfl' : 'portmcfl'}.items():
|
||||||
|
attrval = ifaceobj.get_attr_value_first(attrname)
|
||||||
|
if not attrval:
|
||||||
|
continue
|
||||||
|
portlist = self.parse_port_list(attrval)
|
||||||
|
if not portlist:
|
||||||
|
self.log_warn('%s: could not parse \'%s %s\''
|
||||||
|
%(ifaceobj.name, attrname, attrval))
|
||||||
|
continue
|
||||||
|
for p in portlist:
|
||||||
|
try:
|
||||||
|
(port, val) = p.split('=')
|
||||||
|
if not portattrs.get(port):
|
||||||
|
portattrs[port] = {}
|
||||||
|
portattrs[port].update({dstattrname : val})
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: could not parse %s (%s)'
|
||||||
|
%(ifaceobj.name, attrname, str(e)))
|
||||||
|
for port, attrdict in portattrs.iteritems():
|
||||||
|
self.brctlcmd.set_bridgeport_attrs(ifaceobj.name, port,
|
||||||
|
attrdict)
|
||||||
|
self._set_bridge_vidinfo(ifaceobj)
|
||||||
|
|
||||||
|
self._set_bridge_mcqv4src(ifaceobj)
|
||||||
|
|
||||||
|
self._process_bridge_maxwait(ifaceobj,
|
||||||
|
self._get_bridge_port_list(ifaceobj))
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
|
||||||
|
def _up(self, ifaceobj):
|
||||||
|
try:
|
||||||
|
porterr = False
|
||||||
|
porterrstr = ''
|
||||||
|
self.ipcmd.batch_start()
|
||||||
|
if not self.PERFMODE:
|
||||||
|
if not self.ipcmd.link_exists(ifaceobj.name):
|
||||||
|
self.ipcmd.link_create(ifaceobj.name, 'bridge')
|
||||||
|
else:
|
||||||
|
self.ipcmd.link_create(ifaceobj.name, 'bridge')
|
||||||
|
try:
|
||||||
|
self._add_ports(ifaceobj)
|
||||||
|
except Exception, e:
|
||||||
|
porterr = True
|
||||||
|
porterrstr = str(e)
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
self.ipcmd.batch_commit()
|
||||||
|
self._apply_bridge_settings(ifaceobj)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
if porterr:
|
||||||
|
raise Exception(porterrstr)
|
||||||
|
|
||||||
|
def _down(self, ifaceobj):
|
||||||
|
try:
|
||||||
|
if ifaceobj.get_attr_value_first('bridge-ports'):
|
||||||
|
ports = self.brctlcmd.get_bridge_ports(ifaceobj.name)
|
||||||
|
if ports:
|
||||||
|
for p in ports:
|
||||||
|
proc_file = ('/proc/sys/net/ipv6/conf/%s' %p +
|
||||||
|
'/disable_ipv6')
|
||||||
|
self.write_file(proc_file, '0')
|
||||||
|
self.brctlcmd.delete_bridge(ifaceobj.name)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
|
||||||
|
def _query_running_vidinfo(self, ifaceobjrunning, ports):
|
||||||
|
running_attrs = {}
|
||||||
|
running_vidinfo = self.ipcmd.bridge_port_vids_get_all()
|
||||||
|
|
||||||
|
if ports:
|
||||||
|
running_bridge_port_vids = ''
|
||||||
|
for p in ports:
|
||||||
|
try:
|
||||||
|
running_vids = running_vidinfo.get(p, {}).get('vlan')
|
||||||
|
if running_vids:
|
||||||
|
running_bridge_port_vids += ' %s=%s' %(p,
|
||||||
|
','.join(running_vids))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
running_attrs['bridge-port-vids'] = running_bridge_port_vids
|
||||||
|
|
||||||
|
running_bridge_port_pvids = ''
|
||||||
|
for p in ports:
|
||||||
|
try:
|
||||||
|
running_pvids = running_vidinfo.get(p, {}).get('pvid')
|
||||||
|
if running_pvids:
|
||||||
|
running_bridge_port_pvids += ' %s=%s' %(p,
|
||||||
|
running_pvids)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
running_attrs['bridge-port-pvids'] = running_bridge_port_pvids
|
||||||
|
|
||||||
|
running_bridge_vids = running_vidinfo.get(ifaceobjrunning.name, {}).get('vlan')
|
||||||
|
if running_bridge_vids:
|
||||||
|
running_attrs['bridge-vids'] = ','.join(running_bridge_vids)
|
||||||
|
return running_attrs
|
||||||
|
|
||||||
|
def _query_running_mcqv4src(self, ifaceobjrunning):
|
||||||
|
running_mcqv4src = self.brctlcmd.get_mcqv4src(ifaceobjrunning.name)
|
||||||
|
mcqs = ['%s=%s' %(v, i) for v, i in running_mcqv4src.items()]
|
||||||
|
mcqs.sort()
|
||||||
|
mcq = ' '.join(mcqs)
|
||||||
|
return mcq
|
||||||
|
|
||||||
|
def _query_running_attrs(self, ifaceobjrunning):
|
||||||
|
bridgeattrdict = {}
|
||||||
|
userspace_stp = 0
|
||||||
|
ports = None
|
||||||
|
skip_kernel_stp_attrs = 0
|
||||||
|
|
||||||
|
if self.sysctl_get('net.bridge.bridge-stp-user-space') == '1':
|
||||||
|
userspace_stp = 1
|
||||||
|
|
||||||
|
tmpbridgeattrdict = self.brctlcmd.get_bridge_attrs(ifaceobjrunning.name)
|
||||||
|
if not tmpbridgeattrdict:
|
||||||
|
self.logger.warn('%s: unable to get bridge attrs'
|
||||||
|
%ifaceobjrunning.name)
|
||||||
|
return bridgeattrdict
|
||||||
|
|
||||||
|
# Fill bridge_ports and bridge stp attributes first
|
||||||
|
ports = tmpbridgeattrdict.get('ports')
|
||||||
|
if ports:
|
||||||
|
bridgeattrdict['bridge-ports'] = [' '.join(ports.keys())]
|
||||||
|
stp = tmpbridgeattrdict.get('stp', 'no')
|
||||||
|
if stp != self.get_mod_subattr('bridge-stp', 'default'):
|
||||||
|
bridgeattrdict['bridge-stp'] = [stp]
|
||||||
|
|
||||||
|
if stp == 'yes' and userspace_stp:
|
||||||
|
skip_kernel_stp_attrs = 1
|
||||||
|
|
||||||
|
# pick all other attributes
|
||||||
|
for k,v in tmpbridgeattrdict.items():
|
||||||
|
if not v:
|
||||||
|
continue
|
||||||
|
if k == 'ports' or k == 'stp':
|
||||||
|
continue
|
||||||
|
|
||||||
|
if skip_kernel_stp_attrs and k[:2] != 'mc':
|
||||||
|
# only include igmp attributes if kernel stp is off
|
||||||
|
continue
|
||||||
|
attrname = 'bridge-' + k
|
||||||
|
if v != self.get_mod_subattr(attrname, 'default'):
|
||||||
|
bridgeattrdict[attrname] = [v]
|
||||||
|
|
||||||
|
bridgevidinfo = self._query_running_vidinfo(ifaceobjrunning, ports)
|
||||||
|
if bridgevidinfo:
|
||||||
|
bridgeattrdict.update({k : [v] for k, v in bridgevidinfo.items()
|
||||||
|
if v})
|
||||||
|
|
||||||
|
mcq = self._query_running_mcqv4src(ifaceobjrunning)
|
||||||
|
if mcq:
|
||||||
|
bridgeattrdict['bridge-mcqv4src'] = [mcq]
|
||||||
|
|
||||||
|
if skip_kernel_stp_attrs:
|
||||||
|
return bridgeattrdict
|
||||||
|
|
||||||
|
if ports:
|
||||||
|
portconfig = {'bridge-pathcosts' : '',
|
||||||
|
'bridge-portprios' : ''}
|
||||||
|
for p, v in ports.items():
|
||||||
|
v = self.brctlcmd.get_pathcost(ifaceobjrunning.name, p)
|
||||||
|
if v and v != self.get_mod_subattr('bridge-pathcosts',
|
||||||
|
'default'):
|
||||||
|
portconfig['bridge-pathcosts'] += ' %s=%s' %(p, v)
|
||||||
|
|
||||||
|
v = self.brctlcmd.get_portprio(ifaceobjrunning.name, p)
|
||||||
|
if v and v != self.get_mod_subattr('bridge-portprios',
|
||||||
|
'default'):
|
||||||
|
portconfig['bridge-portprios'] += ' %s=%s' %(p, v)
|
||||||
|
|
||||||
|
bridgeattrdict.update({k : [v] for k, v in portconfig.items()
|
||||||
|
if v})
|
||||||
|
|
||||||
|
return bridgeattrdict
|
||||||
|
|
||||||
|
def _query_check_mcqv4src(self, ifaceobj, ifaceobjcurr):
|
||||||
|
running_mcqs = self._query_running_mcqv4src(ifaceobj)
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src')
|
||||||
|
if attrval:
|
||||||
|
mcqs = attrval.split()
|
||||||
|
mcqs.sort()
|
||||||
|
mcqsout = ' '.join(mcqs)
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-mcqv4src',
|
||||||
|
running_mcqs, 1 if running_mcqs != mcqsout else 0)
|
||||||
|
|
||||||
|
def _query_check_vidinfo(self, ifaceobj, ifaceobjcurr):
|
||||||
|
|
||||||
|
err = 0
|
||||||
|
running_vidinfo = self.ipcmd.bridge_port_vids_get_all()
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bridge-port-vids')
|
||||||
|
if attrval:
|
||||||
|
running_bridge_port_vids = ''
|
||||||
|
portlist = self.parse_port_list(attrval)
|
||||||
|
if not portlist:
|
||||||
|
self.log_warn('%s: could not parse \'%s %s\''
|
||||||
|
%(ifaceobj.name, attrname, attrval))
|
||||||
|
return
|
||||||
|
err = 0
|
||||||
|
for p in portlist:
|
||||||
|
try:
|
||||||
|
(port, val) = p.split('=')
|
||||||
|
vids = val.split(',')
|
||||||
|
running_vids = running_vidinfo.get(port, {}).get('vlan')
|
||||||
|
if running_vids:
|
||||||
|
if not self._compare_vids(vids, running_vids):
|
||||||
|
err += 1
|
||||||
|
running_bridge_port_vids += ' %s=%s' %(port,
|
||||||
|
','.join(running_vids))
|
||||||
|
else:
|
||||||
|
running_bridge_port_vids += ' %s' %p
|
||||||
|
else:
|
||||||
|
err += 1
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: failure checking vid %s (%s)'
|
||||||
|
%(ifaceobj.name, p, str(e)))
|
||||||
|
if err:
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-port-vids',
|
||||||
|
running_bridge_port_vids, 1)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-port-vids',
|
||||||
|
attrval, 0)
|
||||||
|
|
||||||
|
# Install pvids
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bridge-port-pvids')
|
||||||
|
if attrval:
|
||||||
|
portlist = self.parse_port_list(attrval)
|
||||||
|
if not portlist:
|
||||||
|
self.log_warn('%s: could not parse \'%s %s\''
|
||||||
|
%(ifaceobj.name, attrname, attrval))
|
||||||
|
return
|
||||||
|
running_bridge_port_pvids = ''
|
||||||
|
err = 0
|
||||||
|
for p in portlist:
|
||||||
|
try:
|
||||||
|
(port, pvid) = p.split('=')
|
||||||
|
running_pvid = running_vidinfo.get(port, {}).get('pvid')
|
||||||
|
if running_pvid and running_pvid == pvid:
|
||||||
|
running_bridge_port_pvids += ' %s' %p
|
||||||
|
else:
|
||||||
|
err += 1
|
||||||
|
running_bridge_port_pvids += ' %s=%s' %(port,
|
||||||
|
running_pvid)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: failure checking pvid %s (%s)'
|
||||||
|
%(ifaceobj.name, pvid, str(e)))
|
||||||
|
if err:
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-port-pvids',
|
||||||
|
running_bridge_port_pvids, 1)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-port-pvids',
|
||||||
|
running_bridge_port_pvids, 0)
|
||||||
|
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bridge-vids')
|
||||||
|
if attrval:
|
||||||
|
vids = re.split(r'[\s\t]\s*', attrval)
|
||||||
|
running_vids = running_vidinfo.get(ifaceobj.name, {}).get('vlan')
|
||||||
|
if running_vids:
|
||||||
|
if self._compare_vids(vids, running_vids):
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-vids',
|
||||||
|
attrval, 0)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-vids',
|
||||||
|
','.join(running_vids), 1)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-vids', attrval,
|
||||||
|
1)
|
||||||
|
|
||||||
|
def _query_check(self, ifaceobj, ifaceobjcurr):
|
||||||
|
if not self.brctlcmd.bridge_exists(ifaceobj.name):
|
||||||
|
self.logger.info('%s: bridge: does not exist' %(ifaceobj.name))
|
||||||
|
ifaceobjcurr.status = ifaceStatus.NOTFOUND
|
||||||
|
return
|
||||||
|
ifaceattrs = self.dict_key_subset(ifaceobj.config,
|
||||||
|
self.get_mod_attrs())
|
||||||
|
if not ifaceattrs:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
runningattrs = self.brctlcmd.get_bridge_attrs(ifaceobj.name)
|
||||||
|
if not runningattrs:
|
||||||
|
self.logger.debug('%s: bridge: unable to get bridge attrs'
|
||||||
|
%ifaceobj.name)
|
||||||
|
runningattrs = {}
|
||||||
|
except Exception, e:
|
||||||
|
self.logger.warn(str(e))
|
||||||
|
runningattrs = {}
|
||||||
|
filterattrs = ['bridge-vids', 'bridge-port-vids',
|
||||||
|
'bridge-port-pvids']
|
||||||
|
for k in Set(ifaceattrs).difference(filterattrs):
|
||||||
|
# get the corresponding ifaceobj attr
|
||||||
|
v = ifaceobj.get_attr_value_first(k)
|
||||||
|
if not v:
|
||||||
|
continue
|
||||||
|
rv = runningattrs.get(k[7:])
|
||||||
|
if k == 'bridge-mcqv4src':
|
||||||
|
continue
|
||||||
|
if k == 'bridge-stp':
|
||||||
|
# special case stp compare because it may
|
||||||
|
# contain more than one valid values
|
||||||
|
stp_on_vals = ['on', 'yes']
|
||||||
|
stp_off_vals = ['off']
|
||||||
|
if ((v in stp_on_vals and rv in stp_on_vals) or
|
||||||
|
(v in stp_off_vals and rv in stp_off_vals)):
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-stp',
|
||||||
|
v, 0)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-stp',
|
||||||
|
v, 1)
|
||||||
|
elif k == 'bridge-ports':
|
||||||
|
# special case ports because it can contain regex or glob
|
||||||
|
running_port_list = rv.keys() if rv else []
|
||||||
|
bridge_port_list = self._get_bridge_port_list(ifaceobj)
|
||||||
|
if not running_port_list and not bridge_port_list:
|
||||||
|
continue
|
||||||
|
portliststatus = 1
|
||||||
|
if running_port_list and bridge_port_list:
|
||||||
|
difference = set(running_port_list
|
||||||
|
).symmetric_difference(bridge_port_list)
|
||||||
|
if not difference:
|
||||||
|
portliststatus = 0
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-ports',
|
||||||
|
' '.join(running_port_list)
|
||||||
|
if running_port_list else '', portliststatus)
|
||||||
|
elif (k == 'bridge-pathcosts' or
|
||||||
|
k == 'bridge-portprios' or k == 'bridge-portmcrouter'
|
||||||
|
or k == 'bridge-portmcfl'):
|
||||||
|
brctlcmdattrname = k[11:].rstrip('s')
|
||||||
|
# for port attributes, the attributes are in a list
|
||||||
|
# <portname>=<portattrvalue>
|
||||||
|
status = 0
|
||||||
|
currstr = ''
|
||||||
|
vlist = self.parse_port_list(v)
|
||||||
|
if not vlist:
|
||||||
|
continue
|
||||||
|
for vlistitem in vlist:
|
||||||
|
try:
|
||||||
|
(p, v) = vlistitem.split('=')
|
||||||
|
currv = self.brctlcmd.get_bridgeport_attr(
|
||||||
|
ifaceobj.name, p,
|
||||||
|
brctlcmdattrname)
|
||||||
|
if currv:
|
||||||
|
currstr += ' %s=%s' %(p, currv)
|
||||||
|
else:
|
||||||
|
currstr += ' %s=%s' %(p, 'None')
|
||||||
|
if currv != v:
|
||||||
|
status = 1
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
pass
|
||||||
|
ifaceobjcurr.update_config_with_status(k, currstr, status)
|
||||||
|
elif not rv:
|
||||||
|
ifaceobjcurr.update_config_with_status(k, 'notfound', 1)
|
||||||
|
continue
|
||||||
|
elif v != rv:
|
||||||
|
ifaceobjcurr.update_config_with_status(k, rv, 1)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status(k, rv, 0)
|
||||||
|
|
||||||
|
self._query_check_vidinfo(ifaceobj, ifaceobjcurr)
|
||||||
|
|
||||||
|
self._query_check_mcqv4src(ifaceobj, ifaceobjcurr)
|
||||||
|
|
||||||
|
def _query_running(self, ifaceobjrunning):
|
||||||
|
if not self.brctlcmd.bridge_exists(ifaceobjrunning.name):
|
||||||
|
return
|
||||||
|
ifaceobjrunning.update_config_dict(self._query_running_attrs(
|
||||||
|
ifaceobjrunning))
|
||||||
|
|
||||||
|
_run_ops = {'pre-up' : _up,
|
||||||
|
'post-down' : _down,
|
||||||
|
'query-checkcurr' : _query_check,
|
||||||
|
'query-running' : _query_running}
|
||||||
|
|
||||||
|
def get_ops(self):
|
||||||
|
""" returns list of ops supported by this module """
|
||||||
|
return self._run_ops.keys()
|
||||||
|
|
||||||
|
def _init_command_handlers(self):
|
||||||
|
flags = self.get_flags()
|
||||||
|
if not self.ipcmd:
|
||||||
|
self.ipcmd = iproute2(**flags)
|
||||||
|
if not self.brctlcmd:
|
||||||
|
self.brctlcmd = brctl(**flags)
|
||||||
|
|
||||||
|
def run(self, ifaceobj, operation, query_ifaceobj=None):
|
||||||
|
""" run bridge configuration on the interface object passed as
|
||||||
|
argument. Can create bridge interfaces if they dont exist already
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**ifaceobj** (object): iface object
|
||||||
|
|
||||||
|
**operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
|
||||||
|
'query-running'
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
**query_ifaceobj** (object): query check ifaceobject. This is only
|
||||||
|
valid when op is 'query-checkcurr'. It is an object same as
|
||||||
|
ifaceobj, but contains running attribute values and its config
|
||||||
|
status. The modules can use it to return queried running state
|
||||||
|
of interfaces. status is success if the running state is same
|
||||||
|
as user required state in ifaceobj. error otherwise.
|
||||||
|
"""
|
||||||
|
op_handler = self._run_ops.get(operation)
|
||||||
|
if not op_handler:
|
||||||
|
return
|
||||||
|
if (operation != 'query-running' and
|
||||||
|
not self._is_bridge(ifaceobj)):
|
||||||
|
return
|
||||||
|
self._init_command_handlers()
|
||||||
|
if operation == 'query-checkcurr':
|
||||||
|
op_handler(self, ifaceobj, query_ifaceobj)
|
||||||
|
else:
|
||||||
|
op_handler(self, ifaceobj)
|
942
addons/bridgevlanaware.py
Normal file
942
addons/bridgevlanaware.py
Normal file
@ -0,0 +1,942 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
from sets import Set
|
||||||
|
from ifupdown.iface import *
|
||||||
|
from ifupdownaddons.modulebase import moduleBase
|
||||||
|
from ifupdownaddons.bridgeutils import brctl
|
||||||
|
from ifupdownaddons.iproute2 import iproute2
|
||||||
|
import itertools
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
class bridgevlanaware(moduleBase):
|
||||||
|
""" ifupdown2 addon module to configure linux bridges """
|
||||||
|
|
||||||
|
_modinfo = { 'mhelp' : 'bridge configuration module',
|
||||||
|
'attrs' : {
|
||||||
|
'bridge' :
|
||||||
|
{'help': 'bridge this interface is part of',
|
||||||
|
'example' : ['bridge br0']},
|
||||||
|
'bridge-vlan-aware' :
|
||||||
|
{'help': 'Is this a vlan aware bridge ?',
|
||||||
|
'default' : 'no',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['bridge-vlan-aware yes']},
|
||||||
|
'type' :
|
||||||
|
{'help': 'type of interface this module supports',
|
||||||
|
'example' : ['type bridge']},
|
||||||
|
'bridge-stp' :
|
||||||
|
{'help': 'bridge-stp yes/no',
|
||||||
|
'example' : ['bridge-stp no'],
|
||||||
|
'validvals' : ['yes', 'on', 'off', 'no'],
|
||||||
|
'default' : 'no'},
|
||||||
|
'bridge-bridgeprio' :
|
||||||
|
{'help': 'bridge priority',
|
||||||
|
'example' : ['bridge-bridgeprio 32768'],
|
||||||
|
'default' : '32768'},
|
||||||
|
'bridge-ageing' :
|
||||||
|
{'help': 'bridge ageing',
|
||||||
|
'example' : ['bridge-ageing 300'],
|
||||||
|
'default' : '300'},
|
||||||
|
'bridge-fd' :
|
||||||
|
{ 'help' : 'bridge forward delay',
|
||||||
|
'example' : ['bridge-fd 15'],
|
||||||
|
'default' : '15'},
|
||||||
|
'bridge-gcint' :
|
||||||
|
# XXX: recheck values
|
||||||
|
{ 'help' : 'bridge garbage collection interval in secs',
|
||||||
|
'example' : ['bridge-gcint 4'],
|
||||||
|
'default' : '4'},
|
||||||
|
'bridge-hello' :
|
||||||
|
{ 'help' : 'bridge set hello time',
|
||||||
|
'example' : ['bridge-hello 2'],
|
||||||
|
'default' : '2'},
|
||||||
|
'bridge-maxage' :
|
||||||
|
{ 'help' : 'bridge set maxage',
|
||||||
|
'example' : ['bridge-maxage 20'],
|
||||||
|
'default' : '20'},
|
||||||
|
'bridge-pathcosts' :
|
||||||
|
{ 'help' : 'bridge set port path costs',
|
||||||
|
'example' : ['bridge-pathcosts swp1=100 swp2=100'],
|
||||||
|
'default' : '100'},
|
||||||
|
'bridge-priority' :
|
||||||
|
{ 'help' : 'bridge port priority',
|
||||||
|
'example' : ['bridge-priority 32'],
|
||||||
|
'default' : '32'},
|
||||||
|
'bridge-mclmc' :
|
||||||
|
{ 'help' : 'set multicast last member count',
|
||||||
|
'example' : ['bridge-mclmc 2'],
|
||||||
|
'default' : '2'},
|
||||||
|
'bridge-mcrouter' :
|
||||||
|
{ 'help' : 'set multicast router',
|
||||||
|
'default' : '1',
|
||||||
|
'example' : ['bridge-mcrouter 1']},
|
||||||
|
'bridge-mcsnoop' :
|
||||||
|
{ 'help' : 'set multicast snooping',
|
||||||
|
'default' : '1',
|
||||||
|
'example' : ['bridge-mcsnoop 1']},
|
||||||
|
'bridge-mcsqc' :
|
||||||
|
{ 'help' : 'set multicast startup query count',
|
||||||
|
'default' : '2',
|
||||||
|
'example' : ['bridge-mcsqc 2']},
|
||||||
|
'bridge-mcqifaddr' :
|
||||||
|
{ 'help' : 'set multicast query to use ifaddr',
|
||||||
|
'default' : '0',
|
||||||
|
'example' : ['bridge-mcqifaddr 0']},
|
||||||
|
'bridge-mcquerier' :
|
||||||
|
{ 'help' : 'set multicast querier',
|
||||||
|
'default' : '0',
|
||||||
|
'example' : ['bridge-mcquerier 0']},
|
||||||
|
'bridge-hashel' :
|
||||||
|
{ 'help' : 'set hash elasticity',
|
||||||
|
'default' : '4096',
|
||||||
|
'example' : ['bridge-hashel 4096']},
|
||||||
|
'bridge-hashmax' :
|
||||||
|
{ 'help' : 'set hash max',
|
||||||
|
'default' : '4096',
|
||||||
|
'example' : ['bridge-hashmax 4096']},
|
||||||
|
'bridge-mclmi' :
|
||||||
|
{ 'help' : 'set multicast last member interval (in secs)',
|
||||||
|
'default' : '1',
|
||||||
|
'example' : ['bridge-mclmi 1']},
|
||||||
|
'bridge-mcmi' :
|
||||||
|
{ 'help' : 'set multicast membership interval (in secs)',
|
||||||
|
'default' : '260',
|
||||||
|
'example' : ['bridge-mcmi 260']},
|
||||||
|
'bridge-mcqpi' :
|
||||||
|
{ 'help' : 'set multicast querier interval (in secs)',
|
||||||
|
'default' : '255',
|
||||||
|
'example' : ['bridge-mcqpi 255']},
|
||||||
|
'bridge-mcqi' :
|
||||||
|
{ 'help' : 'set multicast query interval (in secs)',
|
||||||
|
'default' : '125',
|
||||||
|
'example' : ['bridge-mcqi 125']},
|
||||||
|
'bridge-mcqri' :
|
||||||
|
{ 'help' : 'set multicast query response interval (in secs)',
|
||||||
|
'default' : '10',
|
||||||
|
'example' : ['bridge-mcqri 10']},
|
||||||
|
'bridge-mcsqi' :
|
||||||
|
{ 'help' : 'set multicast startup query interval (in secs)',
|
||||||
|
'default' : '31',
|
||||||
|
'example' : ['bridge-mcsqi 31']},
|
||||||
|
'bridge-mcqv4src' :
|
||||||
|
{ 'help' : 'set per VLAN v4 multicast querier source address',
|
||||||
|
'compat' : True,
|
||||||
|
'example' : ['bridge-mcqv4src 172.16.100.1']},
|
||||||
|
'bridge-igmp-querier-src' :
|
||||||
|
{ 'help' : 'set per VLAN v4 multicast querier source address',
|
||||||
|
'example' : ['bridge-igmp-querier-src 172.16.100.1']},
|
||||||
|
'bridge-mcfl' :
|
||||||
|
{ 'help' : 'port multicast fast leave',
|
||||||
|
'default' : '0',
|
||||||
|
'example' : ['bridge-mcfl 0']},
|
||||||
|
'bridge-waitport' :
|
||||||
|
{ 'help' : 'wait for a max of time secs for the' +
|
||||||
|
' specified ports to become available,' +
|
||||||
|
'if no ports are specified then those' +
|
||||||
|
' specified on bridge-ports will be' +
|
||||||
|
' used here. Specifying no ports here ' +
|
||||||
|
'should not be used if we are using ' +
|
||||||
|
'regex or \"all\" on bridge_ports,' +
|
||||||
|
'as it wouldnt work.',
|
||||||
|
'default' : '0',
|
||||||
|
'example' : ['bridge-waitport 4 swp1 swp2']},
|
||||||
|
'bridge-maxwait' :
|
||||||
|
{ 'help' : 'forces to time seconds the maximum time ' +
|
||||||
|
'that the Debian bridge setup scripts will ' +
|
||||||
|
'wait for the bridge ports to get to the ' +
|
||||||
|
'forwarding status, doesn\'t allow factional ' +
|
||||||
|
'part. If it is equal to 0 then no waiting' +
|
||||||
|
' is done',
|
||||||
|
'default' : '0',
|
||||||
|
'example' : ['bridge-maxwait 3']},
|
||||||
|
'bridge-vlan' :
|
||||||
|
{ 'help' : 'bridge vlans',
|
||||||
|
'example' : ['bridge-vlan 4000']},
|
||||||
|
'bridge-vlan-native' :
|
||||||
|
{ 'compat' : True,
|
||||||
|
'help' : 'bridge port vlan',
|
||||||
|
'example' : ['bridge-vlan-native 1']},
|
||||||
|
}}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
moduleBase.__init__(self, *args, **kargs)
|
||||||
|
self.ipcmd = None
|
||||||
|
self.brctlcmd = None
|
||||||
|
|
||||||
|
def _is_bridge_port(self, ifaceobj):
|
||||||
|
if ifaceobj.get_attr_value_first('bridge'):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _is_bridge(self, ifaceobj):
|
||||||
|
if ifaceobj.get_attr_value_first('type') == 'bridge':
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _is_bridge_vlan(self, ifaceobj):
|
||||||
|
if ifaceobj.type & ifaceType.BRIDGE_VLAN:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None):
|
||||||
|
bridge = ifaceobj.get_attr_value_first('bridge')
|
||||||
|
if bridge:
|
||||||
|
match = re.match("^%s-([\d]+)" %bridge, ifaceobj.name)
|
||||||
|
if match:
|
||||||
|
ifaceobj.priv_data = int(match.groups()[0], 10)
|
||||||
|
# XXX: mark this iface as a bridge_vlan iface
|
||||||
|
ifaceobj.type = ifaceType.BRIDGE_VLAN
|
||||||
|
return [bridge]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_dependent_ifacenames_running(self, ifaceobj):
|
||||||
|
self._init_command_handlers()
|
||||||
|
if not self.brctlcmd.bridge_exists(ifaceobj.name):
|
||||||
|
return None
|
||||||
|
return self.brctlcmd.get_bridge_ports(ifaceobj.name)
|
||||||
|
|
||||||
|
def _process_bridge_waitport(self, ifaceobj, portlist):
|
||||||
|
waitport_value = ifaceobj.get_attr_value_first('bridge-waitport')
|
||||||
|
if not waitport_value: return
|
||||||
|
try:
|
||||||
|
waitportvals = re.split(r'[\s\t]\s*', waitport_value, 1)
|
||||||
|
if not waitportvals: return
|
||||||
|
try:
|
||||||
|
waitporttime = int(waitportvals[0])
|
||||||
|
except:
|
||||||
|
self.log_warn('%s: invalid waitport value \'%s\''
|
||||||
|
%(ifaceobj.name, waitporttime))
|
||||||
|
return
|
||||||
|
if waitporttime <= 0: return
|
||||||
|
try:
|
||||||
|
waitportlist = self.parse_port_list(waitportvals[1])
|
||||||
|
except IndexError, e:
|
||||||
|
# ignore error and use all bridge ports
|
||||||
|
waitportlist = portlist
|
||||||
|
pass
|
||||||
|
if not waitportlist: return
|
||||||
|
self.logger.info('%s: waiting for ports %s to exist ...'
|
||||||
|
%(ifaceobj.name, str(waitportlist)))
|
||||||
|
starttime = time.time()
|
||||||
|
while ((time.time() - starttime) < waitporttime):
|
||||||
|
if all([False for p in waitportlist
|
||||||
|
if not self.ipcmd.link_exists(p)]):
|
||||||
|
break;
|
||||||
|
time.sleep(1)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: unable to process waitport: %s'
|
||||||
|
%(ifaceobj.name, str(e)))
|
||||||
|
|
||||||
|
def _add_ports(self, ifaceobj):
|
||||||
|
bridgeports = self._get_bridge_port_list(ifaceobj)
|
||||||
|
runningbridgeports = []
|
||||||
|
|
||||||
|
self._process_bridge_waitport(ifaceobj, bridgeports)
|
||||||
|
# Delete active ports not in the new port list
|
||||||
|
if not self.PERFMODE:
|
||||||
|
runningbridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name)
|
||||||
|
if runningbridgeports:
|
||||||
|
[self.ipcmd.link_set(bport, 'nomaster')
|
||||||
|
for bport in runningbridgeports
|
||||||
|
if not bridgeports or bport not in bridgeports]
|
||||||
|
else:
|
||||||
|
runningbridgeports = []
|
||||||
|
if not bridgeports:
|
||||||
|
return
|
||||||
|
err = 0
|
||||||
|
for bridgeport in Set(bridgeports).difference(Set(runningbridgeports)):
|
||||||
|
try:
|
||||||
|
if not self.DRYRUN and not self.ipcmd.link_exists(bridgeport):
|
||||||
|
self.log_warn('%s: bridge port %s does not exist'
|
||||||
|
%(ifaceobj.name, bridgeport))
|
||||||
|
err += 1
|
||||||
|
continue
|
||||||
|
self.ipcmd.link_set(bridgeport, 'master', ifaceobj.name)
|
||||||
|
self.write_file('/proc/sys/net/ipv6/conf/%s' %bridgeport +
|
||||||
|
'/disable_ipv6', '1')
|
||||||
|
self.ipcmd.addr_flush(bridgeport)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
if err:
|
||||||
|
self.log_error('bridge configuration failed (missing ports)')
|
||||||
|
|
||||||
|
def _process_bridge_maxwait(self, ifaceobj, portlist):
|
||||||
|
maxwait = ifaceobj.get_attr_value_first('bridge-maxwait')
|
||||||
|
if not maxwait: return
|
||||||
|
try:
|
||||||
|
maxwait = int(maxwait)
|
||||||
|
except:
|
||||||
|
self.log_warn('%s: invalid maxwait value \'%s\'' %(ifaceobj.name,
|
||||||
|
maxwait))
|
||||||
|
return
|
||||||
|
if not maxwait: return
|
||||||
|
self.logger.info('%s: waiting for ports to go to fowarding state ..'
|
||||||
|
%ifaceobj.name)
|
||||||
|
try:
|
||||||
|
starttime = time.time()
|
||||||
|
while ((time.time() - starttime) < maxwait):
|
||||||
|
if all([False for p in portlist
|
||||||
|
if self.read_file_oneline(
|
||||||
|
'/sys/class/net/%s/brif/%s/state'
|
||||||
|
%(ifaceobj.name, p)) != '3']):
|
||||||
|
break;
|
||||||
|
time.sleep(1)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: unable to process maxwait: %s'
|
||||||
|
%(ifaceobj.name, str(e)))
|
||||||
|
|
||||||
|
def _ints_to_ranges(self, ints):
|
||||||
|
for a, b in itertools.groupby(enumerate(ints), lambda (x, y): y - x):
|
||||||
|
b = list(b)
|
||||||
|
yield b[0][1], b[-1][1]
|
||||||
|
|
||||||
|
def _ranges_to_ints(self, rangelist):
|
||||||
|
""" returns expanded list of integers given set of string ranges
|
||||||
|
example: ['1', '2-4', '6'] returns [1, 2, 3, 4, 6]
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
for part in rangelist:
|
||||||
|
if '-' in part:
|
||||||
|
a, b = part.split('-')
|
||||||
|
a, b = int(a), int(b)
|
||||||
|
result.extend(range(a, b + 1))
|
||||||
|
else:
|
||||||
|
a = int(part)
|
||||||
|
result.append(a)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _diff_vids(self, vids1, vids2):
|
||||||
|
vids_to_add = None
|
||||||
|
vids_to_del = None
|
||||||
|
|
||||||
|
vids1_ints = self._ranges_to_ints(vids1)
|
||||||
|
vids2_ints = self._ranges_to_ints(vids2)
|
||||||
|
vids1_diff = Set(vids1_ints).difference(vids2_ints)
|
||||||
|
vids2_diff = Set(vids2_ints).difference(vids1_ints)
|
||||||
|
if vids1_diff:
|
||||||
|
vids_to_add = ['%d' %start if start == end else '%d-%d' %(start, end)
|
||||||
|
for start, end in self._ints_to_ranges(vids1_diff)]
|
||||||
|
if vids2_diff:
|
||||||
|
vids_to_del = ['%d' %start if start == end else '%d-%d' %(start, end)
|
||||||
|
for start, end in self._ints_to_ranges(vids2_diff)]
|
||||||
|
return (vids_to_del, vids_to_add)
|
||||||
|
|
||||||
|
def _compare_vids(self, vids1, vids2):
|
||||||
|
""" Returns true if the vids are same else return false """
|
||||||
|
|
||||||
|
vids1_ints = self._ranges_to_ints(vids1)
|
||||||
|
vids2_ints = self._ranges_to_ints(vids2)
|
||||||
|
if Set(vids1_ints).symmetric_difference(vids2_ints):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _set_bridge_mcqv4src(self, ifaceobj):
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src')
|
||||||
|
if attrval:
|
||||||
|
running_mcqv4src = {}
|
||||||
|
if not self.PERFMODE:
|
||||||
|
running_mcqv4src = self.brctlcmd.get_mcqv4src(ifaceobj.name)
|
||||||
|
mcqs = {}
|
||||||
|
srclist = attrval.split()
|
||||||
|
for s in srclist:
|
||||||
|
k, v = s.split('=')
|
||||||
|
mcqs[k] = v
|
||||||
|
|
||||||
|
k_to_del = Set(running_mcqv4src.keys()).difference(mcqs.keys())
|
||||||
|
for v in k_to_del:
|
||||||
|
self.brctlcmd.del_mcqv4src(ifaceobj.name, int(v, 10))
|
||||||
|
for v in mcqs.keys():
|
||||||
|
self.brctlcmd.set_mcqv4src(ifaceobj.name, int(v, 10), mcqs[v])
|
||||||
|
|
||||||
|
def _set_bridge_vidinfo(self, ifaceobj, isbridge=True):
|
||||||
|
# Handle bridge vlan attrs
|
||||||
|
running_vidinfo = {}
|
||||||
|
if not self.PERFMODE:
|
||||||
|
running_vidinfo = self.ipcmd.bridge_port_vids_get_all()
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bridge-vlan')
|
||||||
|
if attrval:
|
||||||
|
vids = re.split(r'[\s\t]\s*', attrval)
|
||||||
|
#vids = attrval.split(',')
|
||||||
|
try:
|
||||||
|
if running_vidinfo.get(ifaceobj.name):
|
||||||
|
(vids_to_del, vids_to_add) = \
|
||||||
|
self._diff_vids(vids,
|
||||||
|
running_vidinfo.get(ifaceobj.name, {}).get('vlan'))
|
||||||
|
if vids_to_del:
|
||||||
|
self.ipcmd.bridge_vids_del(ifaceobj.name,
|
||||||
|
vids_to_del, isbridge)
|
||||||
|
if vids_to_add:
|
||||||
|
self.ipcmd.bridge_vids_add(ifaceobj.name,
|
||||||
|
vids_to_add, isbridge)
|
||||||
|
else:
|
||||||
|
self.ipcmd.bridge_vids_add(ifaceobj.name, vids,
|
||||||
|
isbridge)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: failed to set vid `%s` (%s)'
|
||||||
|
%(ifaceobj.name, str(vids), str(e)))
|
||||||
|
else:
|
||||||
|
running_vids = running_vidinfo.get(ifaceobj.name, {}).get('vlan')
|
||||||
|
if running_vids:
|
||||||
|
self.ipcmd.bridge_vids_del(ifaceobj.name, running_vids)
|
||||||
|
|
||||||
|
# Install pvids
|
||||||
|
pvid = ifaceobj.get_attr_value_first('bridge-vlan-native')
|
||||||
|
if pvid:
|
||||||
|
try:
|
||||||
|
running_pvid = running_vidinfo.get(ifaceobj.name,
|
||||||
|
{}).get('pvid')
|
||||||
|
if running_pvid:
|
||||||
|
if running_pvid != pvid:
|
||||||
|
self.ipcmd.bridge_port_pvid_del(ifaceobj.name,
|
||||||
|
running_pvid)
|
||||||
|
else:
|
||||||
|
self.ipcmd.bridge_port_pvid_add(ifaceobj.name, pvid)
|
||||||
|
else:
|
||||||
|
self.ipcmd.bridge_port_pvid_add(ifaceobj.name, pvid)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: failed to set pvid `%s` (%s)'
|
||||||
|
%(ifaceobj.name, pvid, str(e)))
|
||||||
|
|
||||||
|
def _apply_bridge_settings(self, ifaceobj):
|
||||||
|
try:
|
||||||
|
stp = ifaceobj.get_attr_value_first('bridge-stp')
|
||||||
|
if stp:
|
||||||
|
self.brctlcmd.set_stp(ifaceobj.name, stp)
|
||||||
|
# Use the brctlcmd bulk set method: first build a dictionary
|
||||||
|
# and then call set
|
||||||
|
bridgeattrs = { k:v for k,v in
|
||||||
|
{'ageing' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-ageing'),
|
||||||
|
'bridgeprio' :
|
||||||
|
ifaceobj.get_attr_value_first(
|
||||||
|
'bridge-bridgeprio'),
|
||||||
|
'fd' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-fd'),
|
||||||
|
'gcint' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-gcint'),
|
||||||
|
'hello' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-hello'),
|
||||||
|
'maxage' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-maxage'),
|
||||||
|
'mclmc' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mclmc'),
|
||||||
|
'mcrouter' :
|
||||||
|
ifaceobj.get_attr_value_first(
|
||||||
|
'bridge-mcrouter'),
|
||||||
|
'mcsnoop' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mcsnoop'),
|
||||||
|
'mcsqc' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mcsqc'),
|
||||||
|
'mcqifaddr' :
|
||||||
|
ifaceobj.get_attr_value_first(
|
||||||
|
'bridge-mcqifaddr'),
|
||||||
|
'mcquerier' :
|
||||||
|
ifaceobj.get_attr_value_first(
|
||||||
|
'bridge-mcquerier'),
|
||||||
|
'hashel' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-hashel'),
|
||||||
|
'hashmax' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-hashmax'),
|
||||||
|
'mclmi' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mclmi'),
|
||||||
|
'mcmi' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mcmi'),
|
||||||
|
'mcqpi' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mcqpi'),
|
||||||
|
'mcqi' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mcqi'),
|
||||||
|
'mcqri' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mcqri'),
|
||||||
|
'mcsqi' :
|
||||||
|
ifaceobj.get_attr_value_first('bridge-mcsqi')
|
||||||
|
}.items()
|
||||||
|
if v }
|
||||||
|
if bridgeattrs:
|
||||||
|
self.brctlcmd.set_bridge_attrs(ifaceobj.name, bridgeattrs)
|
||||||
|
self._set_bridge_vidinfo(ifaceobj)
|
||||||
|
|
||||||
|
self._set_bridge_mcqv4src(ifaceobj)
|
||||||
|
|
||||||
|
#self._process_bridge_maxwait(ifaceobj,
|
||||||
|
# self._get_bridge_port_list(ifaceobj))
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
|
||||||
|
def _apply_bridge_port_settings(self, ifaceobj, bridge):
|
||||||
|
try:
|
||||||
|
# Use the brctlcmd bulk set method: first build a dictionary
|
||||||
|
# and then call set
|
||||||
|
portattrs = {}
|
||||||
|
for attrname, dstattrname in {'bridge-pathcost' : 'pathcost',
|
||||||
|
'bridge-prio' : 'portprio',
|
||||||
|
'bridge-mcrouter' : 'portmcrouter',
|
||||||
|
'bridge-mcfl' : 'portmcfl'}.items():
|
||||||
|
attrval = ifaceobj.get_attr_value_first(attrname)
|
||||||
|
if not attrval:
|
||||||
|
continue
|
||||||
|
portattrs[ifaceobj.name] = attrval
|
||||||
|
self.brctlcmd.set_bridgeport_attrs(bridge, ifaceobj.name, portattrs)
|
||||||
|
self._set_bridge_vidinfo(ifaceobj, isbridge=False)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
|
||||||
|
def _apply_bridge_vlan_settings(self, ifaceobj, bridge):
|
||||||
|
mcq = ifaceobj.get_attrs_value_first(['bridge-mcqv4src',
|
||||||
|
'bridge-igmp-querier-src'])
|
||||||
|
if mcq:
|
||||||
|
running_mcq = None
|
||||||
|
if not self.PERFMODE:
|
||||||
|
running_mcq = self.brctlcmd.get_mcqv4src(bridge,
|
||||||
|
vlan=ifaceobj.priv_data)
|
||||||
|
if running_mcq != mcq:
|
||||||
|
self.brctlcmd.set_mcqv4src(bridge, ifaceobj.priv_data, mcq)
|
||||||
|
|
||||||
|
self.ipcmd.bridge_vids_add(bridge, [ifaceobj.priv_data], True)
|
||||||
|
|
||||||
|
def _delete_bridge_vlan_settings(self, ifaceobj, bridge):
|
||||||
|
# delete vlan from bridge
|
||||||
|
self.ipcmd.bridge_vids_del(bridge, [ifaceobj.priv_data], True)
|
||||||
|
|
||||||
|
mcq = ifaceobj.get_attr_value_first('bridge-mcqv4src')
|
||||||
|
if mcq:
|
||||||
|
self.brctlcmd.del_mcqv4src(bridge, ifaceobj.name)
|
||||||
|
|
||||||
|
def _up_bridge(self, ifaceobj):
|
||||||
|
try:
|
||||||
|
if not self.PERFMODE:
|
||||||
|
if not self.ipcmd.link_exists(ifaceobj.name):
|
||||||
|
self.ipcmd.link_create(ifaceobj.name, 'bridge')
|
||||||
|
else:
|
||||||
|
self.ipcmd.link_create(ifaceobj.name, 'bridge')
|
||||||
|
self._apply_bridge_settings(ifaceobj)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
|
||||||
|
def _up_bridge_port(self, ifaceobj, bridge):
|
||||||
|
try:
|
||||||
|
self.ipcmd.link_set(ifaceobj.name, 'master', bridge)
|
||||||
|
self._apply_bridge_port_settings(ifaceobj, bridge)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
|
||||||
|
def _up_bridge_vlan(self, ifaceobj, bridge):
|
||||||
|
purge_existing = False if self.PERFMODE else True
|
||||||
|
try:
|
||||||
|
address = ifaceobj.get_attr_value('address')
|
||||||
|
if address:
|
||||||
|
# Create a vlan device,
|
||||||
|
ifacename = '%s.%s' %(bridge, ifaceobj.priv_data)
|
||||||
|
if not self.ipcmd.link_exists(ifacename):
|
||||||
|
self.ipcmd.link_create_vlan(ifacename, bridge,
|
||||||
|
ifaceobj.priv_data)
|
||||||
|
purge_existing = False
|
||||||
|
hwaddress = ifaceobj.get_attr_value_first('hwaddress')
|
||||||
|
if hwaddress:
|
||||||
|
self.ipcmd.link_set_hwaddress(ifacename, hwaddress)
|
||||||
|
self.ipcmd.addr_add_multiple(ifacename, address, purge_existing)
|
||||||
|
self._apply_bridge_vlan_settings(ifaceobj, bridge)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
|
||||||
|
def _up(self, ifaceobj):
|
||||||
|
bridge = ifaceobj.get_attr_value_first('bridge')
|
||||||
|
if ifaceobj.type == ifaceType.BRIDGE_VLAN:
|
||||||
|
self._up_bridge_vlan(ifaceobj, bridge)
|
||||||
|
elif bridge:
|
||||||
|
self._up_bridge_port(ifaceobj, bridge)
|
||||||
|
elif self._is_bridge(ifaceobj):
|
||||||
|
self._up_bridge(ifaceobj)
|
||||||
|
else:
|
||||||
|
# Was this interface part of the bridge at some point and now
|
||||||
|
# got removed ?. If we attached it to the bridge last time
|
||||||
|
# we should release it
|
||||||
|
if os.path.exists('/sys/class/net/%s/brport' %ifaceobj.name):
|
||||||
|
bridgelink = os.readlink('/sys/class/net/%s/brport/bridge'
|
||||||
|
%ifaceobj.name)
|
||||||
|
if bridgelink:
|
||||||
|
bridge = os.path.basename(bridgelink)
|
||||||
|
if (not ifaceobj.upperifaces or
|
||||||
|
bridge not in ifaceobj.upperifaces):
|
||||||
|
# set nomaster
|
||||||
|
self.ipcmd.link_set(ifaceobj.name, 'nomaster')
|
||||||
|
|
||||||
|
def _down_bridge(self, ifaceobj):
|
||||||
|
try:
|
||||||
|
self.brctlcmd.delete_bridge(ifaceobj.name)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
|
||||||
|
def _down_bridge_port(self, ifaceobj):
|
||||||
|
self.ipcmd.link_set(ifaceobj.name, 'nomaster')
|
||||||
|
|
||||||
|
def _down_bridge_vlan(self, ifaceobj, bridge):
|
||||||
|
try:
|
||||||
|
address = ifaceobj.get_attr_value('address')
|
||||||
|
if address:
|
||||||
|
# Create a vlan device,
|
||||||
|
ifacename = '%s.%s' %(bridge, ifaceobj.priv_data)
|
||||||
|
self.ipcmd.link_delete(ifacename)
|
||||||
|
self._delete_bridge_vlan_settings(ifaceobj, bridge)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
|
||||||
|
def _down(self, ifaceobj):
|
||||||
|
bridge = ifaceobj.get_attr_value_first('bridge')
|
||||||
|
if ifaceobj.type == ifaceType.BRIDGE_VLAN:
|
||||||
|
self._down_bridge_vlan(ifaceobj, bridge)
|
||||||
|
elif bridge:
|
||||||
|
self._down_bridge_port(ifaceobj)
|
||||||
|
elif self._is_bridge(ifaceobj):
|
||||||
|
self._down_bridge(ifaceobj)
|
||||||
|
|
||||||
|
def _query_running_vidinfo(self, ifaceobjrunning, ports):
|
||||||
|
running_attrs = {}
|
||||||
|
running_vidinfo = self.ipcmd.bridge_port_vids_get_all()
|
||||||
|
|
||||||
|
if ports:
|
||||||
|
running_bridge_port_vids = ''
|
||||||
|
for p in ports:
|
||||||
|
try:
|
||||||
|
running_vids = running_vidinfo.get(p, {}).get('vlan')
|
||||||
|
if running_vids:
|
||||||
|
running_bridge_port_vids += ' %s=%s' %(p,
|
||||||
|
','.join(running_vids))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
running_attrs['bridge-port-vids'] = running_bridge_port_vids
|
||||||
|
|
||||||
|
running_bridge_port_pvids = ''
|
||||||
|
for p in ports:
|
||||||
|
try:
|
||||||
|
running_pvids = running_vidinfo.get(p, {}).get('pvid')
|
||||||
|
if running_pvids:
|
||||||
|
running_bridge_port_pvids += ' %s=%s' %(p,
|
||||||
|
running_pvids)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
running_attrs['bridge-port-pvids'] = running_bridge_port_pvids
|
||||||
|
|
||||||
|
running_bridge_vids = running_vidinfo.get(ifaceobjrunning.name, {}).get('vlan')
|
||||||
|
if running_bridge_vids:
|
||||||
|
running_attrs['bridge-vids'] = ','.join(running_bridge_vids)
|
||||||
|
return running_attrs
|
||||||
|
|
||||||
|
def _query_running_mcqv4src(self, ifaceobjrunning):
|
||||||
|
running_mcqv4src = self.brctlcmd.get_mcqv4src(ifaceobjrunning.name)
|
||||||
|
mcqs = ['%s=%s' %(v, i) for v, i in running_mcqv4src.items()]
|
||||||
|
mcqs.sort()
|
||||||
|
mcq = ' '.join(mcqs)
|
||||||
|
return mcq
|
||||||
|
|
||||||
|
def _query_running_attrs(self, ifaceobjrunning):
|
||||||
|
bridgeattrdict = {}
|
||||||
|
userspace_stp = 0
|
||||||
|
ports = None
|
||||||
|
skip_kernel_stp_attrs = 0
|
||||||
|
|
||||||
|
if self.sysctl_get('net.bridge.bridge-stp-user-space') == '1':
|
||||||
|
userspace_stp = 1
|
||||||
|
|
||||||
|
tmpbridgeattrdict = self.brctlcmd.get_bridge_attrs(ifaceobjrunning.name)
|
||||||
|
if not tmpbridgeattrdict:
|
||||||
|
self.logger.warn('%s: unable to get bridge attrs'
|
||||||
|
%ifaceobjrunning.name)
|
||||||
|
return bridgeattrdict
|
||||||
|
|
||||||
|
# Fill bridge_ports and bridge stp attributes first
|
||||||
|
ports = tmpbridgeattrdict.get('ports')
|
||||||
|
if ports:
|
||||||
|
bridgeattrdict['bridge-ports'] = [' '.join(ports.keys())]
|
||||||
|
stp = tmpbridgeattrdict.get('stp', 'no')
|
||||||
|
if stp != self.get_mod_subattr('bridge-stp', 'default'):
|
||||||
|
bridgeattrdict['bridge-stp'] = [stp]
|
||||||
|
|
||||||
|
if stp == 'yes' and userspace_stp:
|
||||||
|
skip_kernel_stp_attrs = 1
|
||||||
|
|
||||||
|
# pick all other attributes
|
||||||
|
for k,v in tmpbridgeattrdict.items():
|
||||||
|
if not v:
|
||||||
|
continue
|
||||||
|
if k == 'ports' or k == 'stp':
|
||||||
|
continue
|
||||||
|
|
||||||
|
if skip_kernel_stp_attrs and k[:2] != 'mc':
|
||||||
|
# only include igmp attributes if kernel stp is off
|
||||||
|
continue
|
||||||
|
attrname = 'bridge-' + k
|
||||||
|
if v != self.get_mod_subattr(attrname, 'default'):
|
||||||
|
bridgeattrdict[attrname] = [v]
|
||||||
|
|
||||||
|
bridgevidinfo = self._query_running_vidinfo(ifaceobjrunning, ports)
|
||||||
|
if bridgevidinfo:
|
||||||
|
bridgeattrdict.update({k : [v] for k, v in bridgevidinfo.items()
|
||||||
|
if v})
|
||||||
|
|
||||||
|
mcq = self._query_running_mcqv4src(ifaceobjrunning)
|
||||||
|
if mcq:
|
||||||
|
bridgeattrdict['bridge-mcqv4src'] = [mcq]
|
||||||
|
|
||||||
|
if skip_kernel_stp_attrs:
|
||||||
|
return bridgeattrdict
|
||||||
|
|
||||||
|
if ports:
|
||||||
|
portconfig = {'bridge-pathcosts' : '',
|
||||||
|
'bridge-portprios' : ''}
|
||||||
|
for p, v in ports.items():
|
||||||
|
v = self.brctlcmd.get_pathcost(ifaceobjrunning.name, p)
|
||||||
|
if v and v != self.get_mod_subattr('bridge-pathcosts',
|
||||||
|
'default'):
|
||||||
|
portconfig['bridge-pathcosts'] += ' %s=%s' %(p, v)
|
||||||
|
|
||||||
|
v = self.brctlcmd.get_portprio(ifaceobjrunning.name, p)
|
||||||
|
if v and v != self.get_mod_subattr('bridge-portprios',
|
||||||
|
'default'):
|
||||||
|
portconfig['bridge-portprios'] += ' %s=%s' %(p, v)
|
||||||
|
|
||||||
|
bridgeattrdict.update({k : [v] for k, v in portconfig.items()
|
||||||
|
if v})
|
||||||
|
|
||||||
|
return bridgeattrdict
|
||||||
|
|
||||||
|
def _query_check_mcqv4src(self, ifaceobj, ifaceobjcurr):
|
||||||
|
running_mcqs = self._query_running_mcqv4src(ifaceobj)
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src')
|
||||||
|
if attrval:
|
||||||
|
mcqs = attrval.split()
|
||||||
|
mcqs.sort()
|
||||||
|
mcqsout = ' '.join(mcqs)
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-mcqv4src',
|
||||||
|
running_mcqs, 1 if running_mcqs != mcqsout else 0)
|
||||||
|
|
||||||
|
def _query_check_vidinfo(self, ifaceobj, ifaceobjcurr):
|
||||||
|
|
||||||
|
err = 0
|
||||||
|
running_vidinfo = self.ipcmd.bridge_port_vids_get_all()
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bridge-port-vids')
|
||||||
|
if attrval:
|
||||||
|
running_bridge_port_vids = ''
|
||||||
|
portlist = self.parse_port_list(attrval)
|
||||||
|
if not portlist:
|
||||||
|
self.log_warn('%s: could not parse \'%s %s\''
|
||||||
|
%(ifaceobj.name, attrname, attrval))
|
||||||
|
return
|
||||||
|
err = 0
|
||||||
|
for p in portlist:
|
||||||
|
try:
|
||||||
|
(port, val) = p.split('=')
|
||||||
|
vids = val.split(',')
|
||||||
|
running_vids = running_vidinfo.get(port, {}).get('vlan')
|
||||||
|
if running_vids:
|
||||||
|
if not self._compare_vids(vids, running_vids):
|
||||||
|
err += 1
|
||||||
|
running_bridge_port_vids += ' %s=%s' %(port,
|
||||||
|
','.join(running_vids))
|
||||||
|
else:
|
||||||
|
running_bridge_port_vids += ' %s' %p
|
||||||
|
else:
|
||||||
|
err += 1
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: failure checking vid %s (%s)'
|
||||||
|
%(ifaceobj.name, p, str(e)))
|
||||||
|
if err:
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-port-vids',
|
||||||
|
running_bridge_port_vids, 1)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-port-vids',
|
||||||
|
attrval, 0)
|
||||||
|
|
||||||
|
# Install pvids
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bridge-port-pvids')
|
||||||
|
if attrval:
|
||||||
|
portlist = self.parse_port_list(attrval)
|
||||||
|
if not portlist:
|
||||||
|
self.log_warn('%s: could not parse \'%s %s\''
|
||||||
|
%(ifaceobj.name, attrname, attrval))
|
||||||
|
return
|
||||||
|
running_bridge_port_pvids = ''
|
||||||
|
err = 0
|
||||||
|
for p in portlist:
|
||||||
|
try:
|
||||||
|
(port, pvid) = p.split('=')
|
||||||
|
running_pvid = running_vidinfo.get(port, {}).get('pvid')
|
||||||
|
if running_pvid and running_pvid == pvid:
|
||||||
|
running_bridge_port_pvids += ' %s' %p
|
||||||
|
else:
|
||||||
|
err += 1
|
||||||
|
running_bridge_port_pvids += ' %s=%s' %(port,
|
||||||
|
running_pvid)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: failure checking pvid %s (%s)'
|
||||||
|
%(ifaceobj.name, pvid, str(e)))
|
||||||
|
if err:
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-port-pvids',
|
||||||
|
running_bridge_port_pvids, 1)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-port-pvids',
|
||||||
|
running_bridge_port_pvids, 0)
|
||||||
|
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bridge-vids')
|
||||||
|
if attrval:
|
||||||
|
vids = re.split(r'[\s\t]\s*', attrval)
|
||||||
|
running_vids = running_vidinfo.get(ifaceobj.name, {}).get('vlan')
|
||||||
|
if running_vids:
|
||||||
|
if self._compare_vids(vids, running_vids):
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-vids',
|
||||||
|
attrval, 0)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-vids',
|
||||||
|
','.join(running_vids), 1)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-vids', attrval,
|
||||||
|
1)
|
||||||
|
|
||||||
|
def _query_check(self, ifaceobj, ifaceobjcurr):
|
||||||
|
if not self.brctlcmd.bridge_exists(ifaceobj.name):
|
||||||
|
self.logger.info('%s: bridge: does not exist' %(ifaceobj.name))
|
||||||
|
ifaceobjcurr.status = ifaceStatus.NOTFOUND
|
||||||
|
return
|
||||||
|
ifaceattrs = self.dict_key_subset(ifaceobj.config,
|
||||||
|
self.get_mod_attrs())
|
||||||
|
if not ifaceattrs:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
runningattrs = self.brctlcmd.get_bridge_attrs(ifaceobj.name)
|
||||||
|
if not runningattrs:
|
||||||
|
self.logger.debug('%s: bridge: unable to get bridge attrs'
|
||||||
|
%ifaceobj.name)
|
||||||
|
runningattrs = {}
|
||||||
|
except Exception, e:
|
||||||
|
self.logger.warn(str(e))
|
||||||
|
runningattrs = {}
|
||||||
|
filterattrs = ['bridge-vids', 'bridge-port-vids',
|
||||||
|
'bridge-port-pvids']
|
||||||
|
for k in Set(ifaceattrs).difference(filterattrs):
|
||||||
|
# get the corresponding ifaceobj attr
|
||||||
|
v = ifaceobj.get_attr_value_first(k)
|
||||||
|
if not v:
|
||||||
|
continue
|
||||||
|
rv = runningattrs.get(k[7:])
|
||||||
|
if k == 'bridge-mcqv4src':
|
||||||
|
continue
|
||||||
|
if k == 'bridge-stp':
|
||||||
|
# special case stp compare because it may
|
||||||
|
# contain more than one valid values
|
||||||
|
stp_on_vals = ['on', 'yes']
|
||||||
|
stp_off_vals = ['off']
|
||||||
|
if ((v in stp_on_vals and rv in stp_on_vals) or
|
||||||
|
(v in stp_off_vals and rv in stp_off_vals)):
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-stp',
|
||||||
|
v, 0)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-stp',
|
||||||
|
v, 1)
|
||||||
|
elif k == 'bridge-ports':
|
||||||
|
# special case ports because it can contain regex or glob
|
||||||
|
running_port_list = rv.keys() if rv else []
|
||||||
|
bridge_port_list = self._get_bridge_port_list(ifaceobj)
|
||||||
|
if not running_port_list and not bridge_port_list:
|
||||||
|
continue
|
||||||
|
portliststatus = 1
|
||||||
|
if running_port_list and bridge_port_list:
|
||||||
|
difference = set(running_port_list
|
||||||
|
).symmetric_difference(bridge_port_list)
|
||||||
|
if not difference:
|
||||||
|
portliststatus = 0
|
||||||
|
ifaceobjcurr.update_config_with_status('bridge-ports',
|
||||||
|
' '.join(running_port_list)
|
||||||
|
if running_port_list else '', portliststatus)
|
||||||
|
elif (k == 'bridge-pathcosts' or
|
||||||
|
k == 'bridge-portprios' or k == 'bridge-portmcrouter'
|
||||||
|
or k == 'bridge-portmcfl'):
|
||||||
|
brctlcmdattrname = k[11:].rstrip('s')
|
||||||
|
# for port attributes, the attributes are in a list
|
||||||
|
# <portname>=<portattrvalue>
|
||||||
|
status = 0
|
||||||
|
currstr = ''
|
||||||
|
vlist = self.parse_port_list(v)
|
||||||
|
if not vlist:
|
||||||
|
continue
|
||||||
|
for vlistitem in vlist:
|
||||||
|
try:
|
||||||
|
(p, v) = vlistitem.split('=')
|
||||||
|
currv = self.brctlcmd.get_bridgeport_attr(
|
||||||
|
ifaceobj.name, p,
|
||||||
|
brctlcmdattrname)
|
||||||
|
if currv:
|
||||||
|
currstr += ' %s=%s' %(p, currv)
|
||||||
|
else:
|
||||||
|
currstr += ' %s=%s' %(p, 'None')
|
||||||
|
if currv != v:
|
||||||
|
status = 1
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
pass
|
||||||
|
ifaceobjcurr.update_config_with_status(k, currstr, status)
|
||||||
|
elif not rv:
|
||||||
|
ifaceobjcurr.update_config_with_status(k, 'notfound', 1)
|
||||||
|
continue
|
||||||
|
elif v != rv:
|
||||||
|
ifaceobjcurr.update_config_with_status(k, rv, 1)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status(k, rv, 0)
|
||||||
|
|
||||||
|
self._query_check_vidinfo(ifaceobj, ifaceobjcurr)
|
||||||
|
|
||||||
|
self._query_check_mcqv4src(ifaceobj, ifaceobjcurr)
|
||||||
|
|
||||||
|
def _query_running(self, ifaceobjrunning):
|
||||||
|
if not self.brctlcmd.bridge_exists(ifaceobjrunning.name):
|
||||||
|
return
|
||||||
|
ifaceobjrunning.update_config_dict(self._query_running_attrs(
|
||||||
|
ifaceobjrunning))
|
||||||
|
|
||||||
|
_run_ops = {'pre-up' : _up,
|
||||||
|
'post-down' : _down,
|
||||||
|
'query-checkcurr' : _query_check,
|
||||||
|
'query-running' : _query_running}
|
||||||
|
|
||||||
|
def get_ops(self):
|
||||||
|
""" returns list of ops supported by this module """
|
||||||
|
return self._run_ops.keys()
|
||||||
|
|
||||||
|
def _init_command_handlers(self):
|
||||||
|
flags = self.get_flags()
|
||||||
|
if not self.ipcmd:
|
||||||
|
self.ipcmd = iproute2(**flags)
|
||||||
|
if not self.brctlcmd:
|
||||||
|
self.brctlcmd = brctl(**flags)
|
||||||
|
|
||||||
|
def run(self, ifaceobj, operation, query_ifaceobj=None):
|
||||||
|
""" run bridge configuration on the interface object passed as
|
||||||
|
argument. Can create bridge interfaces if they dont exist already
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**ifaceobj** (object): iface object
|
||||||
|
|
||||||
|
**operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
|
||||||
|
'query-running'
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
**query_ifaceobj** (object): query check ifaceobject. This is only
|
||||||
|
valid when op is 'query-checkcurr'. It is an object same as
|
||||||
|
ifaceobj, but contains running attribute values and its config
|
||||||
|
status. The modules can use it to return queried running state
|
||||||
|
of interfaces. status is success if the running state is same
|
||||||
|
as user required state in ifaceobj. error otherwise.
|
||||||
|
"""
|
||||||
|
op_handler = self._run_ops.get(operation)
|
||||||
|
if not op_handler:
|
||||||
|
return
|
||||||
|
self._init_command_handlers()
|
||||||
|
if operation == 'query-checkcurr':
|
||||||
|
op_handler(self, ifaceobj, query_ifaceobj)
|
||||||
|
else:
|
||||||
|
op_handler(self, ifaceobj)
|
129
addons/dhcp.py
Normal file
129
addons/dhcp.py
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ipaddr import IPNetwork
|
||||||
|
from sets import Set
|
||||||
|
from ifupdown.iface import *
|
||||||
|
from ifupdownaddons.modulebase import moduleBase
|
||||||
|
from ifupdownaddons.dhclient import dhclient
|
||||||
|
from ifupdownaddons.iproute2 import iproute2
|
||||||
|
except ImportError, e:
|
||||||
|
raise ImportError (str(e) + "- required module not found")
|
||||||
|
|
||||||
|
class dhcp(moduleBase):
|
||||||
|
""" ifupdown2 addon module to configure dhcp on interface """
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
moduleBase.__init__(self, *args, **kargs)
|
||||||
|
self.dhclientcmd = dhclient(**kargs)
|
||||||
|
self.ipcmd = None
|
||||||
|
|
||||||
|
def _up(self, ifaceobj):
|
||||||
|
try:
|
||||||
|
if ifaceobj.addr_family == 'inet':
|
||||||
|
# First release any existing dhclient processes
|
||||||
|
try:
|
||||||
|
if not self.PERFMODE:
|
||||||
|
self.dhclientcmd.stop(ifaceobj.name)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.dhclientcmd.start(ifaceobj.name)
|
||||||
|
elif ifaceobj.addr_family == 'inet6':
|
||||||
|
accept_ra = ifaceobj.get_attr_value_first('accept_ra')
|
||||||
|
if accept_ra:
|
||||||
|
# XXX: Validate value
|
||||||
|
self.sysctl_set('net.ipv6.conf.%s' %ifaceobj.name +
|
||||||
|
'.accept_ra', accept_ra)
|
||||||
|
autoconf = ifaceobj.get_attr_value_first('autoconf')
|
||||||
|
if autoconf:
|
||||||
|
# XXX: Validate value
|
||||||
|
self.sysctl_set('net.ipv6.conf.%s' %ifaceobj.name +
|
||||||
|
'.autoconf', autoconf)
|
||||||
|
try:
|
||||||
|
self.dhclientcmd.stop6(ifaceobj.name)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.dhclientcmd.start6(ifaceobj.name)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
|
||||||
|
def _down(self, ifaceobj):
|
||||||
|
self.dhclientcmd.release(ifaceobj.name)
|
||||||
|
self.ipcmd.link_down(ifaceobj.name)
|
||||||
|
|
||||||
|
def _query_check(self, ifaceobj, ifaceobjcurr):
|
||||||
|
if self.dhclientcmd.is_running(ifaceobjcurr.name):
|
||||||
|
ifaceobjcurr.addr_family = 'inet'
|
||||||
|
if ifaceobj.addr_family != 'inet':
|
||||||
|
ifaceobjcurr.status = ifaceStatus.ERROR
|
||||||
|
ifaceobjcurr.addr_method = 'dhcp'
|
||||||
|
elif self.dhclientcmd.is_running6(ifaceobjcurr.name):
|
||||||
|
ifaceobjcurr.addr_family = 'inet6'
|
||||||
|
if ifaceobj.addr_family != 'inet6':
|
||||||
|
ifaceobjcurr.status = ifaceStatus.ERROR
|
||||||
|
ifaceobjcurr.addr_method = 'dhcp'
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.addr_family = None
|
||||||
|
ifaceobjcurr.status = ifaceStatus.ERROR
|
||||||
|
|
||||||
|
def _query_running(self, ifaceobjrunning):
|
||||||
|
if not self.ipcmd.link_exists(ifaceobjrunning.name):
|
||||||
|
self.logger.debug('iface %s not found' %ifaceobjrunning.name)
|
||||||
|
ifaceobjrunning.status = ifaceStatus.NOTFOUND
|
||||||
|
return
|
||||||
|
if self.dhclientcmd.is_running(ifaceobjrunning.name):
|
||||||
|
ifaceobjrunning.addr_family = 'inet'
|
||||||
|
ifaceobjrunning.addr_method = 'dhcp'
|
||||||
|
elif self.dhclientcmd.is_running6(ifaceobjrunning.name):
|
||||||
|
ifaceobjrunning.addr_family = 'inet6'
|
||||||
|
ifaceobjrunning.addr_method = 'dhcp6'
|
||||||
|
|
||||||
|
_run_ops = {'up' : _up,
|
||||||
|
'down' : _down,
|
||||||
|
'query-checkcurr' : _query_check,
|
||||||
|
'query-running' : _query_running }
|
||||||
|
|
||||||
|
def get_ops(self):
|
||||||
|
""" returns list of ops supported by this module """
|
||||||
|
return self._run_ops.keys()
|
||||||
|
|
||||||
|
def _init_command_handlers(self):
|
||||||
|
if not self.ipcmd:
|
||||||
|
self.ipcmd = iproute2(**self.get_flags())
|
||||||
|
|
||||||
|
def run(self, ifaceobj, operation, query_ifaceobj=None):
|
||||||
|
""" run dhcp configuration on the interface object passed as argument
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**ifaceobj** (object): iface object
|
||||||
|
|
||||||
|
**operation** (str): any of 'up', 'down', 'query-checkcurr',
|
||||||
|
'query-running'
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
**query_ifaceobj** (object): query check ifaceobject. This is only
|
||||||
|
valid when op is 'query-checkcurr'. It is an object same as
|
||||||
|
ifaceobj, but contains running attribute values and its config
|
||||||
|
status. The modules can use it to return queried running state
|
||||||
|
of interfaces. status is success if the running state is same
|
||||||
|
as user required state in ifaceobj. error otherwise.
|
||||||
|
"""
|
||||||
|
op_handler = self._run_ops.get(operation)
|
||||||
|
if not op_handler:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
if (operation != 'query-running' and
|
||||||
|
(ifaceobj.addr_method != 'dhcp' and
|
||||||
|
ifaceobj.addr_method != 'dhcp6')):
|
||||||
|
return
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
self._init_command_handlers()
|
||||||
|
if operation == 'query-checkcurr':
|
||||||
|
op_handler(self, ifaceobj, query_ifaceobj)
|
||||||
|
else:
|
||||||
|
op_handler(self, ifaceobj)
|
102
addons/ethtool.py
Normal file
102
addons/ethtool.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ipaddr import IPNetwork
|
||||||
|
from sets import Set
|
||||||
|
from ifupdown.iface import *
|
||||||
|
from ifupdownaddons.modulebase import moduleBase
|
||||||
|
from ifupdownaddons.iproute2 import iproute2
|
||||||
|
except ImportError, e:
|
||||||
|
raise ImportError (str(e) + "- required module not found")
|
||||||
|
|
||||||
|
class ethtool(moduleBase):
|
||||||
|
""" ifupdown2 addon module to configure ethtool attributes """
|
||||||
|
|
||||||
|
_modinfo = {'mhelp' : 'ethtool configuration module for interfaces',
|
||||||
|
'attrs': {
|
||||||
|
'link-speed' :
|
||||||
|
{'help' : 'set link speed',
|
||||||
|
'example' : ['link-speed 1000']},
|
||||||
|
'link-duplex' :
|
||||||
|
{'help': 'set link duplex',
|
||||||
|
'example' : ['link-duplex full'],
|
||||||
|
'validvals' : ['half', 'full'],
|
||||||
|
'default' : 'half'},
|
||||||
|
'link-autoneg' :
|
||||||
|
{'help': 'set autonegotiation',
|
||||||
|
'example' : ['link-autoneg on'],
|
||||||
|
'validvals' : ['on', 'off'],
|
||||||
|
'default' : 'off'}}}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
moduleBase.__init__(self, *args, **kargs)
|
||||||
|
self.ipcmd = None
|
||||||
|
|
||||||
|
def _post_up(self, ifaceobj):
|
||||||
|
if not self.ipcmd.link_exists(ifaceobj.name):
|
||||||
|
return
|
||||||
|
cmd = ''
|
||||||
|
attrval = ifaceobj.get_attr_value_first('link-speed')
|
||||||
|
if attrval:
|
||||||
|
cmd += ' speed %s' %attrval
|
||||||
|
attrval = ifaceobj.get_attr_value_first('link-duplex')
|
||||||
|
if attrval:
|
||||||
|
cmd += ' duplex %s' %attrval
|
||||||
|
attrval = ifaceobj.get_attr_value_first('link-autoneg')
|
||||||
|
if attrval:
|
||||||
|
cmd += ' autoneg %s' %attrval
|
||||||
|
if cmd:
|
||||||
|
try:
|
||||||
|
cmd = 'ethtool -s %s %s' %(ifaceobj.name, cmd)
|
||||||
|
self.exec_command(cmd)
|
||||||
|
except Exception, e:
|
||||||
|
ifaceobj.status = ifaceStatus.ERROR
|
||||||
|
self.log_warn('%s: %s' %(ifaceobj.name, str(e)))
|
||||||
|
|
||||||
|
def _query_check(self, ifaceobj, ifaceobjcurr):
|
||||||
|
return
|
||||||
|
|
||||||
|
def _query_running(self, ifaceobjrunning):
|
||||||
|
return
|
||||||
|
|
||||||
|
_run_ops = {'post-up' : _post_up,
|
||||||
|
'query-checkcurr' : _query_check,
|
||||||
|
'query-running' : _query_running }
|
||||||
|
|
||||||
|
def get_ops(self):
|
||||||
|
""" returns list of ops supported by this module """
|
||||||
|
return self._run_ops.keys()
|
||||||
|
|
||||||
|
def _init_command_handlers(self):
|
||||||
|
if not self.ipcmd:
|
||||||
|
self.ipcmd = iproute2(**self.get_flags())
|
||||||
|
|
||||||
|
def run(self, ifaceobj, operation, query_ifaceobj=None):
|
||||||
|
""" run ethtool configuration on the interface object passed as
|
||||||
|
argument
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**ifaceobj** (object): iface object
|
||||||
|
|
||||||
|
**operation** (str): any of 'post-up', 'query-checkcurr',
|
||||||
|
'query-running'
|
||||||
|
Kwargs:
|
||||||
|
**query_ifaceobj** (object): query check ifaceobject. This is only
|
||||||
|
valid when op is 'query-checkcurr'. It is an object same as
|
||||||
|
ifaceobj, but contains running attribute values and its config
|
||||||
|
status. The modules can use it to return queried running state
|
||||||
|
of interfaces. status is success if the running state is same
|
||||||
|
as user required state in ifaceobj. error otherwise.
|
||||||
|
"""
|
||||||
|
op_handler = self._run_ops.get(operation)
|
||||||
|
if not op_handler:
|
||||||
|
return
|
||||||
|
self._init_command_handlers()
|
||||||
|
if operation == 'query-checkcurr':
|
||||||
|
op_handler(self, ifaceobj, query_ifaceobj)
|
||||||
|
else:
|
||||||
|
op_handler(self, ifaceobj)
|
380
addons/ifenslave.py
Normal file
380
addons/ifenslave.py
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
from sets import Set
|
||||||
|
from ifupdown.iface import *
|
||||||
|
import ifupdownaddons
|
||||||
|
from ifupdownaddons.modulebase import moduleBase
|
||||||
|
from ifupdownaddons.ifenslaveutil import ifenslaveutil
|
||||||
|
from ifupdownaddons.iproute2 import iproute2
|
||||||
|
|
||||||
|
class ifenslave(moduleBase):
|
||||||
|
""" ifupdown2 addon module to configure bond interfaces """
|
||||||
|
_modinfo = { 'mhelp' : 'bond configuration module',
|
||||||
|
'attrs' : {
|
||||||
|
'bond-use-carrier':
|
||||||
|
{'help' : 'bond use carrier',
|
||||||
|
'validvals' : ['0', '1'],
|
||||||
|
'default' : '1',
|
||||||
|
'example': ['bond-use-carrier 1']},
|
||||||
|
'bond-num-grat-arp':
|
||||||
|
{'help' : 'bond use carrier',
|
||||||
|
'validrange' : ['0', '255'],
|
||||||
|
'default' : '1',
|
||||||
|
'example' : ['bond-num-grat-arp 1']},
|
||||||
|
'bond-num-unsol-na' :
|
||||||
|
{'help' : 'bond slave devices',
|
||||||
|
'validrange' : ['0', '255'],
|
||||||
|
'default' : '1',
|
||||||
|
'example' : ['bond-num-unsol-na 1']},
|
||||||
|
'bond-xmit-hash-policy' :
|
||||||
|
{'help' : 'bond slave devices',
|
||||||
|
'validvals' : ['layer2', 'layer3+4', 'layer2+3'],
|
||||||
|
'default' : 'layer2',
|
||||||
|
'example' : ['bond-xmit-hash-policy layer2']},
|
||||||
|
'bond-miimon' :
|
||||||
|
{'help' : 'bond miimon',
|
||||||
|
'validrange' : ['0', '255'],
|
||||||
|
'default' : '0',
|
||||||
|
'example' : ['bond-miimon 0']},
|
||||||
|
'bond-mode' :
|
||||||
|
{'help' : 'bond mode',
|
||||||
|
'validvals' : ['balance-rr', 'active-backup',
|
||||||
|
'balance-xor', 'broadcast', '802.3ad',
|
||||||
|
'balance-tlb', 'balance-alb'],
|
||||||
|
'default' : 'balance-rr',
|
||||||
|
'example' : ['bond-mode 802.3ad']},
|
||||||
|
'bond-lacp-rate':
|
||||||
|
{'help' : 'bond use carrier',
|
||||||
|
'validvals' : ['0', '1'],
|
||||||
|
'default' : '0',
|
||||||
|
'example' : ['bond-lacp-rate 0']},
|
||||||
|
'bond-min-links':
|
||||||
|
{'help' : 'bond min links',
|
||||||
|
'default' : '0',
|
||||||
|
'example' : ['bond-min-links 0']},
|
||||||
|
'bond-ad-sys-priority':
|
||||||
|
{'help' : '802.3ad system priority',
|
||||||
|
'default' : '65535',
|
||||||
|
'example' : ['bond-ad-sys-priority 65535']},
|
||||||
|
'bond-ad-sys-mac-addr':
|
||||||
|
{'help' : '802.3ad system mac address',
|
||||||
|
'default' : '00:00:00:00:00:00',
|
||||||
|
'example' : ['bond-ad-sys-mac-addr 00:00:00:00:00:00']},
|
||||||
|
'bond-lacp-fallback-allow':
|
||||||
|
{'help' : 'allow lacp fall back',
|
||||||
|
'validvals' : ['0', '1'],
|
||||||
|
'default' : '0',
|
||||||
|
'example' : ['bond-lacp-fallback-allow 0']},
|
||||||
|
'bond-lacp-fallback-period':
|
||||||
|
{'help' : 'grace period (seconds) for lacp fall back',
|
||||||
|
'validrange' : ['0', '100'],
|
||||||
|
'default' : '90',
|
||||||
|
'example' : ['bond-lacp-fallback-period 100']},
|
||||||
|
'bond-lacp-fallback-priority':
|
||||||
|
{'help' : 'slave priority for lacp fall back',
|
||||||
|
'example' : ['bond-lacp-fallback-priority swp1=1 swp2=1 swp3=2']},
|
||||||
|
'bond-slaves' :
|
||||||
|
{'help' : 'bond slaves',
|
||||||
|
'required' : True,
|
||||||
|
'example' : ['bond-slaves swp1 swp2',
|
||||||
|
'bond-slaves glob swp1-2',
|
||||||
|
'bond-slaves regex (swp[1|2)']}}}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
ifupdownaddons.modulebase.moduleBase.__init__(self, *args, **kargs)
|
||||||
|
self.ipcmd = None
|
||||||
|
self.ifenslavecmd = None
|
||||||
|
|
||||||
|
def _is_bond(self, ifaceobj):
|
||||||
|
if ifaceobj.get_attr_value_first('bond-slaves'):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None):
|
||||||
|
""" Returns list of interfaces dependent on ifaceobj """
|
||||||
|
|
||||||
|
if not self._is_bond(ifaceobj):
|
||||||
|
return None
|
||||||
|
slave_list = self.parse_port_list(ifaceobj.get_attr_value_first(
|
||||||
|
'bond-slaves'), ifacenames_all)
|
||||||
|
|
||||||
|
# Also save a copy for future use
|
||||||
|
ifaceobj.priv_data = list(slave_list)
|
||||||
|
return slave_list
|
||||||
|
|
||||||
|
def get_dependent_ifacenames_running(self, ifaceobj):
|
||||||
|
self._init_command_handlers()
|
||||||
|
return self.ifenslavecmd.get_slaves(ifaceobj.name)
|
||||||
|
|
||||||
|
def _get_slave_list(self, ifaceobj):
|
||||||
|
""" Returns slave list present in ifaceobj config """
|
||||||
|
|
||||||
|
# If priv data already has slave list use that first.
|
||||||
|
if ifaceobj.priv_data:
|
||||||
|
return ifaceobj.priv_data
|
||||||
|
slaves = ifaceobj.get_attr_value_first('bond-slaves')
|
||||||
|
if slaves:
|
||||||
|
return self.parse_port_list(slaves)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def fetch_attr(self, ifaceobj, attrname):
|
||||||
|
attrval = ifaceobj.get_attr_value_first(attrname)
|
||||||
|
if attrval:
|
||||||
|
msg = ('%s: invalid value %s for attr %s.'
|
||||||
|
%(ifaceobj.name, attrval, attrname))
|
||||||
|
optiondict = self.get_mod_attr(attrname)
|
||||||
|
if not optiondict:
|
||||||
|
return None
|
||||||
|
validvals = optiondict.get('validvals')
|
||||||
|
if validvals and attrval not in validvals:
|
||||||
|
raise Exception(msg + ' Valid values are %s' %str(validvals))
|
||||||
|
validrange = optiondict.get('validrange')
|
||||||
|
if validrange:
|
||||||
|
if (int(attrval) < int(validrange[0]) or
|
||||||
|
int(attrval) > int(validrange[1])):
|
||||||
|
raise Exception(msg + ' Valid range is [%s,%s]'
|
||||||
|
%(validrange[0], validrange[1]))
|
||||||
|
return attrval
|
||||||
|
|
||||||
|
def _apply_master_settings(self, ifaceobj):
|
||||||
|
have_attrs_to_set = 0
|
||||||
|
ifenslavecmd_attrmap = OrderedDict([('bond-mode' , 'mode'),
|
||||||
|
('bond-miimon' , 'miimon'),
|
||||||
|
('bond-use-carrier', 'use_carrier'),
|
||||||
|
('bond-lacp-rate' , 'lacp_rate'),
|
||||||
|
('bond-xmit-hash-policy' , 'xmit_hash_policy'),
|
||||||
|
('bond-min-links' , 'min_links'),
|
||||||
|
('bond-num-grat-arp' , 'num_grat_arp'),
|
||||||
|
('bond-num-unsol-na' , 'num_unsol_na'),
|
||||||
|
('bond-ad-sys-mac-addr' , 'ad_sys_mac_addr'),
|
||||||
|
('bond-ad-sys-priority' , 'ad_sys_priority'),
|
||||||
|
('bond-lacp-fallback-allow', 'lacp_fallback_allow'),
|
||||||
|
('bond-lacp-fallback-period', 'lacp_fallback_period')])
|
||||||
|
linkstatus = self.ipcmd.link_get_status(ifaceobj.name)
|
||||||
|
if not linkstatus:
|
||||||
|
# assume link status is 'UP'
|
||||||
|
linkstatus = 'UP'
|
||||||
|
try:
|
||||||
|
# order of attributes set matters for bond, so
|
||||||
|
# construct the list sequentially
|
||||||
|
attrstoset = OrderedDict()
|
||||||
|
for k, dstk in ifenslavecmd_attrmap.items():
|
||||||
|
v = self.fetch_attr(ifaceobj, k)
|
||||||
|
if v:
|
||||||
|
attrstoset[dstk] = v
|
||||||
|
if not attrstoset:
|
||||||
|
return
|
||||||
|
have_attrs_to_set = 1
|
||||||
|
self.ifenslavecmd.set_attrs(ifaceobj.name, attrstoset,
|
||||||
|
self.ipcmd.link_down if linkstatus == 'UP' else None)
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if have_attrs_to_set and linkstatus == 'UP':
|
||||||
|
self.ipcmd.link_up(ifaceobj.name)
|
||||||
|
|
||||||
|
def _add_slaves(self, ifaceobj):
|
||||||
|
runningslaves = []
|
||||||
|
|
||||||
|
slaves = self._get_slave_list(ifaceobj)
|
||||||
|
if not slaves:
|
||||||
|
self.logger.debug('%s: no slaves found' %ifaceobj.name)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.PERFMODE:
|
||||||
|
runningslaves = self.ifenslavecmd.get_slaves(ifaceobj.name);
|
||||||
|
if runningslaves:
|
||||||
|
# Delete active slaves not in the new slave list
|
||||||
|
[ self.ifenslavecmd.remove_slave(ifaceobj.name, s)
|
||||||
|
for s in runningslaves if s not in slaves ]
|
||||||
|
|
||||||
|
for slave in Set(slaves).difference(Set(runningslaves)):
|
||||||
|
if (not self.PERFMODE and
|
||||||
|
not self.ipcmd.link_exists(slave)):
|
||||||
|
self.log_warn('%s: skipping slave %s, does not exist'
|
||||||
|
%(ifaceobj.name, slave))
|
||||||
|
continue
|
||||||
|
self.ifenslavecmd.enslave_slave(ifaceobj.name, slave,
|
||||||
|
prehook=self.ipcmd.link_down,
|
||||||
|
posthook=self.ipcmd.link_up)
|
||||||
|
|
||||||
|
def _apply_slaves_lacp_fallback_prio(self, ifaceobj):
|
||||||
|
slaves = self.ifenslavecmd.get_slaves(ifaceobj.name)
|
||||||
|
attrval = ifaceobj.get_attr_value_first('bond-lacp-fallback-priority')
|
||||||
|
if attrval:
|
||||||
|
portlist = self.parse_port_list(attrval)
|
||||||
|
if not portlist:
|
||||||
|
self.log_warn('%s: could not parse \'%s %s\''
|
||||||
|
%(ifaceobj.name, attrname, attrval))
|
||||||
|
return
|
||||||
|
|
||||||
|
for p in portlist:
|
||||||
|
try:
|
||||||
|
(port, val) = p.split('=')
|
||||||
|
if port not in slaves:
|
||||||
|
self.log_warn('%s: skipping slave %s, does not exist'
|
||||||
|
%(ifaceobj.name, port))
|
||||||
|
continue
|
||||||
|
slaves.remove(port)
|
||||||
|
self.ifenslavecmd.set_lacp_fallback_priority(ifaceobj.name, port, val)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: failed to set lacp_fallback_priority %s (%s)'
|
||||||
|
%(ifaceobj.name, port, str(e)))
|
||||||
|
|
||||||
|
for p in slaves:
|
||||||
|
try:
|
||||||
|
self.ifenslavecmd.set_lacp_fallback_priority(ifaceobj.name, p, '0')
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: failed to clear lacp_fallback_priority %s (%s)'
|
||||||
|
%(ifaceobj.name, p, str(e)))
|
||||||
|
|
||||||
|
|
||||||
|
def _up(self, ifaceobj):
|
||||||
|
try:
|
||||||
|
if not self.ipcmd.link_exists(ifaceobj.name):
|
||||||
|
self.ifenslavecmd.create_bond(ifaceobj.name)
|
||||||
|
self._apply_master_settings(ifaceobj)
|
||||||
|
self._add_slaves(ifaceobj)
|
||||||
|
self._apply_slaves_lacp_fallback_prio(ifaceobj)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
|
||||||
|
def _down(self, ifaceobj):
|
||||||
|
try:
|
||||||
|
self.ifenslavecmd.delete_bond(ifaceobj.name)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
|
||||||
|
def _query_check(self, ifaceobj, ifaceobjcurr):
|
||||||
|
slaves = None
|
||||||
|
|
||||||
|
if not self.ifenslavecmd.bond_exists(ifaceobj.name):
|
||||||
|
self.logger.debug('bond iface %s' %ifaceobj.name +
|
||||||
|
' does not exist')
|
||||||
|
ifaceobjcurr.status = ifaceStatus.NOTFOUND
|
||||||
|
return
|
||||||
|
|
||||||
|
ifaceattrs = self.dict_key_subset(ifaceobj.config,
|
||||||
|
self.get_mod_attrs())
|
||||||
|
if not ifaceattrs: return
|
||||||
|
runningattrs = self._query_running_attrs(ifaceobj.name)
|
||||||
|
for k in ifaceattrs:
|
||||||
|
v = ifaceobj.get_attr_value_first(k)
|
||||||
|
if not v:
|
||||||
|
continue
|
||||||
|
if k == 'bond-slaves':
|
||||||
|
slaves = v.split()
|
||||||
|
continue
|
||||||
|
rv = runningattrs.get(k)
|
||||||
|
if not rv:
|
||||||
|
ifaceobjcurr.update_config_with_status(k, 'None', 1)
|
||||||
|
else:
|
||||||
|
if k == 'bond-lacp-fallback-priority':
|
||||||
|
prios = v.split()
|
||||||
|
prios.sort()
|
||||||
|
prio_str = ' '.join(prios)
|
||||||
|
ifaceobjcurr.update_config_with_status(k, rv,
|
||||||
|
1 if prio_str != rv else 0)
|
||||||
|
continue
|
||||||
|
|
||||||
|
ifaceobjcurr.update_config_with_status(k, rv,
|
||||||
|
1 if v != rv else 0)
|
||||||
|
runningslaves = runningattrs.get('bond-slaves')
|
||||||
|
if not slaves and not runningslaves:
|
||||||
|
return
|
||||||
|
retslave = 1
|
||||||
|
if slaves and runningslaves:
|
||||||
|
if slaves and runningslaves:
|
||||||
|
difference = set(slaves).symmetric_difference(runningslaves)
|
||||||
|
if not difference:
|
||||||
|
retslave = 0
|
||||||
|
ifaceobjcurr.update_config_with_status('bond-slaves',
|
||||||
|
' '.join(runningslaves)
|
||||||
|
if runningslaves else 'None', retslave)
|
||||||
|
|
||||||
|
def _query_running_attrs(self, bondname):
|
||||||
|
bondattrs = {'bond-mode' :
|
||||||
|
self.ifenslavecmd.get_mode(bondname),
|
||||||
|
'bond-miimon' :
|
||||||
|
self.ifenslavecmd.get_miimon(bondname),
|
||||||
|
'bond-use-carrier' :
|
||||||
|
self.ifenslavecmd.get_use_carrier(bondname),
|
||||||
|
'bond-lacp-rate' :
|
||||||
|
self.ifenslavecmd.get_lacp_rate(bondname),
|
||||||
|
'bond-min-links' :
|
||||||
|
self.ifenslavecmd.get_min_links(bondname),
|
||||||
|
'bond-ad-sys-mac-addr' :
|
||||||
|
self.ifenslavecmd.get_ad_sys_mac_addr(bondname),
|
||||||
|
'bond-ad-sys-priority' :
|
||||||
|
self.ifenslavecmd.get_ad_sys_priority(bondname),
|
||||||
|
'bond-xmit-hash-policy' :
|
||||||
|
self.ifenslavecmd.get_xmit_hash_policy(bondname),
|
||||||
|
'bond-lacp-fallback-allow' :
|
||||||
|
self.ifenslavecmd.get_lacp_fallback_allow(bondname),
|
||||||
|
'bond-lacp-fallback-period' :
|
||||||
|
self.ifenslavecmd.get_lacp_fallback_period(bondname),
|
||||||
|
'bond-lacp-fallback-priority' :
|
||||||
|
self.ifenslavecmd.get_lacp_fallback_priority(bondname)}
|
||||||
|
slaves = self.ifenslavecmd.get_slaves(bondname)
|
||||||
|
if slaves:
|
||||||
|
bondattrs['bond-slaves'] = slaves
|
||||||
|
return bondattrs
|
||||||
|
|
||||||
|
def _query_running(self, ifaceobjrunning):
|
||||||
|
if not self.ifenslavecmd.bond_exists(ifaceobjrunning.name):
|
||||||
|
return
|
||||||
|
bondattrs = self._query_running_attrs(ifaceobjrunning.name)
|
||||||
|
if bondattrs.get('bond-slaves'):
|
||||||
|
bondattrs['bond-slaves'] = ' '.join(bondattrs.get('bond-slaves'))
|
||||||
|
[ifaceobjrunning.update_config(k, v)
|
||||||
|
for k, v in bondattrs.items()
|
||||||
|
if v and v != self.get_mod_subattr(k, 'default')]
|
||||||
|
|
||||||
|
_run_ops = {'pre-up' : _up,
|
||||||
|
'post-down' : _down,
|
||||||
|
'query-running' : _query_running,
|
||||||
|
'query-checkcurr' : _query_check}
|
||||||
|
|
||||||
|
def get_ops(self):
|
||||||
|
""" returns list of ops supported by this module """
|
||||||
|
return self._run_ops.keys()
|
||||||
|
|
||||||
|
def _init_command_handlers(self):
|
||||||
|
flags = self.get_flags()
|
||||||
|
if not self.ipcmd:
|
||||||
|
self.ipcmd = iproute2(**flags)
|
||||||
|
if not self.ifenslavecmd:
|
||||||
|
self.ifenslavecmd = ifenslaveutil(**flags)
|
||||||
|
|
||||||
|
def run(self, ifaceobj, operation, query_ifaceobj=None):
|
||||||
|
""" run bond configuration on the interface object passed as argument
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**ifaceobj** (object): iface object
|
||||||
|
|
||||||
|
**operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
|
||||||
|
'query-running'
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
**query_ifaceobj** (object): query check ifaceobject. This is only
|
||||||
|
valid when op is 'query-checkcurr'. It is an object same as
|
||||||
|
ifaceobj, but contains running attribute values and its config
|
||||||
|
status. The modules can use it to return queried running state
|
||||||
|
of interfaces. status is success if the running state is same
|
||||||
|
as user required state in ifaceobj. error otherwise.
|
||||||
|
"""
|
||||||
|
op_handler = self._run_ops.get(operation)
|
||||||
|
if not op_handler:
|
||||||
|
return
|
||||||
|
if operation != 'query-running' and not self._is_bond(ifaceobj):
|
||||||
|
return
|
||||||
|
self._init_command_handlers()
|
||||||
|
if operation == 'query-checkcurr':
|
||||||
|
op_handler(self, ifaceobj, query_ifaceobj)
|
||||||
|
else:
|
||||||
|
op_handler(self, ifaceobj)
|
64
addons/loopback.py
Normal file
64
addons/loopback.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
# This should be pretty simple and might not really even need to exist.
|
||||||
|
# The key is that we need to call link_create with a type of "dummy"
|
||||||
|
# since that will translate to 'ip link add loopbackX type dummy'
|
||||||
|
# The config file should probably just indicate that the type is
|
||||||
|
# loopback or dummy.
|
||||||
|
|
||||||
|
from ifupdown.iface import *
|
||||||
|
from ifupdownaddons.modulebase import moduleBase
|
||||||
|
from ifupdownaddons.iproute2 import iproute2
|
||||||
|
import logging
|
||||||
|
|
||||||
|
class loopback(moduleBase):
|
||||||
|
_modinfo = {'mhelp' : 'configure extra loopback module based on ' +
|
||||||
|
'dummy device' }
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
moduleBase.__init__(self, *args, **kargs)
|
||||||
|
self.ipcmd = None
|
||||||
|
|
||||||
|
def _is_loopback_by_name(self, ifacename):
|
||||||
|
return 'loop' in ifacename
|
||||||
|
|
||||||
|
def _up(self, ifaceobj):
|
||||||
|
if self._is_loopback_by_name(ifaceobj.name):
|
||||||
|
self.ipcmd.link_create(ifaceobj.name, 'dummy')
|
||||||
|
|
||||||
|
def _down(self, ifaceobj):
|
||||||
|
if not self.PERFMODE and not self.ipcmd.link_exists(ifaceobj.name):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.ipcmd.link_delete(ifaceobj.name)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
|
||||||
|
def _query_check(self, ifaceobj, ifaceobjcurr):
|
||||||
|
if not self.ipcmd.link_exists(ifaceobj.name):
|
||||||
|
ifaceobjcurr.status = ifaceStatus.NOTFOUND
|
||||||
|
return
|
||||||
|
|
||||||
|
_run_ops = {'pre-up' : _up,
|
||||||
|
'post-down' : _down,
|
||||||
|
'query-checkcurr' : _query_check}
|
||||||
|
|
||||||
|
def get_ops(self):
|
||||||
|
return self._run_ops.keys()
|
||||||
|
|
||||||
|
def _init_command_handlers(self):
|
||||||
|
if not self.ipcmd:
|
||||||
|
self.ipcmd = iproute2(**self.get_flags())
|
||||||
|
|
||||||
|
def run(self, ifaceobj, operation, query_ifaceobj=None):
|
||||||
|
op_handler = self._run_ops.get(operation)
|
||||||
|
if not op_handler:
|
||||||
|
return
|
||||||
|
if (operation != 'query-running' and
|
||||||
|
not self._is_loopback_by_name(ifaceobj.name)):
|
||||||
|
return
|
||||||
|
self._init_command_handlers()
|
||||||
|
if operation == 'query-checkcurr':
|
||||||
|
op_handler(self, ifaceobj, query_ifaceobj)
|
||||||
|
else:
|
||||||
|
op_handler(self, ifaceobj)
|
710
addons/mstpctl.py
Normal file
710
addons/mstpctl.py
Normal file
@ -0,0 +1,710 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
from sets import Set
|
||||||
|
from ifupdown.iface import *
|
||||||
|
from ifupdownaddons.modulebase import moduleBase
|
||||||
|
from ifupdownaddons.bridgeutils import brctl
|
||||||
|
from ifupdownaddons.iproute2 import iproute2
|
||||||
|
from ifupdownaddons.mstpctlutil import mstpctlutil
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
class mstpctl(moduleBase):
|
||||||
|
""" ifupdown2 addon module to configure mstp attributes """
|
||||||
|
|
||||||
|
_modinfo = {'mhelp' : 'mstp configuration module for bridges',
|
||||||
|
'attrs' : {
|
||||||
|
'mstpctl-ports' :
|
||||||
|
{'help' : 'mstp ports',
|
||||||
|
'compat' : True},
|
||||||
|
'mstpctl-stp' :
|
||||||
|
{'help': 'bridge stp yes/no',
|
||||||
|
'compat' : True,
|
||||||
|
'default' : 'no'},
|
||||||
|
'mstpctl-treeprio' :
|
||||||
|
{'help': 'tree priority',
|
||||||
|
'default' : '32768',
|
||||||
|
'validrange' : ['0', '65535'],
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-treeprio 32768']},
|
||||||
|
'mstpctl-ageing' :
|
||||||
|
{'help': 'ageing time',
|
||||||
|
'default' : '300',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-ageing 300']},
|
||||||
|
'mstpctl-maxage' :
|
||||||
|
{ 'help' : 'max message age',
|
||||||
|
'default' : '20',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-maxage 20']},
|
||||||
|
'mstpctl-fdelay' :
|
||||||
|
{ 'help' : 'set forwarding delay',
|
||||||
|
'default' : '15',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-fdelay 15']},
|
||||||
|
'mstpctl-maxhops' :
|
||||||
|
{ 'help' : 'bridge max hops',
|
||||||
|
'default' : '15',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-maxhops 15']},
|
||||||
|
'mstpctl-txholdcount' :
|
||||||
|
{ 'help' : 'bridge transmit holdcount',
|
||||||
|
'default' : '6',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-txholdcount 6']},
|
||||||
|
'mstpctl-forcevers' :
|
||||||
|
{ 'help' : 'bridge force stp version',
|
||||||
|
'default' : 'rstp',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-forcevers rstp']},
|
||||||
|
'mstpctl-portpathcost' :
|
||||||
|
{ 'help' : 'bridge port path cost',
|
||||||
|
'default' : '0',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-portpathcost swp1=0 swp2=1']},
|
||||||
|
'mstpctl-portadminage' :
|
||||||
|
{ 'help' : 'bridge port admin age',
|
||||||
|
'default' : 'no',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-portadminage swp1=no swp2=no']},
|
||||||
|
'mstpctl-portp2p' :
|
||||||
|
{ 'help' : 'bridge port p2p detection mode',
|
||||||
|
'default' : 'no',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-portp2p swp1=no swp2=no']},
|
||||||
|
'mstpctl-portrestrrole' :
|
||||||
|
{ 'help' :
|
||||||
|
'enable/disable port ability to take root role of the port',
|
||||||
|
'default' : 'no',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-portrestrrole swp1=no swp2=no']},
|
||||||
|
'mstpctl-portrestrtcn' :
|
||||||
|
{ 'help' :
|
||||||
|
'enable/disable port ability to propagate received topology change notification of the port',
|
||||||
|
'default' : 'no',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-portrestrtcn swp1=no swp2=no']},
|
||||||
|
'mstpctl-bpduguard' :
|
||||||
|
{ 'help' :
|
||||||
|
'enable/disable bpduguard',
|
||||||
|
'default' : 'no',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-bpduguard swp1=no swp2=no']},
|
||||||
|
'mstpctl-treeportprio' :
|
||||||
|
{ 'help' :
|
||||||
|
'port priority for MSTI instance',
|
||||||
|
'default' : '128',
|
||||||
|
'validrange' : ['0', '240'],
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-treeportprio swp1=128 swp2=128']},
|
||||||
|
'mstpctl-hello' :
|
||||||
|
{ 'help' : 'set hello time',
|
||||||
|
'default' : '2',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-hello 2']},
|
||||||
|
'mstpctl-portnetwork' :
|
||||||
|
{ 'help' : 'enable/disable bridge assurance capability for a port',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'default' : 'no',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-portnetwork swp1=no swp2=no']},
|
||||||
|
'mstpctl-portadminedge' :
|
||||||
|
{ 'help' : 'enable/disable initial edge state of the port',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'default' : 'no',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-portadminedge swp1=no swp2=no']},
|
||||||
|
'mstpctl-portautoedge' :
|
||||||
|
{ 'help' : 'enable/disable auto transition to/from edge state of the port',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'default' : 'no',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-portautoedge swp1=yes swp2=yes']},
|
||||||
|
'mstpctl-treeportcost' :
|
||||||
|
{ 'help' : 'port tree cost',
|
||||||
|
'required' : False},
|
||||||
|
'mstpctl-portbpdufilter' :
|
||||||
|
{ 'help' : 'enable/disable bpdu filter on a port',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'default' : 'no',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-portbpdufilter swp1=no swp2=no']},
|
||||||
|
'mstpctl-pathcost' :
|
||||||
|
{ 'help' : 'port path cost',
|
||||||
|
'default' : '0',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-pathcost 1']},
|
||||||
|
'mstpctl-adminage' :
|
||||||
|
{ 'help' : 'bridge port admin age',
|
||||||
|
'default' : 'no',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-adminage no']},
|
||||||
|
'mstpctl-p2p' :
|
||||||
|
{ 'help' : 'bridge port p2p detection mode',
|
||||||
|
'default' : 'no',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-p2p yes']},
|
||||||
|
'mstpctl-restrrole' :
|
||||||
|
{ 'help' :
|
||||||
|
'enable/disable port ability to take root role of the port',
|
||||||
|
'default' : 'no',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-restrrole yes']},
|
||||||
|
'mstpctl-restrtcn' :
|
||||||
|
{ 'help' :
|
||||||
|
'enable/disable port ability to propagate received topology change notification of the port',
|
||||||
|
'default' : 'no',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-restrtcn yes']},
|
||||||
|
'mstpctl-treeprio' :
|
||||||
|
{ 'help' :
|
||||||
|
'port priority for MSTI instance',
|
||||||
|
'default' : '128',
|
||||||
|
'validrange' : ['0', '240'],
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-treeprio 128']},
|
||||||
|
'mstpctl-network' :
|
||||||
|
{ 'help' : 'enable/disable bridge assurance capability for a port',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'default' : 'no',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-network no']},
|
||||||
|
'mstpctl-adminedge' :
|
||||||
|
{ 'help' : 'enable/disable initial edge state of the port',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'default' : 'no',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-adminedge no']},
|
||||||
|
'mstpctl-autoedge' :
|
||||||
|
{ 'help' : 'enable/disable auto transition to/from edge state of the port',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'default' : 'no',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-autoedge yes']},
|
||||||
|
'mstpctl-treecost' :
|
||||||
|
{ 'help' : 'port tree cost',
|
||||||
|
'required' : False},
|
||||||
|
'mstpctl-bpdufilter' :
|
||||||
|
{ 'help' : 'enable/disable bpdu filter on a port',
|
||||||
|
'validvals' : ['yes', 'no'],
|
||||||
|
'default' : 'no',
|
||||||
|
'required' : False,
|
||||||
|
'example' : ['mstpctl-bpdufilter yes']},
|
||||||
|
}}
|
||||||
|
|
||||||
|
_port_attrs_map = {'mstpctl-pathcost' : 'portpathcost',
|
||||||
|
'mstpctl-adminedge' : 'portadminedge',
|
||||||
|
'mstpctl-p2p' : 'portp2p',
|
||||||
|
'mstpctl-restrrole' : 'portrestrrole',
|
||||||
|
'mstpctl-restrtcn' : 'portrestrtcn',
|
||||||
|
'mstpctl-bpduguard' : 'bpduguard',
|
||||||
|
'mstpctl-treeprio' : 'treeportprio',
|
||||||
|
'mstpctl-treecost' : 'treeportcost',
|
||||||
|
'mstpctl-network' : 'portnetwork',
|
||||||
|
'mstpctl-bpdufilter' : 'portbpdufilter'}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
moduleBase.__init__(self, *args, **kargs)
|
||||||
|
self.ipcmd = None
|
||||||
|
self.brctlcmd = None
|
||||||
|
self.mstpctlcmd = None
|
||||||
|
|
||||||
|
def _is_bridge(self, ifaceobj):
|
||||||
|
if (ifaceobj.get_attr_value_first('mstpctl-ports') or
|
||||||
|
ifaceobj.get_attr_value_first('bridge-ports')):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None):
|
||||||
|
if not self._is_bridge(ifaceobj):
|
||||||
|
return None
|
||||||
|
return self.parse_port_list(ifaceobj.get_attr_value_first(
|
||||||
|
'mstpctl-ports'), ifacenames_all)
|
||||||
|
|
||||||
|
def get_dependent_ifacenames_running(self, ifaceobj):
|
||||||
|
self._init_command_handlers()
|
||||||
|
if (self.brctlcmd.bridge_exists(ifaceobj.name) and
|
||||||
|
not self.mstpctlcmd.mstpbridge_exists(ifaceobj.name)):
|
||||||
|
return None
|
||||||
|
return self.brctlcmd.get_bridge_ports(ifaceobj.name)
|
||||||
|
|
||||||
|
def _get_bridge_port_list(self, ifaceobj):
|
||||||
|
|
||||||
|
# port list is also available in the previously
|
||||||
|
# parsed dependent list. Use that if available, instead
|
||||||
|
# of parsing port expr again
|
||||||
|
port_list = ifaceobj.lowerifaces
|
||||||
|
if port_list:
|
||||||
|
return port_list
|
||||||
|
ports = ifaceobj.get_attr_value_first('mstpctl-ports')
|
||||||
|
if ports:
|
||||||
|
return self.parse_port_list(ports)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _add_ports(self, ifaceobj):
|
||||||
|
bridgeports = self._get_bridge_port_list(ifaceobj)
|
||||||
|
|
||||||
|
runningbridgeports = []
|
||||||
|
# Delete active ports not in the new port list
|
||||||
|
if not self.PERFMODE:
|
||||||
|
runningbridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name)
|
||||||
|
if runningbridgeports:
|
||||||
|
[self.ipcmd.link_set(bport, 'nomaster')
|
||||||
|
for bport in runningbridgeports
|
||||||
|
if not bridgeports or bport not in bridgeports]
|
||||||
|
else:
|
||||||
|
runningbridgeports = []
|
||||||
|
if not bridgeports:
|
||||||
|
return
|
||||||
|
err = 0
|
||||||
|
for bridgeport in Set(bridgeports).difference(Set(runningbridgeports)):
|
||||||
|
try:
|
||||||
|
if not self.DRYRUN and not self.ipcmd.link_exists(bridgeport):
|
||||||
|
self.log_warn('%s: bridge port %s does not exist'
|
||||||
|
%(ifaceobj.name, bridgeport))
|
||||||
|
err += 1
|
||||||
|
continue
|
||||||
|
self.ipcmd.link_set(bridgeport, 'master', ifaceobj.name)
|
||||||
|
self.write_file('/proc/sys/net/ipv6/conf/%s' %bridgeport +
|
||||||
|
'/disable_ipv6', '1')
|
||||||
|
self.ipcmd.addr_flush(bridgeport)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
if err:
|
||||||
|
self.log_error('error configuring bridge (missing ports)')
|
||||||
|
|
||||||
|
def _apply_bridge_settings(self, ifaceobj):
|
||||||
|
check = False if self.PERFMODE else True
|
||||||
|
try:
|
||||||
|
bridgeattrs = {k:v for k,v in
|
||||||
|
{'treeprio' :
|
||||||
|
ifaceobj.get_attr_value_first('mstpctl-treeprio'),
|
||||||
|
'ageing' :
|
||||||
|
ifaceobj.get_attr_value_first('mstpctl-ageing'),
|
||||||
|
'maxage' :
|
||||||
|
ifaceobj.get_attr_value_first('mstpctl-maxage'),
|
||||||
|
'fdelay' :
|
||||||
|
ifaceobj.get_attr_value_first('mstpctl-fdelay'),
|
||||||
|
'maxhops' :
|
||||||
|
ifaceobj.get_attr_value_first('mstpctl-maxhops'),
|
||||||
|
'txholdcount' :
|
||||||
|
ifaceobj.get_attr_value_first('mstpctl-txholdcount'),
|
||||||
|
'forcevers' :
|
||||||
|
ifaceobj.get_attr_value_first('mstpctl-forcevers'),
|
||||||
|
'hello' :
|
||||||
|
ifaceobj.get_attr_value_first('mstpctl-hello')
|
||||||
|
}.items() if v}
|
||||||
|
|
||||||
|
if bridgeattrs:
|
||||||
|
# set bridge attributes
|
||||||
|
for k,v in bridgeattrs.items():
|
||||||
|
if k == 'treeprio':
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
if v:
|
||||||
|
self.mstpctlcmd.set_bridge_attr(ifaceobj.name, k,
|
||||||
|
v, check)
|
||||||
|
except Exception, e:
|
||||||
|
self.logger.warn('%s' %str(e))
|
||||||
|
pass
|
||||||
|
if bridgeattrs.get('treeprio'):
|
||||||
|
try:
|
||||||
|
self.mstpctlcmd.set_bridge_treeprio(ifaceobj.name,
|
||||||
|
bridgeattrs['treeprio'], check)
|
||||||
|
except Exception, e:
|
||||||
|
self.logger.warn('%s' %str(e))
|
||||||
|
pass
|
||||||
|
|
||||||
|
# set bridge port attributes
|
||||||
|
for attrname in ['mstpctl-portpathcost', 'mstpctl-portadminedge',
|
||||||
|
'mstpctl-portp2p', 'mstpctl-portrestrrole',
|
||||||
|
'mstpctl-portrestrtcn', 'mstpctl-bpduguard',
|
||||||
|
'mstpctl-treeportprio', 'mstpctl-treeportcost',
|
||||||
|
'mstpctl-portnetwork', 'mstpctl-portbpdufilter']:
|
||||||
|
attrval = ifaceobj.get_attr_value_first(attrname)
|
||||||
|
if not attrval:
|
||||||
|
continue
|
||||||
|
dstattrname = attrname.split('-')[1]
|
||||||
|
portlist = self.parse_port_list(attrval)
|
||||||
|
if not portlist:
|
||||||
|
self.log_warn('%s: error parsing \'%s %s\''
|
||||||
|
%(ifaceobj.name, attrname, attrval))
|
||||||
|
continue
|
||||||
|
for p in portlist:
|
||||||
|
try:
|
||||||
|
(port, val) = p.split('=')
|
||||||
|
self.mstpctlcmd.set_bridgeport_attr(ifaceobj.name,
|
||||||
|
port, dstattrname, val, check)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: error setting %s (%s)'
|
||||||
|
%(ifaceobj.name, attrname, str(e)))
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _apply_bridge_port_settings(self, ifaceobj, bridge):
|
||||||
|
check = False if self.PERFMODE else True
|
||||||
|
try:
|
||||||
|
# set bridge port attributes
|
||||||
|
for attrname, dstattrname in self._port_attrs_map.items():
|
||||||
|
attrval = ifaceobj.get_attr_value_first(attrname)
|
||||||
|
if not attrval:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
self.mstpctlcmd.set_bridgeport_attr(bridge,
|
||||||
|
ifaceobj.name, dstattrname, attrval, check)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn('%s: error setting %s (%s)'
|
||||||
|
%(ifaceobj.name, attrname, str(e)))
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _up(self, ifaceobj):
|
||||||
|
# Check if bridge port
|
||||||
|
bridge = ifaceobj.get_attr_value_first('bridge')
|
||||||
|
if bridge:
|
||||||
|
if self.mstpctlcmd.is_mstpd_running():
|
||||||
|
self._apply_bridge_port_settings(ifaceobj, bridge)
|
||||||
|
return
|
||||||
|
|
||||||
|
stp = None
|
||||||
|
try:
|
||||||
|
porterr = False
|
||||||
|
porterrstr = ''
|
||||||
|
if ifaceobj.get_attr_value_first('mstpctl-ports'):
|
||||||
|
# If bridge ports specified with mstpctl attr, create the
|
||||||
|
# bridge and also add its ports
|
||||||
|
self.ipcmd.batch_start()
|
||||||
|
if not self.PERFMODE:
|
||||||
|
if not self.ipcmd.link_exists(ifaceobj.name):
|
||||||
|
self.ipcmd.link_create(ifaceobj.name, 'bridge')
|
||||||
|
else:
|
||||||
|
self.ipcmd.link_create(ifaceobj.name, 'bridge')
|
||||||
|
try:
|
||||||
|
self._add_ports(ifaceobj)
|
||||||
|
except Exception, e:
|
||||||
|
porterr = True
|
||||||
|
porterrstr = str(e)
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
self.ipcmd.batch_commit()
|
||||||
|
stp = ifaceobj.get_attr_value_first('mstpctl-stp')
|
||||||
|
if stp:
|
||||||
|
self.set_iface_attr(ifaceobj, 'mstpctl-stp',
|
||||||
|
self.brctlcmd.set_stp)
|
||||||
|
else:
|
||||||
|
stp = self.brctlcmd.get_stp(ifaceobj.name)
|
||||||
|
if (self.mstpctlcmd.is_mstpd_running() and
|
||||||
|
(stp == 'yes' or stp == 'on')):
|
||||||
|
self._apply_bridge_settings(ifaceobj)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
if porterr:
|
||||||
|
raise Exception(porterrstr)
|
||||||
|
|
||||||
|
def _down(self, ifaceobj):
|
||||||
|
try:
|
||||||
|
if ifaceobj.get_attr_value_first('mstpctl-ports'):
|
||||||
|
# If bridge ports specified with mstpctl attr, delete the
|
||||||
|
# bridge
|
||||||
|
ports = self.brctlcmd.get_bridge_ports(ifaceobj.name)
|
||||||
|
if ports:
|
||||||
|
for p in ports:
|
||||||
|
proc_file = ('/proc/sys/net/ipv6/conf/%s' %p +
|
||||||
|
'/disable_ipv6')
|
||||||
|
self.write_file(proc_file, '0')
|
||||||
|
self.brctlcmd.delete_bridge(ifaceobj.name)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_error(str(e))
|
||||||
|
|
||||||
|
def _query_running_attrs(self, ifaceobjrunning):
|
||||||
|
bridgeattrdict = {}
|
||||||
|
|
||||||
|
tmpbridgeattrdict = self.mstpctlcmd.get_bridge_attrs(ifaceobjrunning.name)
|
||||||
|
if not tmpbridgeattrdict:
|
||||||
|
return bridgeattrdict
|
||||||
|
|
||||||
|
for k,v in tmpbridgeattrdict.items():
|
||||||
|
if k == 'stp' or not v:
|
||||||
|
continue
|
||||||
|
if k == 'ports':
|
||||||
|
ports = v.keys()
|
||||||
|
continue
|
||||||
|
attrname = 'mstpctl-' + k
|
||||||
|
if v and v != self.get_mod_subattr(attrname, 'default'):
|
||||||
|
bridgeattrdict[attrname] = [v]
|
||||||
|
|
||||||
|
ports = self.brctlcmd.get_bridge_ports(ifaceobjrunning.name)
|
||||||
|
if ports:
|
||||||
|
portconfig = {'mstpctl-portnetwork' : '',
|
||||||
|
'mstpctl-portpathcost' : '',
|
||||||
|
'mstpctl-portadminedge' : '',
|
||||||
|
'mstpctl-portautoedge' : '',
|
||||||
|
'mstpctl-portp2p' : '',
|
||||||
|
'mstpctl-portrestrrole' : '',
|
||||||
|
'mstpctl-portrestrtcn' : '',
|
||||||
|
'mstpctl-bpduguard' : '',
|
||||||
|
'mstpctl-treeportprio' : '',
|
||||||
|
'mstpctl-treeportcost' : ''}
|
||||||
|
|
||||||
|
for p in ports:
|
||||||
|
v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
|
||||||
|
p, 'portnetwork')
|
||||||
|
if v and v != 'no':
|
||||||
|
portconfig['mstpctl-portnetwork'] += ' %s=%s' %(p, v)
|
||||||
|
|
||||||
|
# XXX: Can we really get path cost of a port ???
|
||||||
|
#v = self.mstpctlcmd.get_portpathcost(ifaceobjrunning.name, p)
|
||||||
|
#if v and v != self.get_mod_subattr('mstpctl-portpathcost',
|
||||||
|
# 'default'):
|
||||||
|
# portconfig['mstpctl-portpathcost'] += ' %s=%s' %(p, v)
|
||||||
|
|
||||||
|
v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
|
||||||
|
p, 'portadminedge')
|
||||||
|
if v and v != 'no':
|
||||||
|
portconfig['mstpctl-portadminedge'] += ' %s=%s' %(p, v)
|
||||||
|
|
||||||
|
v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
|
||||||
|
p, 'portp2p')
|
||||||
|
if v and v != 'no':
|
||||||
|
portconfig['mstpctl-portp2p'] += ' %s=%s' %(p, v)
|
||||||
|
|
||||||
|
v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
|
||||||
|
p, 'portrestrrole')
|
||||||
|
if v and v != 'no':
|
||||||
|
portconfig['mstpctl-portrestrrole'] += ' %s=%s' %(p, v)
|
||||||
|
|
||||||
|
v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
|
||||||
|
p, 'portrestrtcn')
|
||||||
|
if v and v != 'no':
|
||||||
|
portconfig['mstpctl-portrestrtcn'] += ' %s=%s' %(p, v)
|
||||||
|
|
||||||
|
v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
|
||||||
|
p, 'bpduguard')
|
||||||
|
if v and v != 'no':
|
||||||
|
portconfig['mstpctl-bpduguard'] += ' %s=%s' %(p, v)
|
||||||
|
|
||||||
|
# XXX: Can we really get path cost of a port ???
|
||||||
|
#v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
|
||||||
|
# p, 'treeprio')
|
||||||
|
#if v and v != self.get_mod_subattr('mstpctl-treeportprio',
|
||||||
|
# 'default'):
|
||||||
|
# portconfig['mstpctl-treeportprio'] += ' %s=%s' %(p, v)
|
||||||
|
|
||||||
|
#v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
|
||||||
|
# p, 'treecost')
|
||||||
|
#if v and v != self.get_mod_subattr('mstpctl-treeportcost',
|
||||||
|
# 'default'):
|
||||||
|
# portconfig['mstpctl-treeportcost'] += ' %s=%s' %(p, v)
|
||||||
|
|
||||||
|
bridgeattrdict.update({k : [v] for k, v in portconfig.items()
|
||||||
|
if v})
|
||||||
|
self.logger.debug(bridgeattrdict)
|
||||||
|
return bridgeattrdict
|
||||||
|
|
||||||
|
def _query_check_bridge(self, ifaceobj, ifaceobjcurr):
|
||||||
|
# list of attributes that are not supported currently
|
||||||
|
blacklistedattrs = ['mstpctl-portpathcost',
|
||||||
|
'mstpctl-treeportprio', 'mstpctl-treeportcost']
|
||||||
|
if not self.brctlcmd.bridge_exists(ifaceobj.name):
|
||||||
|
self.logger.debug('bridge %s does not exist' %ifaceobj.name)
|
||||||
|
ifaceobjcurr.status = ifaceStatus.NOTFOUND
|
||||||
|
return
|
||||||
|
ifaceattrs = self.dict_key_subset(ifaceobj.config,
|
||||||
|
self.get_mod_attrs())
|
||||||
|
if not ifaceattrs:
|
||||||
|
return
|
||||||
|
runningattrs = self.mstpctlcmd.get_bridge_attrs(ifaceobj.name)
|
||||||
|
if not runningattrs:
|
||||||
|
runningattrs = {}
|
||||||
|
for k in ifaceattrs:
|
||||||
|
# for all mstpctl options
|
||||||
|
if k in blacklistedattrs:
|
||||||
|
continue
|
||||||
|
# get the corresponding ifaceobj attr
|
||||||
|
v = ifaceobj.get_attr_value_first(k)
|
||||||
|
if not v:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get the running attribute
|
||||||
|
rv = runningattrs.get(k[8:])
|
||||||
|
if k == 'mstpctl-stp':
|
||||||
|
# special case stp compare because it may
|
||||||
|
# contain more than one valid values
|
||||||
|
stp_on_vals = ['on', 'yes']
|
||||||
|
stp_off_vals = ['off']
|
||||||
|
rv = self.brctlcmd.get_stp(ifaceobj.name)
|
||||||
|
if ((v in stp_on_vals and rv in stp_on_vals) or
|
||||||
|
(v in stp_off_vals and rv in stp_off_vals)):
|
||||||
|
ifaceobjcurr.update_config_with_status('mstpctl-stp', v, 0)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('mstpctl-stp', v, 1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if k == 'mstpctl-ports':
|
||||||
|
# special case ports because it can contain regex or glob
|
||||||
|
# XXX: We get all info from mstputils, which means if
|
||||||
|
# mstpd is down, we will not be returning any bridge bridgeports
|
||||||
|
running_port_list = self.brctlcmd.get_bridge_ports(ifaceobj.name)
|
||||||
|
bridge_port_list = self._get_bridge_port_list(ifaceobj)
|
||||||
|
if not running_port_list and not bridge_port_list:
|
||||||
|
continue
|
||||||
|
portliststatus = 1
|
||||||
|
if running_port_list and bridge_port_list:
|
||||||
|
difference = Set(running_port_list).symmetric_difference(
|
||||||
|
Set(bridge_port_list))
|
||||||
|
if not difference:
|
||||||
|
portliststatus = 0
|
||||||
|
ifaceobjcurr.update_config_with_status('mstpctl-ports',
|
||||||
|
' '.join(running_port_list)
|
||||||
|
if running_port_list else '', portliststatus)
|
||||||
|
elif k[:12] == 'mstpctl-port' or k == 'mstpctl-bpduguard':
|
||||||
|
# Now, look at port attributes
|
||||||
|
# derive the mstpctlcmd attr name
|
||||||
|
#mstpctlcmdattrname = k[12:] if k[:12] == 'mstpctl-port' else k[8:]
|
||||||
|
mstpctlcmdattrname = k[8:]
|
||||||
|
|
||||||
|
# for port attributes, the attributes are in a list
|
||||||
|
# <portname>=<portattrvalue>
|
||||||
|
status = 0
|
||||||
|
currstr = ''
|
||||||
|
vlist = self.parse_port_list(v)
|
||||||
|
if not vlist:
|
||||||
|
continue
|
||||||
|
for vlistitem in vlist:
|
||||||
|
try:
|
||||||
|
(p, v) = vlistitem.split('=')
|
||||||
|
currv = self.mstpctlcmd.get_bridgeport_attr(
|
||||||
|
ifaceobj.name, p, mstpctlcmdattrname)
|
||||||
|
if currv:
|
||||||
|
currstr += ' %s=%s' %(p, currv)
|
||||||
|
else:
|
||||||
|
currstr += ' %s=%s' %(p, 'None')
|
||||||
|
if currv != v:
|
||||||
|
status = 1
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
pass
|
||||||
|
ifaceobjcurr.update_config_with_status(k, currstr, status)
|
||||||
|
elif not rv:
|
||||||
|
ifaceobjcurr.update_config_with_status(k, '', 1)
|
||||||
|
elif v != rv:
|
||||||
|
ifaceobjcurr.update_config_with_status(k, rv, 1)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status(k, rv, 0)
|
||||||
|
|
||||||
|
def _query_check_bridge_port(self, ifaceobj, ifaceobjcurr, bridge):
|
||||||
|
# list of attributes that are not supported currently
|
||||||
|
blacklistedattrs = ['mstpctl-pathcost',
|
||||||
|
'mstpctl-treeprio', 'mstpctl-treecost']
|
||||||
|
if not self.ipcmd.link_exists():
|
||||||
|
self.logger.debug('bridge port %s does not exist' %ifaceobj.name)
|
||||||
|
ifaceobjcurr.status = ifaceStatus.NOTFOUND
|
||||||
|
return
|
||||||
|
ifaceattrs = self.dict_key_subset(ifaceobj.config,
|
||||||
|
self._port_attrs_map.keys())
|
||||||
|
if not ifaceattrs:
|
||||||
|
return
|
||||||
|
runningattrs = self.mstpctlcmd.get_bridge_attrs(ifaceobj.name)
|
||||||
|
if not runningattrs:
|
||||||
|
runningattrs = {}
|
||||||
|
for k in ifaceattrs:
|
||||||
|
# for all mstpctl options
|
||||||
|
if k in blacklistedattrs:
|
||||||
|
continue
|
||||||
|
# get the corresponding ifaceobj attr
|
||||||
|
v = ifaceobj.get_attr_value_first(k)
|
||||||
|
if not v:
|
||||||
|
continue
|
||||||
|
|
||||||
|
currv = self.mstpctlcmd.get_bridgeport_attr(bridge,
|
||||||
|
ifaceobj.name, self._port_attrs_map.get(k))
|
||||||
|
if currv:
|
||||||
|
if currv != v:
|
||||||
|
ifaceobjcurr.update_config_with_status(k, currv, 1)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status(k, currv, 0)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status(k, None, 1)
|
||||||
|
|
||||||
|
def _query_check(self, ifaceobj, ifaceobjcurr):
|
||||||
|
# Check if bridge port
|
||||||
|
bridge = ifaceobj.get_attr_value_first('bridge')
|
||||||
|
if bridge:
|
||||||
|
self._query_check_bridge_port(ifaceobj, ifaceobjcurr, bridge)
|
||||||
|
return
|
||||||
|
self._query_check_bridge(ifaceobj, ifaceobjcurr)
|
||||||
|
|
||||||
|
def _query_running(self, ifaceobjrunning):
|
||||||
|
if not self.brctlcmd.bridge_exists(ifaceobjrunning.name):
|
||||||
|
return
|
||||||
|
if self.brctlcmd.get_stp(ifaceobjrunning.name) == 'no':
|
||||||
|
# This bridge does not run stp, return
|
||||||
|
return
|
||||||
|
# if userspace stp not set, return
|
||||||
|
if self.sysctl_get('net.bridge.bridge-stp-user-space') != '1':
|
||||||
|
return
|
||||||
|
# Check if mstp really knows about this bridge
|
||||||
|
if not self.mstpctlcmd.mstpbridge_exists(ifaceobjrunning.name):
|
||||||
|
return
|
||||||
|
ifaceobjrunning.update_config_dict(self._query_running_attrs(
|
||||||
|
ifaceobjrunning))
|
||||||
|
|
||||||
|
_run_ops = {'pre-up' : _up,
|
||||||
|
'post-down' : _down,
|
||||||
|
'query-checkcurr' : _query_check,
|
||||||
|
'query-running' : _query_running}
|
||||||
|
|
||||||
|
def get_ops(self):
|
||||||
|
""" returns list of ops supported by this module """
|
||||||
|
return self._run_ops.keys()
|
||||||
|
|
||||||
|
def _init_command_handlers(self):
|
||||||
|
flags = self.get_flags()
|
||||||
|
if not self.ipcmd:
|
||||||
|
self.ipcmd = iproute2(**flags)
|
||||||
|
if not self.brctlcmd:
|
||||||
|
self.brctlcmd = brctl(**flags)
|
||||||
|
if not self.mstpctlcmd:
|
||||||
|
self.mstpctlcmd = mstpctlutil(**flags)
|
||||||
|
|
||||||
|
def run(self, ifaceobj, operation, query_ifaceobj=None):
|
||||||
|
""" run mstp configuration on the interface object passed as argument
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**ifaceobj** (object): iface object
|
||||||
|
|
||||||
|
**operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
|
||||||
|
'query-running'
|
||||||
|
Kwargs:
|
||||||
|
**query_ifaceobj** (object): query check ifaceobject. This is only
|
||||||
|
valid when op is 'query-checkcurr'. It is an object same as
|
||||||
|
ifaceobj, but contains running attribute values and its config
|
||||||
|
status. The modules can use it to return queried running state
|
||||||
|
of interfaces. status is success if the running state is same
|
||||||
|
as user required state in ifaceobj. error otherwise.
|
||||||
|
"""
|
||||||
|
op_handler = self._run_ops.get(operation)
|
||||||
|
if not op_handler:
|
||||||
|
return
|
||||||
|
if operation != 'query-running' and not self._is_bridge(ifaceobj):
|
||||||
|
return
|
||||||
|
self._init_command_handlers()
|
||||||
|
if operation == 'query-checkcurr':
|
||||||
|
op_handler(self, ifaceobj, query_ifaceobj)
|
||||||
|
else:
|
||||||
|
op_handler(self, ifaceobj)
|
97
addons/usercmds.py
Normal file
97
addons/usercmds.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import ifupdownaddons
|
||||||
|
|
||||||
|
class usercmds(ifupdownaddons.modulebase.moduleBase):
|
||||||
|
""" ifupdown2 addon module to configure user specified commands """
|
||||||
|
|
||||||
|
_modinfo = {'mhelp' : 'user commands for interfaces',
|
||||||
|
'attrs' : {
|
||||||
|
'pre-up' :
|
||||||
|
{'help' : 'run command before bringing the interface up'},
|
||||||
|
'up' :
|
||||||
|
{'help' : 'run command at interface bring up'},
|
||||||
|
'post-up' :
|
||||||
|
{'help' : 'run command after interface bring up'},
|
||||||
|
'pre-down' :
|
||||||
|
{'help' : 'run command before bringing the interface down'},
|
||||||
|
'down' :
|
||||||
|
{'help' : 'run command at interface down'},
|
||||||
|
'post-down' :
|
||||||
|
{'help' : 'run command after bringing interface down'}}}
|
||||||
|
|
||||||
|
def _exec_user_cmd(self, cmd):
|
||||||
|
""" exec's commands using subprocess Popen
|
||||||
|
|
||||||
|
special wrapper using use closefds=True and shell=True
|
||||||
|
for user commands
|
||||||
|
"""
|
||||||
|
|
||||||
|
cmd_returncode = 0
|
||||||
|
try:
|
||||||
|
self.logger.info('executing %s' %cmd)
|
||||||
|
if self.DRYRUN:
|
||||||
|
return
|
||||||
|
ch = subprocess.Popen(cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
shell=True,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
close_fds=True)
|
||||||
|
cmd_returncode = ch.wait()
|
||||||
|
cmdout = ch.communicate()[0]
|
||||||
|
except Exception, e:
|
||||||
|
raise Exception('failed to execute cmd \'%s\' (%s)'
|
||||||
|
%(cmd, str(e)))
|
||||||
|
if cmd_returncode != 0:
|
||||||
|
raise Exception(cmdout)
|
||||||
|
return cmdout
|
||||||
|
|
||||||
|
def _run_command(self, ifaceobj, op):
|
||||||
|
cmd_list = ifaceobj.get_attr_value(op)
|
||||||
|
if cmd_list:
|
||||||
|
for cmd in cmd_list:
|
||||||
|
self.logger.info('executing cmd \'%s\'' %cmd)
|
||||||
|
try:
|
||||||
|
self._exec_user_cmd(cmd)
|
||||||
|
except Exception, e:
|
||||||
|
if not self.ignore_error(str(e)):
|
||||||
|
self.logger.warn('%s: %s cmd \'%s\' failed (%s)'
|
||||||
|
%(ifaceobj.name, op, cmd, str(e).strip('\n')))
|
||||||
|
pass
|
||||||
|
|
||||||
|
_run_ops = {'pre-up' : _run_command,
|
||||||
|
'pre-down' : _run_command,
|
||||||
|
'up' : _run_command,
|
||||||
|
'post-up' : _run_command,
|
||||||
|
'down' : _run_command,
|
||||||
|
'post-down' : _run_command}
|
||||||
|
|
||||||
|
def get_ops(self):
|
||||||
|
""" returns list of ops supported by this module """
|
||||||
|
return self._run_ops.keys()
|
||||||
|
|
||||||
|
def run(self, ifaceobj, operation, query_ifaceobj=None):
|
||||||
|
""" run user commands
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**ifaceobj** (object): iface object
|
||||||
|
|
||||||
|
**operation** (str): list of ops
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
**query_ifaceobj** (object): query check ifaceobject. This is only
|
||||||
|
valid when op is 'query-checkcurr'. It is an object same as
|
||||||
|
ifaceobj, but contains running attribute values and its config
|
||||||
|
status. The modules can use it to return queried running state
|
||||||
|
of interfaces. status is success if the running state is same
|
||||||
|
as user required state in ifaceobj. error otherwise.
|
||||||
|
"""
|
||||||
|
op_handler = self._run_ops.get(operation)
|
||||||
|
if not op_handler:
|
||||||
|
return
|
||||||
|
op_handler(self, ifaceobj, operation)
|
191
addons/vlan.py
Normal file
191
addons/vlan.py
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
from ifupdown.iface import *
|
||||||
|
from ifupdownaddons.modulebase import moduleBase
|
||||||
|
from ifupdownaddons.iproute2 import iproute2
|
||||||
|
import logging
|
||||||
|
|
||||||
|
class vlan(moduleBase):
|
||||||
|
""" ifupdown2 addon module to configure vlans """
|
||||||
|
|
||||||
|
_modinfo = {'mhelp' : 'vlan module configures vlan interfaces.' +
|
||||||
|
'This module understands vlan interfaces with dot ' +
|
||||||
|
'notations. eg swp1.100. Vlan interfaces with any ' +
|
||||||
|
'other names need to have raw device and vlan id ' +
|
||||||
|
'attributes',
|
||||||
|
'attrs' : {
|
||||||
|
'vlan-raw-device' :
|
||||||
|
{'help' : 'vlan raw device'},
|
||||||
|
'vlan-id' :
|
||||||
|
{'help' : 'vlan id'}}}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
moduleBase.__init__(self, *args, **kargs)
|
||||||
|
self.ipcmd = None
|
||||||
|
|
||||||
|
def _is_vlan_device(self, ifaceobj):
|
||||||
|
vlan_raw_device = ifaceobj.get_attr_value_first('vlan-raw-device')
|
||||||
|
if vlan_raw_device:
|
||||||
|
return True
|
||||||
|
elif '.' in ifaceobj.name:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_vlan_id(self, ifaceobj):
|
||||||
|
""" Derives vlanid from iface name
|
||||||
|
|
||||||
|
Example:
|
||||||
|
Returns 1 for ifname vlan0001 returns 1
|
||||||
|
Returns 1 for ifname vlan1
|
||||||
|
Returns 1 for ifname eth0.1
|
||||||
|
|
||||||
|
Returns -1 if vlan id cannot be determined
|
||||||
|
"""
|
||||||
|
vid_str = ifaceobj.get_attr_value_first('vlan-id')
|
||||||
|
try:
|
||||||
|
if vid_str: return int(vid_str)
|
||||||
|
except:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
if ifaceobj.name.startswith('vlan'):
|
||||||
|
vid_str = ifaceobj.name[4:]
|
||||||
|
elif '.' in ifaceobj.name:
|
||||||
|
vid_str = ifaceobj.name.split('.', 1)[1]
|
||||||
|
else:
|
||||||
|
return -1
|
||||||
|
try:
|
||||||
|
vid = int(vid_str)
|
||||||
|
except:
|
||||||
|
return -1
|
||||||
|
return vid
|
||||||
|
|
||||||
|
def _is_vlan_by_name(self, ifacename):
|
||||||
|
return '.' in ifacename
|
||||||
|
|
||||||
|
def _get_vlan_raw_device_from_ifacename(self, ifacename):
|
||||||
|
""" Returns vlan raw device from ifname
|
||||||
|
Example:
|
||||||
|
Returns eth0 for ifname eth0.100
|
||||||
|
|
||||||
|
Returns None if vlan raw device name cannot
|
||||||
|
be determined
|
||||||
|
"""
|
||||||
|
vlist = ifacename.split('.', 1)
|
||||||
|
if len(vlist) == 2:
|
||||||
|
return vlist[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_vlan_raw_device(self, ifaceobj):
|
||||||
|
vlan_raw_device = ifaceobj.get_attr_value_first('vlan-raw-device')
|
||||||
|
if vlan_raw_device:
|
||||||
|
return vlan_raw_device
|
||||||
|
return self._get_vlan_raw_device_from_ifacename(ifaceobj.name)
|
||||||
|
|
||||||
|
def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None):
|
||||||
|
if not self._is_vlan_device(ifaceobj):
|
||||||
|
return None
|
||||||
|
return [self._get_vlan_raw_device(ifaceobj)]
|
||||||
|
|
||||||
|
def _up(self, ifaceobj):
|
||||||
|
vlanid = self._get_vlan_id(ifaceobj)
|
||||||
|
if vlanid == -1:
|
||||||
|
raise Exception('could not determine vlanid')
|
||||||
|
vlanrawdevice = self._get_vlan_raw_device(ifaceobj)
|
||||||
|
if not vlanrawdevice:
|
||||||
|
raise Exception('could not determine vlan raw device')
|
||||||
|
self.ipcmd.link_create_vlan(ifaceobj.name,
|
||||||
|
vlanrawdevice, vlanid)
|
||||||
|
|
||||||
|
def _down(self, ifaceobj):
|
||||||
|
vlanid = self._get_vlan_id(ifaceobj)
|
||||||
|
if vlanid == -1:
|
||||||
|
raise Exception('could not determine vlanid')
|
||||||
|
vlan_raw_device = self._get_vlan_raw_device(ifaceobj)
|
||||||
|
if not vlan_raw_device:
|
||||||
|
raise Exception('could not determine vlan raw device')
|
||||||
|
if not self.PERFMODE and not self.ipcmd.link_exists(ifaceobj.name):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.ipcmd.link_delete(ifaceobj.name)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
|
||||||
|
def _query_check(self, ifaceobj, ifaceobjcurr):
|
||||||
|
if not self.ipcmd.link_exists(ifaceobj.name):
|
||||||
|
ifaceobjcurr.status = ifaceStatus.NOTFOUND
|
||||||
|
return
|
||||||
|
if not '.' in ifaceobj.name:
|
||||||
|
# if vlan name is not in the dot format, check its running state
|
||||||
|
(vlanrawdev, vlanid) = self.ipcmd.get_vlandev_attrs(ifaceobj.name)
|
||||||
|
if vlanrawdev != ifaceobj.get_attr_value_first('vlan-raw-device'):
|
||||||
|
ifaceobjcurr.update_config_with_status('vlan-raw-device',
|
||||||
|
vlanrawdev, 1)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('vlan-raw-device',
|
||||||
|
vlanrawdev, 0)
|
||||||
|
if vlanid != ifaceobj.get_attr_value_first('vlan-id'):
|
||||||
|
ifaceobjcurr.update_config_with_status('vlan-id', vlanid, 1)
|
||||||
|
else:
|
||||||
|
ifaceobjcurr.update_config_with_status('vlan-id',
|
||||||
|
vlanid, 0)
|
||||||
|
|
||||||
|
def _query_running(self, ifaceobjrunning):
|
||||||
|
if not self.ipcmd.link_exists(ifaceobjrunning.name):
|
||||||
|
if self._is_vlan_by_name(ifaceobjrunning.name):
|
||||||
|
ifaceobjcurr.status = ifaceStatus.NOTFOUND
|
||||||
|
return
|
||||||
|
if not self.ipcmd.get_vlandev_attrs(ifaceobjrunning.name):
|
||||||
|
return
|
||||||
|
# If vlan name is not in the dot format, get the
|
||||||
|
# vlan dev and vlan id
|
||||||
|
if not '.' in ifaceobjrunning.name:
|
||||||
|
(vlanrawdev, vlanid) = self.ipcmd.get_vlandev_attrs(ifaceobjrunning.name)
|
||||||
|
ifaceobjrunning.update_config_dict({(k, v) for k, v in
|
||||||
|
{'vlan-raw-device' : vlanrawdev,
|
||||||
|
'vlan-id' : vlanid}.items()
|
||||||
|
if v})
|
||||||
|
|
||||||
|
_run_ops = {'pre-up' : _up,
|
||||||
|
'post-down' : _down,
|
||||||
|
'query-checkcurr' : _query_check,
|
||||||
|
'query-running' : _query_running}
|
||||||
|
|
||||||
|
def get_ops(self):
|
||||||
|
""" returns list of ops supported by this module """
|
||||||
|
return self._run_ops.keys()
|
||||||
|
|
||||||
|
def _init_command_handlers(self):
|
||||||
|
if not self.ipcmd:
|
||||||
|
self.ipcmd = iproute2(**self.get_flags())
|
||||||
|
|
||||||
|
def run(self, ifaceobj, operation, query_ifaceobj=None):
|
||||||
|
""" run vlan configuration on the interface object passed as argument
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**ifaceobj** (object): iface object
|
||||||
|
|
||||||
|
**operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
|
||||||
|
'query-running'
|
||||||
|
Kwargs:
|
||||||
|
**query_ifaceobj** (object): query check ifaceobject. This is only
|
||||||
|
valid when op is 'query-checkcurr'. It is an object same as
|
||||||
|
ifaceobj, but contains running attribute values and its config
|
||||||
|
status. The modules can use it to return queried running state
|
||||||
|
of interfaces. status is success if the running state is same
|
||||||
|
as user required state in ifaceobj. error otherwise.
|
||||||
|
"""
|
||||||
|
op_handler = self._run_ops.get(operation)
|
||||||
|
if not op_handler:
|
||||||
|
return
|
||||||
|
if (operation != 'query-running' and
|
||||||
|
not self._is_vlan_device(ifaceobj)):
|
||||||
|
return
|
||||||
|
self._init_command_handlers()
|
||||||
|
if operation == 'query-checkcurr':
|
||||||
|
op_handler(self, ifaceobj, query_ifaceobj)
|
||||||
|
else:
|
||||||
|
op_handler(self, ifaceobj)
|
87
addons/vxlan.py
Normal file
87
addons/vxlan.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
from ifupdown.iface import *
|
||||||
|
from ifupdownaddons.modulebase import moduleBase
|
||||||
|
from ifupdownaddons.iproute2 import iproute2
|
||||||
|
import logging
|
||||||
|
|
||||||
|
class vxlan(moduleBase):
|
||||||
|
_modinfo = {'mhelp' : 'vxlan module configures vxlan interfaces.',
|
||||||
|
'attrs' : {
|
||||||
|
'vxlan-id' :
|
||||||
|
{'help' : 'vxlan id',
|
||||||
|
'required' : True,
|
||||||
|
'example': ['vxlan-id 100']},
|
||||||
|
'vxlan-local-tunnelip' :
|
||||||
|
{'help' : 'vxlan local tunnel ip',
|
||||||
|
'example': ['vxlan-local-tunnelip 172.16.20.103']},
|
||||||
|
'vxlan-svcnodeip' :
|
||||||
|
{'help' : 'vxlan id',
|
||||||
|
'example': ['vxlan-svcnodeip 172.16.22.125']},
|
||||||
|
'vxlan-peernodeip' :
|
||||||
|
{'help' : 'vxlan peer node ip',
|
||||||
|
'example': ['vxlan-peernodeip 172.16.22.127']},
|
||||||
|
'vxlan-learning' :
|
||||||
|
{'help' : 'vxlan learning on/off',
|
||||||
|
'example': ['vxlan-learning on']},
|
||||||
|
}}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
moduleBase.__init__(self, *args, **kargs)
|
||||||
|
self.ipcmd = None
|
||||||
|
|
||||||
|
def _is_vxlan_device(self, ifaceobj):
|
||||||
|
if ifaceobj.get_attr_value_first('vxlan-id'):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _up(self, ifaceobj):
|
||||||
|
vxlanid = ifaceobj.get_attr_value_first('vxlan-id')
|
||||||
|
if vxlanid:
|
||||||
|
self.ipcmd.link_create_vxlan(ifaceobj.name, vxlanid,
|
||||||
|
localtunnelip=ifaceobj.get_attr_value_first('vxlan-local-tunnelip'),
|
||||||
|
svcnodeips=ifaceobj.get_attr_value('vxlan-svcnodeip'),
|
||||||
|
peernodeips=ifaceobj.get_attr_value('vxlan-peernodeip'),
|
||||||
|
learning=ifaceobj.get_attr_value_first('vxlan-learning'))
|
||||||
|
|
||||||
|
def _down(self, ifaceobj):
|
||||||
|
try:
|
||||||
|
self.ipcmd.link_delete(ifaceobj.name)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
|
||||||
|
def _query_check(self, ifaceobj, ifaceobjcurr):
|
||||||
|
if not self.ipcmd.link_exists(ifaceobj.name):
|
||||||
|
ifaceobjcurr.status = ifaceStatus.NOTFOUND
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update vxlan object
|
||||||
|
|
||||||
|
def _query_running(self, ifaceobjrunning):
|
||||||
|
# Not implemented
|
||||||
|
return
|
||||||
|
|
||||||
|
_run_ops = {'pre-up' : _up,
|
||||||
|
'post-down' : _down,
|
||||||
|
'query-checkcurr' : _query_check,
|
||||||
|
'query-running' : _query_running}
|
||||||
|
|
||||||
|
def get_ops(self):
|
||||||
|
return self._run_ops.keys()
|
||||||
|
|
||||||
|
def _init_command_handlers(self):
|
||||||
|
if not self.ipcmd:
|
||||||
|
self.ipcmd = iproute2(**self.get_flags())
|
||||||
|
|
||||||
|
def run(self, ifaceobj, operation, query_ifaceobj=None):
|
||||||
|
op_handler = self._run_ops.get(operation)
|
||||||
|
if not op_handler:
|
||||||
|
return
|
||||||
|
if (operation != 'query-running' and
|
||||||
|
not self._is_vxlan_device(ifaceobj)):
|
||||||
|
return
|
||||||
|
self._init_command_handlers()
|
||||||
|
if operation == 'query-checkcurr':
|
||||||
|
op_handler(self, ifaceobj, query_ifaceobj)
|
||||||
|
else:
|
||||||
|
op_handler(self, ifaceobj)
|
28
config/addons.conf
Normal file
28
config/addons.conf
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
pre-up,bridge
|
||||||
|
pre-up,mstpctl
|
||||||
|
pre-up,vlan
|
||||||
|
pre-up,vxlan
|
||||||
|
pre-up,ifenslave
|
||||||
|
pre-up,bridgevlanaware
|
||||||
|
pre-up,usercmds
|
||||||
|
pre-up,loopback
|
||||||
|
up,dhcp
|
||||||
|
up,address
|
||||||
|
up,addressvirtual
|
||||||
|
up,usercmds
|
||||||
|
up,loopback
|
||||||
|
post-up,ethtool
|
||||||
|
post-up,usercmds
|
||||||
|
pre-down,usercmds
|
||||||
|
down,dhcp
|
||||||
|
down,addressvirtual
|
||||||
|
down,address
|
||||||
|
down,usercmds
|
||||||
|
post-down,bridgevlanaware
|
||||||
|
post-down,bridge
|
||||||
|
post-down,mstpctl
|
||||||
|
post-down,vxlan
|
||||||
|
post-down,vlan
|
||||||
|
post-down,ifenslave
|
||||||
|
post-down,usercmds
|
||||||
|
post-down,loopback
|
153
docs.addons/Makefile
Normal file
153
docs.addons/Makefile
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
# Makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build
|
||||||
|
PAPER =
|
||||||
|
BUILDDIR = build
|
||||||
|
|
||||||
|
# Internal variables.
|
||||||
|
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||||
|
PAPEROPT_letter = -D latex_paper_size=letter
|
||||||
|
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||||
|
# the i18n builder cannot share the environment and doctrees with the others
|
||||||
|
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||||
|
|
||||||
|
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " dirhtml to make HTML files named index.html in directories"
|
||||||
|
@echo " singlehtml to make a single large HTML file"
|
||||||
|
@echo " pickle to make pickle files"
|
||||||
|
@echo " json to make JSON files"
|
||||||
|
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||||
|
@echo " qthelp to make HTML files and a qthelp project"
|
||||||
|
@echo " devhelp to make HTML files and a Devhelp project"
|
||||||
|
@echo " epub to make an epub"
|
||||||
|
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||||
|
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||||
|
@echo " text to make text files"
|
||||||
|
@echo " man to make manual pages"
|
||||||
|
@echo " texinfo to make Texinfo files"
|
||||||
|
@echo " info to make Texinfo files and run them through makeinfo"
|
||||||
|
@echo " gettext to make PO message catalogs"
|
||||||
|
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||||
|
@echo " linkcheck to check all external links for integrity"
|
||||||
|
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
-rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
|
html:
|
||||||
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
|
||||||
|
dirhtml:
|
||||||
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||||
|
|
||||||
|
singlehtml:
|
||||||
|
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||||
|
|
||||||
|
pickle:
|
||||||
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the pickle files."
|
||||||
|
|
||||||
|
json:
|
||||||
|
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the JSON files."
|
||||||
|
|
||||||
|
htmlhelp:
|
||||||
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
|
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||||
|
|
||||||
|
qthelp:
|
||||||
|
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||||
|
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||||
|
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ifupdown2.qhcp"
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ifupdown2.qhc"
|
||||||
|
|
||||||
|
devhelp:
|
||||||
|
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished."
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# mkdir -p $$HOME/.local/share/devhelp/ifupdown2"
|
||||||
|
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ifupdown2"
|
||||||
|
@echo "# devhelp"
|
||||||
|
|
||||||
|
epub:
|
||||||
|
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||||
|
|
||||||
|
latex:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||||
|
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||||
|
"(use \`make latexpdf' here to do that automatically)."
|
||||||
|
|
||||||
|
latexpdf:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo "Running LaTeX files through pdflatex..."
|
||||||
|
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||||
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
|
text:
|
||||||
|
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||||
|
|
||||||
|
man:
|
||||||
|
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||||
|
|
||||||
|
texinfo:
|
||||||
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||||
|
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||||
|
"(use \`make info' here to do that automatically)."
|
||||||
|
|
||||||
|
info:
|
||||||
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
|
@echo "Running Texinfo files through makeinfo..."
|
||||||
|
make -C $(BUILDDIR)/texinfo info
|
||||||
|
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||||
|
|
||||||
|
gettext:
|
||||||
|
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||||
|
|
||||||
|
changes:
|
||||||
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||||
|
@echo
|
||||||
|
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||||
|
|
||||||
|
linkcheck:
|
||||||
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||||
|
@echo
|
||||||
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
|
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||||
|
|
||||||
|
doctest:
|
||||||
|
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||||
|
@echo "Testing of doctests in the sources finished, look at the " \
|
||||||
|
"results in $(BUILDDIR)/doctest/output.txt."
|
61
docs.addons/source/addonsapiref.rst
Normal file
61
docs.addons/source/addonsapiref.rst
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
Documentation for the ifupdownaddons default addons modules
|
||||||
|
***********************************************************
|
||||||
|
|
||||||
|
address
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. automodule:: address
|
||||||
|
|
||||||
|
.. autoclass:: address
|
||||||
|
:members: run, get_ops
|
||||||
|
|
||||||
|
|
||||||
|
bridge
|
||||||
|
======
|
||||||
|
|
||||||
|
.. automodule:: bridge
|
||||||
|
|
||||||
|
.. autoclass:: bridge
|
||||||
|
:members: run, get_ops
|
||||||
|
|
||||||
|
dhcp
|
||||||
|
====
|
||||||
|
|
||||||
|
.. automodule:: dhcp
|
||||||
|
|
||||||
|
.. autoclass:: dhcp
|
||||||
|
|
||||||
|
ethtool
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. automodule:: ethtool
|
||||||
|
|
||||||
|
.. autoclass:: ethtool
|
||||||
|
|
||||||
|
ifenslave
|
||||||
|
=========
|
||||||
|
|
||||||
|
.. automodule:: ifenslave
|
||||||
|
|
||||||
|
.. autoclass:: ifenslave
|
||||||
|
|
||||||
|
mstpctl
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. automodule:: mstpctl
|
||||||
|
|
||||||
|
.. autoclass:: mstpctl
|
||||||
|
|
||||||
|
usercmds
|
||||||
|
========
|
||||||
|
|
||||||
|
.. automodule:: usercmds
|
||||||
|
|
||||||
|
.. autoclass:: usercmds
|
||||||
|
|
||||||
|
vlan
|
||||||
|
====
|
||||||
|
|
||||||
|
.. automodule:: vlan
|
||||||
|
|
||||||
|
.. autoclass:: vlan
|
44
docs.addons/source/addonshelperapiref.rst
Normal file
44
docs.addons/source/addonshelperapiref.rst
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
Documentation for the ifupdownaddons package helper modules
|
||||||
|
***********************************************************
|
||||||
|
|
||||||
|
This package contains modules that provide helper methods
|
||||||
|
for ifupdown2 addon modules to interact directly with tools
|
||||||
|
like iproute2, brctl etc.
|
||||||
|
|
||||||
|
|
||||||
|
bridgeutils
|
||||||
|
===========
|
||||||
|
|
||||||
|
Helper module to work with bridgeutil commands
|
||||||
|
|
||||||
|
.. automodule:: bridgeutils
|
||||||
|
|
||||||
|
.. autoclass:: brctl
|
||||||
|
|
||||||
|
ifenslaveutil
|
||||||
|
=============
|
||||||
|
|
||||||
|
Helper module to interact with linux api to create bonds.
|
||||||
|
Currently this is via sysfs.
|
||||||
|
|
||||||
|
.. automodule:: ifenslaveutil
|
||||||
|
|
||||||
|
.. autoclass:: ifenslaveutil
|
||||||
|
|
||||||
|
dhclient
|
||||||
|
========
|
||||||
|
|
||||||
|
Helper module to interact with dhclient tools.
|
||||||
|
|
||||||
|
.. automodule:: dhclient
|
||||||
|
|
||||||
|
.. autoclass:: dhclient
|
||||||
|
|
||||||
|
iproute2
|
||||||
|
========
|
||||||
|
|
||||||
|
Helper module to interact with iproute2 tools.
|
||||||
|
|
||||||
|
.. automodule:: iproute2
|
||||||
|
|
||||||
|
.. autoclass:: iproute2
|
247
docs.addons/source/conf.py
Normal file
247
docs.addons/source/conf.py
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# ifupdown2-addons documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Mon Jul 21 11:17:17 2014.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its containing dir.
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.abspath('../../addons'))
|
||||||
|
sys.path.append(os.path.abspath('../../'))
|
||||||
|
sys.path.append(os.path.abspath('../../ifupdownaddons'))
|
||||||
|
sys.path.append(os.path.abspath('../../../ifupdown2'))
|
||||||
|
|
||||||
|
# -- General configuration -----------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
|
extensions = ['sphinx.ext.autodoc']
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix of source filenames.
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The encoding of source files.
|
||||||
|
#source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = u'ifupdown2-addons'
|
||||||
|
copyright = u'2014, Roopa Prabhu'
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
# built documents.
|
||||||
|
#
|
||||||
|
# The short X.Y version.
|
||||||
|
version = '0.1'
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = '0.1'
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#language = None
|
||||||
|
|
||||||
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
|
# non-false value, then it is used:
|
||||||
|
#today = ''
|
||||||
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
|
#today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
exclude_patterns = []
|
||||||
|
|
||||||
|
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||||
|
#default_role = None
|
||||||
|
|
||||||
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
#add_function_parentheses = True
|
||||||
|
|
||||||
|
# If true, the current module name will be prepended to all description
|
||||||
|
# unit titles (such as .. function::).
|
||||||
|
#add_module_names = True
|
||||||
|
|
||||||
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
|
# output. They are ignored by default.
|
||||||
|
#show_authors = False
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# A list of ignored prefixes for module index sorting.
|
||||||
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output ---------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
html_theme = 'default'
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
#html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
|
#html_theme_path = []
|
||||||
|
|
||||||
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
|
# "<project> v<release> documentation".
|
||||||
|
#html_title = None
|
||||||
|
|
||||||
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
|
#html_short_title = None
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
|
# of the sidebar.
|
||||||
|
#html_logo = None
|
||||||
|
|
||||||
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
|
# pixels large.
|
||||||
|
#html_favicon = None
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
|
# using the given strftime format.
|
||||||
|
#html_last_updated_fmt = '%b %d, %Y'
|
||||||
|
|
||||||
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
|
# typographically correct entities.
|
||||||
|
#html_use_smartypants = True
|
||||||
|
|
||||||
|
# Custom sidebar templates, maps document names to template names.
|
||||||
|
#html_sidebars = {}
|
||||||
|
|
||||||
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
|
# template names.
|
||||||
|
#html_additional_pages = {}
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#html_domain_indices = True
|
||||||
|
|
||||||
|
# If false, no index is generated.
|
||||||
|
#html_use_index = True
|
||||||
|
|
||||||
|
# If true, the index is split into individual pages for each letter.
|
||||||
|
#html_split_index = False
|
||||||
|
|
||||||
|
# If true, links to the reST sources are added to the pages.
|
||||||
|
#html_show_sourcelink = True
|
||||||
|
|
||||||
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_sphinx = True
|
||||||
|
|
||||||
|
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_copyright = True
|
||||||
|
|
||||||
|
# If true, an OpenSearch description file will be output, and all pages will
|
||||||
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
|
# base URL from which the finished HTML is served.
|
||||||
|
#html_use_opensearch = ''
|
||||||
|
|
||||||
|
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
|
#html_file_suffix = None
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'ifupdown2-addonsdoc'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
|
|
||||||
|
latex_elements = {
|
||||||
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
#'papersize': 'letterpaper',
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#'pointsize': '10pt',
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#'preamble': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
|
latex_documents = [
|
||||||
|
('index', 'ifupdown2-addons.tex', u'ifupdown2-addons Documentation',
|
||||||
|
u'Roopa Prabhu', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
# the title page.
|
||||||
|
#latex_logo = None
|
||||||
|
|
||||||
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
|
# not chapters.
|
||||||
|
#latex_use_parts = False
|
||||||
|
|
||||||
|
# If true, show page references after internal links.
|
||||||
|
#latex_show_pagerefs = False
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#latex_show_urls = False
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#latex_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output --------------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
('index', 'ifupdown2-addons', u'ifupdown2-addons Documentation',
|
||||||
|
[u'Roopa Prabhu'], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#man_show_urls = False
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Texinfo output ------------------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
|
# (source start file, target name, title, author,
|
||||||
|
# dir menu entry, description, category)
|
||||||
|
texinfo_documents = [
|
||||||
|
('index', 'ifupdown2-addons', u'ifupdown2-addons Documentation',
|
||||||
|
u'Roopa Prabhu', 'ifupdown2-addons', 'One line description of project.',
|
||||||
|
'Miscellaneous'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#texinfo_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#texinfo_domain_indices = True
|
||||||
|
|
||||||
|
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||||
|
#texinfo_show_urls = 'footnote'
|
58
docs.addons/source/developmentcorner.rst
Normal file
58
docs.addons/source/developmentcorner.rst
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
Development Corner
|
||||||
|
==================
|
||||||
|
|
||||||
|
Writing a ifupdown2 addon module
|
||||||
|
--------------------------------
|
||||||
|
ifupdown2 addon modules are part of the python-ifupdown2-addons package.
|
||||||
|
They are installed under /usr/share/ifupdownaddons directory on the target
|
||||||
|
system.
|
||||||
|
|
||||||
|
The center of the universe for an addon module is the 'class iface' object
|
||||||
|
exported by the python-ifupdown2 package.
|
||||||
|
|
||||||
|
The iface object is modeled after an iface entry in the user provided network
|
||||||
|
configuration file (eg. /etc/network/interfaces). For more details see
|
||||||
|
the api reference for the iface class.
|
||||||
|
|
||||||
|
ifupdown2 dynamically loads a python addon module. It expects the addon module
|
||||||
|
to implement a few methods.
|
||||||
|
|
||||||
|
* all addon modules must inherit from moduleBase class
|
||||||
|
* the module must implement a class by the same name
|
||||||
|
* the network interface object (class iface) and the operation to be performed
|
||||||
|
is passed to the modules. Operation can be any of 'pre-up', 'up', 'post-up',
|
||||||
|
'pre-down', 'down', 'post-down', 'query-check', 'query-running'.
|
||||||
|
The module can choose to support a subset or all operations.
|
||||||
|
In cases when the operation is query-check, the module must compare between
|
||||||
|
the given and running state and return the checked state of the object in
|
||||||
|
queryobjcur passed as argument to the run menthod.
|
||||||
|
* the python addon class must provide a few methods:
|
||||||
|
* run() : method to configure the interface.
|
||||||
|
* get_ops() : must return a list of operations it supports.
|
||||||
|
eg: 'pre-up', 'post-down'
|
||||||
|
* get_dependent_ifacenames() : must return a list of interfaces the
|
||||||
|
supported interface is dependent on. This is used to build the
|
||||||
|
dependency list for sorting and executing interfaces in dependency order.
|
||||||
|
* if the module supports -r option to ifquery, ie ability to construct the
|
||||||
|
ifaceobj from running state, it can optionally implement the
|
||||||
|
get_dependent_ifacenames_running() method, to return the list of
|
||||||
|
dependent interfaces derived from running state of the interface.
|
||||||
|
This is different from get_dependent_ifacenames() where the dependent
|
||||||
|
interfaces are derived from the interfaces config file (provided by the
|
||||||
|
user).
|
||||||
|
* provide a dictionary of all supported attributes in the _modinfo
|
||||||
|
attribute. This is useful for syntax help and man page generation.
|
||||||
|
|
||||||
|
python-ifupdown2-addons package also installs ifupdownaddons python package
|
||||||
|
that contains helper modules for all addon modules. Its optional for the addon
|
||||||
|
module to use this package.
|
||||||
|
|
||||||
|
see example address handling module /usr/share/ifupdownaddons/address.py
|
||||||
|
|
||||||
|
API reference
|
||||||
|
-------------
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
addonsapiref.rst
|
||||||
|
addonshelperapiref.rst
|
29
docs.addons/source/gettingstarted.rst
Normal file
29
docs.addons/source/gettingstarted.rst
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
Getting Started
|
||||||
|
===============
|
||||||
|
|
||||||
|
Prerequisites
|
||||||
|
-------------
|
||||||
|
* python-ifupdown2-addons is currently only tested on debian wheezy
|
||||||
|
* python-ifupdown2-addons needs python version 2.6 or greater
|
||||||
|
* build depends on: python-stdeb (for deb builds), python-docutils (for rst2man)
|
||||||
|
* depends on python-gvgen package for printing interface graphs (this will be made optional in the future)
|
||||||
|
* optional dependency for template engine: python-mako
|
||||||
|
* python-ifupdown2-addons has an install dependency on python-ifupdown2
|
||||||
|
|
||||||
|
Building
|
||||||
|
--------
|
||||||
|
$git clone <ifupdown2 git url> ifupdown2
|
||||||
|
|
||||||
|
$cd ifupdown2/ifupdown2-addons
|
||||||
|
|
||||||
|
$./build.sh
|
||||||
|
|
||||||
|
Installing
|
||||||
|
----------
|
||||||
|
install generated python-ifupdown2-addons-<ver>.deb using dpkg
|
||||||
|
|
||||||
|
$dpkg -i <python-ifupdown2-addons-<ver>.deb
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
25
docs.addons/source/index.rst
Normal file
25
docs.addons/source/index.rst
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
.. ifupdown2 documentation master file, created by
|
||||||
|
sphinx-quickstart on Sun Jul 6 23:49:20 2014.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
|
Welcome to ifupdown2-addons documentation!
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
Contents:
|
||||||
|
=========
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
intro.rst
|
||||||
|
gettingstarted.rst
|
||||||
|
developmentcorner.rst
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
==================
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
||||||
|
|
21
docs.addons/source/intro.rst
Normal file
21
docs.addons/source/intro.rst
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
python-ifupdown2-addons
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
The python-ifupdown2-addons package contains ifupdown2 addon modules.
|
||||||
|
|
||||||
|
addon modules are responsible for applying interface configuration.
|
||||||
|
The modules are installed under /usr/share/ifupdownmodules.
|
||||||
|
|
||||||
|
Each module can declare its own set of supported attributes. Each module
|
||||||
|
is passed the iface object (which is a representation of /etc/network/interfaces
|
||||||
|
iface entry). Each module is also passed the operation to be performed.
|
||||||
|
|
||||||
|
Example modules are /usr/share/ifupdownmodules/address.py,
|
||||||
|
/usr/share/ifupdownmodules/bridge.py etc
|
||||||
|
|
||||||
|
The order in which these modules are invoked is listed in
|
||||||
|
/var/lib/ifupdownaddons/addons.conf. There is a ifaddon utility in the works
|
||||||
|
to better manage the module ordering.
|
||||||
|
|
||||||
|
For details on how to add a module, see the api reference and development
|
||||||
|
documentation.
|
0
ifupdownaddons/__init__.py
Normal file
0
ifupdownaddons/__init__.py
Normal file
498
ifupdownaddons/bridgeutils.py
Normal file
498
ifupdownaddons/bridgeutils.py
Normal file
@ -0,0 +1,498 @@
|
|||||||
|
#!/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 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 self.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 = self.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.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(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['mcrouter'] = self.read_file_oneline(
|
||||||
|
'/sys/class/net/%s/brport/multicast_router' %pname)
|
||||||
|
bportattrs['mcfl'] = self.read_file_oneline(
|
||||||
|
'/sys/class/net/%s/brport/multicast_fast_leave' %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(str(e))
|
||||||
|
pass
|
||||||
|
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 = self.exec_command('/sbin/brctl show')
|
||||||
|
else:
|
||||||
|
brctlout = self.exec_command('/sbin/brctl show ' + 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 self.DRYRUN:
|
||||||
|
return None
|
||||||
|
if self.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 self.DRYRUN: return
|
||||||
|
try:
|
||||||
|
linkCache.add_attr(attrlist, value)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _cache_delete(self, attrlist):
|
||||||
|
if self.DRYRUN: return
|
||||||
|
try:
|
||||||
|
linkCache.del_attr(attrlist)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _cache_invalidate(self):
|
||||||
|
if self.DRYRUN: return
|
||||||
|
linkCache.invalidate()
|
||||||
|
|
||||||
|
def create_bridge(self, bridgename):
|
||||||
|
if self.bridge_exists(bridgename):
|
||||||
|
return
|
||||||
|
self.exec_command('/sbin/brctl addbr %s' %bridgename)
|
||||||
|
self._cache_update([bridgename], {})
|
||||||
|
|
||||||
|
def delete_bridge(self, bridgename):
|
||||||
|
if not self.bridge_exists(bridgename):
|
||||||
|
return
|
||||||
|
self.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
|
||||||
|
self.exec_command('/sbin/brctl addif ' + 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
|
||||||
|
self.exec_command('/sbin/brctl delif ' + 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 self.CACHE:
|
||||||
|
curval = portattrs.get(k)
|
||||||
|
if curval and curval == v:
|
||||||
|
continue
|
||||||
|
self.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
|
||||||
|
self.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:
|
||||||
|
self.exec_command('/sbin/brctl set%s %s %s'
|
||||||
|
%(k, bridgename, v))
|
||||||
|
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
|
||||||
|
self.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):
|
||||||
|
self.exec_command('/sbin/brctl stp ' + 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):
|
||||||
|
self.exec_command('/sbin/brctl setageing ' + 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, bridgeprio):
|
||||||
|
self.exec_command('/sbin/brctl setbridgeprio ' + bridge + ' ' +
|
||||||
|
bridgeprio)
|
||||||
|
|
||||||
|
def get_bridgeprio(self, bridge):
|
||||||
|
return self.read_file_oneline(
|
||||||
|
'/sys/class/net/%s/bridge/priority' %bridge)
|
||||||
|
|
||||||
|
def set_fd(self, bridge, fd):
|
||||||
|
self.exec_command('/sbin/brctl setfd ' + 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):
|
||||||
|
self.exec_command('/sbin/brctl sethello ' + 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):
|
||||||
|
self.exec_command('/sbin/brctl setmaxage ' + 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):
|
||||||
|
self.exec_command('/sbin/brctl setpathcost %s' %bridge + ' %s' %port +
|
||||||
|
' %s' %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):
|
||||||
|
self.exec_command('/sbin/brctl setportprio %s' %bridge + ' %s' %port +
|
||||||
|
' %s' %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):
|
||||||
|
self.exec_command('/sbin/brctl sethashmax %s' %bridge + ' %s' %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):
|
||||||
|
self.exec_command('/sbin/brctl sethashel %s' %bridge + ' %s' %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):
|
||||||
|
self.exec_command('/sbin/brctl setmclmc %s' %bridge + ' %s' %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):
|
||||||
|
self.exec_command('/sbin/brctl setmcrouter %s' %bridge +
|
||||||
|
' %s' %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):
|
||||||
|
self.exec_command('/sbin/brctl setmcsnoop %s' %bridge +
|
||||||
|
' %s' %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):
|
||||||
|
self.exec_command('/sbin/brctl setmcsqc %s' %bridge +
|
||||||
|
' %s' %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):
|
||||||
|
self.exec_command('/sbin/brctl setmcqifaddr %s' %bridge +
|
||||||
|
' %s' %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):
|
||||||
|
self.exec_command('/sbin/brctl setmcquerier %s' %bridge +
|
||||||
|
' %s' %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
|
||||||
|
|
||||||
|
self.exec_command('/sbin/brctl setmcqv4src %s' %bridge +
|
||||||
|
' %d %s' %(vlan, mcquerier))
|
||||||
|
|
||||||
|
def del_mcqv4src(self, bridge, vlan):
|
||||||
|
self.exec_command('/sbin/brctl delmcqv4src %s %d' %(bridge, vlan))
|
||||||
|
|
||||||
|
def get_mcqv4src(self, bridge, vlan=None):
|
||||||
|
mcqv4src = {}
|
||||||
|
mcqout = self.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):
|
||||||
|
self.exec_command('/sbin/brctl setmclmi %s' %bridge +
|
||||||
|
' %s' %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):
|
||||||
|
self.exec_command('/sbin/brctl setmcmi %s' %bridge +
|
||||||
|
' %s' %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 bridge_port_exists(self, bridge, bridgeportname):
|
||||||
|
try:
|
||||||
|
return bridgeportname in os.listdir('/sys/class/net/%s/brif'
|
||||||
|
%bridge)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_bridge_ports(self, bridgename):
|
||||||
|
try:
|
||||||
|
return os.listdir('/sys/class/net/%s/brif/' %bridgename)
|
||||||
|
except:
|
||||||
|
return {}
|
90
ifupdownaddons/cache.py
Normal file
90
ifupdownaddons/cache.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
import pprint
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
class linkCache():
|
||||||
|
""" This class contains methods and instance variables to cache
|
||||||
|
link info """
|
||||||
|
|
||||||
|
_shared_state = {}
|
||||||
|
|
||||||
|
""" { <ifacename> : { 'ifindex': <index>,
|
||||||
|
'mtu': <mtu>,
|
||||||
|
'state' : <state>',
|
||||||
|
'flags' : <flags>,
|
||||||
|
'kind' : <kind: bridge, bond, vlan>,
|
||||||
|
'linkinfo' : {<attr1> : <attrval1>,
|
||||||
|
<attr2> : <attrval2>,
|
||||||
|
<ports> : {
|
||||||
|
} """
|
||||||
|
links = {}
|
||||||
|
@classmethod
|
||||||
|
def get_attr(cls, mapList):
|
||||||
|
return reduce(lambda d, k: d[k], mapList, linkCache.links)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_attr(cls, mapList, value):
|
||||||
|
cls.get_attr(mapList[:-1])[mapList[-1]] = value
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def del_attr(cls, mapList):
|
||||||
|
try:
|
||||||
|
del cls.get_attr(mapList[:-1])[mapList[-1]]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_attrdict(cls, mapList, valuedict):
|
||||||
|
try:
|
||||||
|
cls.get_attr(mapList[:-1])[mapList[-1]].update(valuedict)
|
||||||
|
except:
|
||||||
|
cls.get_attr(mapList[:-1])[mapList[-1]] = valuedict
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def append_to_attrlist(cls, mapList, value):
|
||||||
|
cls.get_attr(mapList[:-1])[mapList[-1]].append(value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def remove_from_attrlist(cls, mapList, value):
|
||||||
|
try:
|
||||||
|
cls.get_attr(mapList[:-1])[mapList[-1]].remove(value)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def check_attr(cls, attrlist, value=None):
|
||||||
|
try:
|
||||||
|
cachedvalue = cls.get_attr(attrlist)
|
||||||
|
if value:
|
||||||
|
if cachedvalue == value:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
elif cachedvalue:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def invalidate(cls):
|
||||||
|
cls.links = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def dump(cls):
|
||||||
|
print 'Dumping link cache'
|
||||||
|
pp = pprint.PrettyPrinter(indent=4)
|
||||||
|
pp.pprint(cls.links)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def dump_link(cls, linkname):
|
||||||
|
print 'Dumping link %s' %linkname
|
||||||
|
pp = pprint.PrettyPrinter(indent=4)
|
||||||
|
pp.pprint(cls.links.get(linkname))
|
86
ifupdownaddons/dhclient.py
Normal file
86
ifupdownaddons/dhclient.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
from utilsbase import *
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
|
||||||
|
FNULL = open(os.devnull, 'w')
|
||||||
|
|
||||||
|
class dhclient(utilsBase):
|
||||||
|
""" This class contains helper methods to interact with the dhclient
|
||||||
|
utility """
|
||||||
|
|
||||||
|
def _pid_exists(self, pidfilename):
|
||||||
|
if os.path.exists(pidfilename):
|
||||||
|
pid = self.read_file_oneline(pidfilename)
|
||||||
|
if not os.path.exists('/proc/%s' %pid):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_running(self, ifacename):
|
||||||
|
return self._pid_exists('/run/dhclient.%s.pid' %ifacename)
|
||||||
|
|
||||||
|
def is_running6(self, ifacename):
|
||||||
|
return self._pid_exists('/run/dhclient6.%s.pid' %ifacename)
|
||||||
|
|
||||||
|
def stop(self, ifacename):
|
||||||
|
if os.path.exists('/sbin/dhclient3'):
|
||||||
|
cmd = ['/sbin/dhclient3', '-x', '-pf',
|
||||||
|
'/run/dhclient.%s.pid' %ifacename, '-lf',
|
||||||
|
'/var/lib/dhcp3/dhclient.%s.leases' %ifacename,
|
||||||
|
'%s' %ifacename]
|
||||||
|
else:
|
||||||
|
cmd = ['/sbin/dhclient', '-x', '-pf',
|
||||||
|
'/run/dhclient.%s.pid' %ifacename,
|
||||||
|
'-lf', '/var/lib/dhcp/dhclient.%s.leases' %ifacename,
|
||||||
|
'%s' %ifacename]
|
||||||
|
self.subprocess_check_call(cmd)
|
||||||
|
|
||||||
|
def start(self, ifacename):
|
||||||
|
if os.path.exists('/sbin/dhclient3'):
|
||||||
|
cmd = ['/sbin/dhclient3', '-pf',
|
||||||
|
'/run/dhclient.%s.pid' %ifacename,
|
||||||
|
'-lf', '/var/lib/dhcp3/dhclient.%s.leases' %ifacename,
|
||||||
|
'%s' %ifacename]
|
||||||
|
else:
|
||||||
|
cmd = ['/sbin/dhclient', '-pf', '/run/dhclient.%s.pid' %ifacename,
|
||||||
|
'-lf', '/var/lib/dhcp/dhclient.%s.leases' %ifacename,
|
||||||
|
'%s' %ifacename]
|
||||||
|
self.subprocess_check_call(cmd)
|
||||||
|
|
||||||
|
def release(self, ifacename):
|
||||||
|
if os.path.exists('/sbin/dhclient3'):
|
||||||
|
cmd = ['/sbin/dhclient3', '-r', '-pf',
|
||||||
|
'/run/dhclient.%s.pid' %ifacename, '-lf',
|
||||||
|
'/var/lib/dhcp3/dhclient.%s.leases' %ifacename,
|
||||||
|
'%s' %ifacename]
|
||||||
|
else:
|
||||||
|
cmd = ['/sbin/dhclient', '-r', '-pf',
|
||||||
|
'/run/dhclient.%s.pid' %ifacename,
|
||||||
|
'-lf', '/var/lib/dhcp/dhclient.%s.leases' %ifacename,
|
||||||
|
'%s' %ifacename]
|
||||||
|
self.subprocess_check_call(cmd)
|
||||||
|
|
||||||
|
def start6(self, ifacename):
|
||||||
|
self.subprocess_check_call(['dhclient', '-6', '-pf',
|
||||||
|
'/run/dhclient6.%s.pid' %ifacename, '-lf',
|
||||||
|
'/var/lib/dhcp/dhclient.%s.leases ' %ifacename,
|
||||||
|
'%s' %ifacename])
|
||||||
|
|
||||||
|
def stop6(self, ifacename):
|
||||||
|
self.subprocess_check_call(['dhclient', '-6', '-x', '-pf',
|
||||||
|
'/run/dhclient.%s.pid' %ifacename, '-lf',
|
||||||
|
'/var/lib/dhcp/dhclient.%s.leases ' %ifacename,
|
||||||
|
'%s' %ifacename])
|
||||||
|
|
||||||
|
def release6(self, ifacename):
|
||||||
|
self.subprocess_check_call(['dhclient', '-6', '-r', '-pf',
|
||||||
|
'/run/dhclient6.%s.pid' %ifacename, '-lf',
|
||||||
|
'/var/lib/dhcp/dhclient6.%s.leases' %ifacename,
|
||||||
|
'%s' %ifacename])
|
407
ifupdownaddons/ifenslaveutil.py
Normal file
407
ifupdownaddons/ifenslaveutil.py
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
from ifupdown.iface import *
|
||||||
|
from utilsbase import *
|
||||||
|
from iproute2 import *
|
||||||
|
from cache import *
|
||||||
|
|
||||||
|
class ifenslaveutil(utilsBase):
|
||||||
|
""" This class contains methods to interact with linux kernel bond
|
||||||
|
related interfaces """
|
||||||
|
|
||||||
|
_cache_fill_done = False
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
utilsBase.__init__(self, *args, **kargs)
|
||||||
|
if self.CACHE and not self._cache_fill_done:
|
||||||
|
self._bond_linkinfo_fill_all()
|
||||||
|
self._cache_fill_done = True
|
||||||
|
|
||||||
|
def _bond_linkinfo_fill_attrs(self, bondname):
|
||||||
|
try:
|
||||||
|
linkCache.links[bondname]['linkinfo'] = {}
|
||||||
|
except:
|
||||||
|
linkCache.links[bondname] = {'linkinfo': {}}
|
||||||
|
|
||||||
|
try:
|
||||||
|
linkCache.set_attr([bondname, 'linkinfo', 'slaves'],
|
||||||
|
self.read_file_oneline('/sys/class/net/%s/bonding/slaves'
|
||||||
|
%bondname).split())
|
||||||
|
linkCache.set_attr([bondname, 'linkinfo', 'mode'],
|
||||||
|
self.read_file_oneline('/sys/class/net/%s/bonding/mode'
|
||||||
|
%bondname).split()[0])
|
||||||
|
linkCache.set_attr([bondname, 'linkinfo', 'xmit_hash_policy'],
|
||||||
|
self.read_file_oneline(
|
||||||
|
'/sys/class/net/%s/bonding/xmit_hash_policy'
|
||||||
|
%bondname).split()[0])
|
||||||
|
linkCache.set_attr([bondname, 'linkinfo', 'lacp_rate'],
|
||||||
|
self.read_file_oneline('/sys/class/net/%s/bonding/lacp_rate'
|
||||||
|
%bondname).split()[1])
|
||||||
|
linkCache.set_attr([bondname, 'linkinfo', 'ad_sys_priority'],
|
||||||
|
self.read_file_oneline('/sys/class/net/%s/bonding/ad_sys_priority'
|
||||||
|
%bondname))
|
||||||
|
linkCache.set_attr([bondname, 'linkinfo', 'ad_sys_mac_addr'],
|
||||||
|
self.read_file_oneline('/sys/class/net/%s/bonding/ad_sys_mac_addr'
|
||||||
|
%bondname))
|
||||||
|
map(lambda x: linkCache.set_attr([bondname, 'linkinfo', x],
|
||||||
|
self.read_file_oneline('/sys/class/net/%s/bonding/%s'
|
||||||
|
%(bondname, x))),
|
||||||
|
['use_carrier', 'miimon', 'min_links', 'num_unsol_na',
|
||||||
|
'num_grat_arp'])
|
||||||
|
except Exception, e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _bond_linkinfo_fill_all(self):
|
||||||
|
bondstr = self.read_file_oneline('/sys/class/net/bonding_masters')
|
||||||
|
if not bondstr:
|
||||||
|
return
|
||||||
|
[self._bond_linkinfo_fill_attrs(b) for b in bondstr.split()]
|
||||||
|
|
||||||
|
def _bond_linkinfo_fill(self, bondname, refresh=False):
|
||||||
|
try:
|
||||||
|
linkCache.get_attr([bondname, 'linkinfo', 'slaves'])
|
||||||
|
return
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
bondstr = self.read_file_oneline('/sys/class/net/bonding_masters')
|
||||||
|
if (not bondstr or bondname not in bondstr.split()):
|
||||||
|
raise Exception('bond %s not found' %bondname)
|
||||||
|
self._bond_linkinfo_fill_attrs(bondname)
|
||||||
|
|
||||||
|
def _cache_get(self, attrlist, refresh=False):
|
||||||
|
try:
|
||||||
|
if self.DRYRUN:
|
||||||
|
return None
|
||||||
|
if self.CACHE:
|
||||||
|
if not ifenslaveutil._cache_fill_done:
|
||||||
|
self._bond_linkinfo_fill_all()
|
||||||
|
ifenslaveutil._cache_fill_done = True
|
||||||
|
return linkCache.get_attr(attrlist)
|
||||||
|
if not refresh:
|
||||||
|
return linkCache.get_attr(attrlist)
|
||||||
|
self._bond_linkinfo_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 self.DRYRUN: return
|
||||||
|
try:
|
||||||
|
if attrlist[-1] == 'slaves':
|
||||||
|
linkCache.add_to_attrlist(attrlist, value)
|
||||||
|
return
|
||||||
|
linkCache.add_attr(attrlist, value)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _cache_delete(self, attrlist, value=None):
|
||||||
|
if self.DRYRUN: return
|
||||||
|
try:
|
||||||
|
if attrlist[-1] == 'slaves':
|
||||||
|
linkCache.remove_from_attrlist(attrlist, value)
|
||||||
|
return
|
||||||
|
linkCache.del_attr(attrlist)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _cache_invalidate(self):
|
||||||
|
if self.DRYRUN: return
|
||||||
|
linkCache.invalidate()
|
||||||
|
|
||||||
|
def set_attrs(self, bondname, attrdict, prehook):
|
||||||
|
for attrname, attrval in attrdict.items():
|
||||||
|
if (self._cache_check([bondname, 'linkinfo',
|
||||||
|
attrname], attrval)):
|
||||||
|
continue
|
||||||
|
if (attrname == 'mode' or attrname == 'xmit_hash_policy' or
|
||||||
|
attrname == 'lacp_rate' or attrname == 'min_links'):
|
||||||
|
if prehook:
|
||||||
|
prehook(bondname)
|
||||||
|
try:
|
||||||
|
self.write_file('/sys/class/net/%s/bonding/%s'
|
||||||
|
%(bondname, attrname), attrval)
|
||||||
|
except Exception, e:
|
||||||
|
if self.FORCE:
|
||||||
|
self.logger.warn(str(e))
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def set_use_carrier(self, bondname, use_carrier):
|
||||||
|
if not use_carrier or (use_carrier != '0' and use_carrier != '1'):
|
||||||
|
return
|
||||||
|
if (self._cache_check([bondname, 'linkinfo', 'use_carrier'],
|
||||||
|
use_carrier)):
|
||||||
|
return
|
||||||
|
self.write_file('/sys/class/net/%s' %bondname +
|
||||||
|
'/bonding/use_carrier', use_carrier)
|
||||||
|
self._cache_update([bondname, 'linkinfo',
|
||||||
|
'use_carrier'], use_carrier)
|
||||||
|
|
||||||
|
def get_use_carrier(self, bondname):
|
||||||
|
return self._cache_get([bondname, 'linkinfo', 'use_carrier'])
|
||||||
|
|
||||||
|
def set_xmit_hash_policy(self, bondname, hash_policy, prehook=None):
|
||||||
|
valid_values = ['layer2', 'layer3+4', 'layer2+3']
|
||||||
|
if not hash_policy:
|
||||||
|
return
|
||||||
|
if hash_policy not in valid_values:
|
||||||
|
raise Exception('invalid hash policy value %s' %hash_policy)
|
||||||
|
if (self._cache_check([bondname, 'linkinfo', 'xmit_hash_policy'],
|
||||||
|
hash_policy)):
|
||||||
|
return
|
||||||
|
if prehook:
|
||||||
|
prehook(bondname)
|
||||||
|
self.write_file('/sys/class/net/%s' %bondname +
|
||||||
|
'/bonding/xmit_hash_policy', hash_policy)
|
||||||
|
self._cache_update([bondname, 'linkinfo', 'xmit_hash_policy'],
|
||||||
|
hash_policy)
|
||||||
|
|
||||||
|
def get_xmit_hash_policy(self, bondname):
|
||||||
|
return self._cache_get([bondname, 'linkinfo', 'xmit_hash_policy'])
|
||||||
|
|
||||||
|
def set_miimon(self, bondname, miimon):
|
||||||
|
if (self._cache_check([bondname, 'linkinfo', 'miimon'],
|
||||||
|
miimon)):
|
||||||
|
return
|
||||||
|
self.write_file('/sys/class/net/%s' %bondname +
|
||||||
|
'/bonding/miimon', miimon)
|
||||||
|
self._cache_update([bondname, 'linkinfo', 'miimon'], miimon)
|
||||||
|
|
||||||
|
def get_miimon(self, bondname):
|
||||||
|
return self._cache_get([bondname, 'linkinfo', 'miimon'])
|
||||||
|
|
||||||
|
def set_mode(self, bondname, mode, prehook=None):
|
||||||
|
valid_modes = ['balance-rr', 'active-backup', 'balance-xor',
|
||||||
|
'broadcast', '802.3ad', 'balance-tlb', 'balance-alb']
|
||||||
|
if not mode:
|
||||||
|
return
|
||||||
|
if mode not in valid_modes:
|
||||||
|
raise Exception('invalid mode %s' %mode)
|
||||||
|
if (self._cache_check([bondname, 'linkinfo', 'mode'],
|
||||||
|
mode)):
|
||||||
|
return
|
||||||
|
if prehook:
|
||||||
|
prehook(bondname)
|
||||||
|
self.write_file('/sys/class/net/%s' %bondname + '/bonding/mode', mode)
|
||||||
|
self._cache_update([bondname, 'linkinfo', 'mode'], mode)
|
||||||
|
|
||||||
|
def get_mode(self, bondname):
|
||||||
|
return self._cache_get([bondname, 'linkinfo', 'mode'])
|
||||||
|
|
||||||
|
def set_lacp_rate(self, bondname, lacp_rate, prehook=None, posthook=None):
|
||||||
|
if not lacp_rate or (lacp_rate != '0' and lacp_rate != '1'):
|
||||||
|
return
|
||||||
|
if (self._cache_check([bondname, 'linkinfo', 'lacp_rate'],
|
||||||
|
lacp_rate)):
|
||||||
|
return
|
||||||
|
if prehook:
|
||||||
|
prehook(bondname)
|
||||||
|
try:
|
||||||
|
self.write_file('/sys/class/net/%s' %bondname +
|
||||||
|
'/bonding/lacp_rate', lacp_rate)
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if posthook:
|
||||||
|
prehook(bondname)
|
||||||
|
self._cache_update([bondname, 'linkinfo',
|
||||||
|
'lacp_rate'], lacp_rate)
|
||||||
|
|
||||||
|
def get_lacp_rate(self, bondname):
|
||||||
|
return self._cache_get([bondname, 'linkinfo', 'lacp_rate'])
|
||||||
|
|
||||||
|
def set_lacp_fallback_allow(self, bondname, allow, prehook=None, posthook=None):
|
||||||
|
if (self._cache_check([bondname, 'linkinfo', 'lacp_fallback_allow'],
|
||||||
|
lacp_fallback_allow)):
|
||||||
|
return
|
||||||
|
if prehook:
|
||||||
|
prehook(bondname)
|
||||||
|
try:
|
||||||
|
self.write_file('/sys/class/net/%s' %bondname +
|
||||||
|
'/bonding/lacp_fallback_allow', allow)
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if posthook:
|
||||||
|
posthook(bondname)
|
||||||
|
self._cache_update([bondname, 'linkinfo',
|
||||||
|
'lacp_fallback_allow'], allow)
|
||||||
|
|
||||||
|
def get_lacp_fallback_allow(self, bondname):
|
||||||
|
return self._cache_get([bondname, 'linkinfo', 'lacp_fallback_allow'])
|
||||||
|
|
||||||
|
def set_lacp_fallback_period(self, bondname, period, prehook=None, posthook=None):
|
||||||
|
if (self._cache_check([bondname, 'linkinfo', 'lacp_fallback_period'],
|
||||||
|
lacp_fallback_period)):
|
||||||
|
return
|
||||||
|
if prehook:
|
||||||
|
prehook(bondname)
|
||||||
|
try:
|
||||||
|
self.write_file('/sys/class/net/%s' %bondname +
|
||||||
|
'/bonding/lacp_fallback_period', period)
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if posthook:
|
||||||
|
posthook(bondname)
|
||||||
|
self._cache_update([bondname, 'linkinfo',
|
||||||
|
'lacp_fallback_period'], period)
|
||||||
|
|
||||||
|
def get_lacp_fallback_period(self, bondname):
|
||||||
|
return self._cache_get([bondname, 'linkinfo', 'lacp_fallback_period'])
|
||||||
|
|
||||||
|
def set_min_links(self, bondname, min_links, prehook=None):
|
||||||
|
if (self._cache_check([bondname, 'linkinfo', 'min_links'],
|
||||||
|
min_links)):
|
||||||
|
return
|
||||||
|
if prehook:
|
||||||
|
prehook(bondname)
|
||||||
|
self.write_file('/sys/class/net/%s/bonding/min_links' %bondname,
|
||||||
|
min_links)
|
||||||
|
self._cache_update([bondname, 'linkinfo', 'min_links'], min_links)
|
||||||
|
|
||||||
|
def get_min_links(self, bondname):
|
||||||
|
return self._cache_get([bondname, 'linkinfo', 'min_links'])
|
||||||
|
|
||||||
|
def set_lacp_fallback_priority(self, bondname, port, val):
|
||||||
|
slavefile = '/sys/class/net/%s/bonding_slave/lacp_fallback_priority' %port
|
||||||
|
if os.path.exists(slavefile):
|
||||||
|
self.write_file(slavefile, val)
|
||||||
|
|
||||||
|
def get_lacp_fallback_priority(self, bondname):
|
||||||
|
slaves = self.get_slaves(bondname)
|
||||||
|
if not slaves:
|
||||||
|
return slaves
|
||||||
|
prios = []
|
||||||
|
for slave in slaves:
|
||||||
|
priofile = '/sys/class/net/%s/bonding_slave/lacp_fallback_priority' %slave
|
||||||
|
if os.path.exists(priofile):
|
||||||
|
val = self.read_file_oneline(priofile)
|
||||||
|
if val and val != '0':
|
||||||
|
prio = slave + '=' + val
|
||||||
|
prios.append(prio)
|
||||||
|
prios.sort()
|
||||||
|
prio_str = ' '.join(prios)
|
||||||
|
return prio_str
|
||||||
|
|
||||||
|
def get_ad_sys_mac_addr(self, bondname):
|
||||||
|
return self._cache_get([bondname, 'linkinfo', 'ad_sys_mac_addr'])
|
||||||
|
|
||||||
|
def get_ad_sys_priority(self, bondname):
|
||||||
|
return self._cache_get([bondname, 'linkinfo', 'ad_sys_priority'])
|
||||||
|
|
||||||
|
def enslave_slave(self, bondname, slave, prehook=None, posthook=None):
|
||||||
|
slaves = self._cache_get([bondname, 'linkinfo', 'slaves'])
|
||||||
|
if slaves and slave in slaves: return
|
||||||
|
if prehook:
|
||||||
|
prehook(slave)
|
||||||
|
self.write_file('/sys/class/net/%s' %bondname +
|
||||||
|
'/bonding/slaves', '+' + slave)
|
||||||
|
if posthook:
|
||||||
|
posthook(slave)
|
||||||
|
self._cache_update([bondname, 'linkinfo', 'slaves'], slave)
|
||||||
|
|
||||||
|
def remove_slave(self, bondname, slave):
|
||||||
|
slaves = self._cache_get([bondname, 'linkinfo', 'slaves'])
|
||||||
|
if slave not in slaves:
|
||||||
|
return
|
||||||
|
sysfs_bond_path = ('/sys/class/net/%s' %bondname +
|
||||||
|
'/bonding/slaves')
|
||||||
|
if not os.path.exists(sysfs_bond_path):
|
||||||
|
return
|
||||||
|
self.write_file(sysfs_bond_path, '-' + slave)
|
||||||
|
self._cache_delete([bondname, 'linkinfo', 'slaves'], slave)
|
||||||
|
|
||||||
|
def remove_slaves_all(self, bondname):
|
||||||
|
if not _self._cache_get([bondname, 'linkinfo', 'slaves']):
|
||||||
|
return
|
||||||
|
slaves = None
|
||||||
|
sysfs_bond_path = ('/sys/class/net/%s' %bondname +
|
||||||
|
'/bonding/slaves')
|
||||||
|
ipcmd = iproute2()
|
||||||
|
try:
|
||||||
|
f = open(sysfs_bond_path, 'r')
|
||||||
|
slaves = f.readline().strip().split()
|
||||||
|
f.close()
|
||||||
|
except IOError, e:
|
||||||
|
raise Exception('error reading slaves of bond %s' %bondname
|
||||||
|
+ '(' + str(e) + ')')
|
||||||
|
for slave in slaves:
|
||||||
|
ipcmd.ip_link_down(slave)
|
||||||
|
try:
|
||||||
|
self.remove_slave(bondname, slave)
|
||||||
|
except Exception, e:
|
||||||
|
if not self.FORCE:
|
||||||
|
raise Exception('error removing slave %s'
|
||||||
|
%slave + ' from bond %s' %bondname +
|
||||||
|
'(%s)' %str(e))
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
self._cache_del([bondname, 'linkinfo', 'slaves'])
|
||||||
|
|
||||||
|
def load_bonding_module(self):
|
||||||
|
return self.exec_command('modprobe -q bonding')
|
||||||
|
|
||||||
|
def create_bond(self, bondname):
|
||||||
|
if self.bond_exists(bondname):
|
||||||
|
return
|
||||||
|
sysfs_net = '/sys/class/net/'
|
||||||
|
sysfs_bonding_masters = sysfs_net + 'bonding_masters'
|
||||||
|
if not os.path.exists(sysfs_bonding_masters):
|
||||||
|
self.logger.debug('loading bonding driver')
|
||||||
|
self.load_bonding_module()
|
||||||
|
return True
|
||||||
|
self.write_file(sysfs_bonding_masters, '+' + bondname)
|
||||||
|
self._cache_update([bondname], {})
|
||||||
|
|
||||||
|
def delete_bond(self, bondname):
|
||||||
|
if not os.path.exists('/sys/class/net/%s' %bondname):
|
||||||
|
return
|
||||||
|
self.write_file('/sys/class/net/bonding_masters', '-' + bondname)
|
||||||
|
self._cache_delete([bondname])
|
||||||
|
|
||||||
|
def unset_master(self, bondname):
|
||||||
|
print 'Do nothing yet'
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_slaves(self, bondname):
|
||||||
|
slaves = self._cache_get([bondname, 'linkinfo', 'slaves'])
|
||||||
|
if slaves:
|
||||||
|
return list(slaves)
|
||||||
|
slavefile = '/sys/class/net/%s/bonding/slaves' %bondname
|
||||||
|
if os.path.exists(slavefile):
|
||||||
|
buf = self.read_file_oneline(slavefile)
|
||||||
|
if buf:
|
||||||
|
slaves = buf.split()
|
||||||
|
if not slaves:
|
||||||
|
return slaves
|
||||||
|
self._cache_update([bondname, 'linkinfo', 'slaves'], slaves)
|
||||||
|
return list(slaves)
|
||||||
|
|
||||||
|
def bond_slave_exists(self, bond, slave):
|
||||||
|
slaves = self.get_slaves(bond)
|
||||||
|
if not slaves: return False
|
||||||
|
return slave in slaves
|
||||||
|
|
||||||
|
def bond_exists(self, bondname):
|
||||||
|
return os.path.exists('/sys/class/net/%s/bonding' %bondname)
|
613
ifupdownaddons/iproute2.py
Normal file
613
ifupdownaddons/iproute2.py
Normal file
@ -0,0 +1,613 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
from collections import OrderedDict
|
||||||
|
from utilsbase import *
|
||||||
|
from cache import *
|
||||||
|
|
||||||
|
class iproute2(utilsBase):
|
||||||
|
""" This class contains helper methods to cache and interact with the
|
||||||
|
commands in the iproute2 package """
|
||||||
|
|
||||||
|
_cache_fill_done = False
|
||||||
|
ipbatchbuf = ''
|
||||||
|
ipbatch = False
|
||||||
|
ipbatch_pause = False
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
utilsBase.__init__(self, *args, **kargs)
|
||||||
|
if self.CACHE and not iproute2._cache_fill_done:
|
||||||
|
self._link_fill()
|
||||||
|
self._addr_fill()
|
||||||
|
iproute2._cache_fill_done = True
|
||||||
|
|
||||||
|
def _link_fill(self, ifacename=None, refresh=False):
|
||||||
|
""" fills cache with link information
|
||||||
|
|
||||||
|
if ifacename argument given, fill cache for ifacename, else
|
||||||
|
fill cache for all interfaces in the system
|
||||||
|
"""
|
||||||
|
|
||||||
|
linkout = {}
|
||||||
|
if iproute2._cache_fill_done and not refresh: return
|
||||||
|
try:
|
||||||
|
# if ifacename already present, return
|
||||||
|
if (ifacename and not refresh and
|
||||||
|
linkCache.get_attr([ifacename, 'ifflag'])):
|
||||||
|
return
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
cmdout = self.link_show(ifacename=ifacename)
|
||||||
|
if not cmdout:
|
||||||
|
return
|
||||||
|
for c in cmdout.splitlines():
|
||||||
|
citems = c.split()
|
||||||
|
ifnamenlink = citems[1].split('@')
|
||||||
|
if len(ifnamenlink) > 1:
|
||||||
|
ifname = ifnamenlink[0]
|
||||||
|
iflink = ifnamenlink[1].strip(':')
|
||||||
|
else:
|
||||||
|
ifname = ifnamenlink[0].strip(':')
|
||||||
|
iflink = None
|
||||||
|
linkattrs = {}
|
||||||
|
linkattrs['link'] = iflink
|
||||||
|
linkattrs['ifindex'] = citems[0].strip(':')
|
||||||
|
flags = citems[2].strip('<>').split(',')
|
||||||
|
linkattrs['flags'] = flags
|
||||||
|
linkattrs['ifflag'] = 'UP' if 'UP' in flags else 'DOWN'
|
||||||
|
for i in range(0, len(citems)):
|
||||||
|
if citems[i] == 'mtu': linkattrs['mtu'] = citems[i+1]
|
||||||
|
elif citems[i] == 'state': linkattrs['state'] = citems[i+1]
|
||||||
|
elif citems[i] == 'link/ether': linkattrs['hwaddress'] = citems[i+1]
|
||||||
|
elif citems[i] == 'vlan' and citems[i+1] == 'id':
|
||||||
|
linkattrs['linkinfo'] = {'vlanid' : citems[i+2]}
|
||||||
|
elif citems[i] == 'vxlan' and citems[i+1] == 'id':
|
||||||
|
vattrs = {'vxlanid' : citems[i+2],
|
||||||
|
'svcnode' : []}
|
||||||
|
for j in range(i+2, len(citems)):
|
||||||
|
if citems[j] == 'local':
|
||||||
|
vattrs['local'] = citems[j+1]
|
||||||
|
elif citems[j] == 'svcnode':
|
||||||
|
vattrs['svcnode'].append(citems[j+1])
|
||||||
|
elif citems[j] == 'peernode':
|
||||||
|
vattrs['peernode'].append(citems[j+1])
|
||||||
|
linkattrs['linkinfo'] = vattrs
|
||||||
|
break
|
||||||
|
#linkattrs['alias'] = self.read_file_oneline(
|
||||||
|
# '/sys/class/net/%s/ifalias' %ifname)
|
||||||
|
linkout[ifname] = linkattrs
|
||||||
|
[linkCache.update_attrdict([ifname], linkattrs)
|
||||||
|
for ifname, linkattrs in linkout.items()]
|
||||||
|
|
||||||
|
def _addr_filter(self, addr, scope=None):
|
||||||
|
default_addrs = ['127.0.0.1/8', '::1/128' , '0.0.0.0']
|
||||||
|
if addr in default_addrs:
|
||||||
|
return True
|
||||||
|
if scope and scope == 'link':
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _addr_fill(self, ifacename=None, refresh=False):
|
||||||
|
""" fills cache with address information
|
||||||
|
|
||||||
|
if ifacename argument given, fill cache for ifacename, else
|
||||||
|
fill cache for all interfaces in the system
|
||||||
|
"""
|
||||||
|
|
||||||
|
linkout = {}
|
||||||
|
if iproute2._cache_fill_done: return
|
||||||
|
try:
|
||||||
|
# Check if ifacename is already full, in which case, return
|
||||||
|
if ifacename:
|
||||||
|
linkCache.get_attr([ifacename, 'addrs'])
|
||||||
|
return
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
cmdout = self.addr_show(ifacename=ifacename)
|
||||||
|
if not cmdout:
|
||||||
|
return
|
||||||
|
for c in cmdout.splitlines():
|
||||||
|
citems = c.split()
|
||||||
|
ifnamenlink = citems[1].split('@')
|
||||||
|
if len(ifnamenlink) > 1:
|
||||||
|
ifname = ifnamenlink[0]
|
||||||
|
else:
|
||||||
|
ifname = ifnamenlink[0].strip(':')
|
||||||
|
if citems[2] == 'inet':
|
||||||
|
if self._addr_filter(citems[3], scope=citems[5]):
|
||||||
|
continue
|
||||||
|
addrattrs = {}
|
||||||
|
addrattrs['scope'] = citems[5]
|
||||||
|
addrattrs['type'] = 'inet'
|
||||||
|
linkout[ifname]['addrs'][citems[3]] = addrattrs
|
||||||
|
elif citems[2] == 'inet6':
|
||||||
|
if self._addr_filter(citems[3], scope=citems[5]):
|
||||||
|
continue
|
||||||
|
if citems[5] == 'link': continue #skip 'link' addresses
|
||||||
|
addrattrs = {}
|
||||||
|
addrattrs['scope'] = citems[5]
|
||||||
|
addrattrs['type'] = 'inet6'
|
||||||
|
linkout[ifname]['addrs'][citems[3]] = addrattrs
|
||||||
|
else:
|
||||||
|
linkattrs = {}
|
||||||
|
linkattrs['addrs'] = OrderedDict({})
|
||||||
|
try:
|
||||||
|
linkout[ifname].update(linkattrs)
|
||||||
|
except KeyError:
|
||||||
|
linkout[ifname] = linkattrs
|
||||||
|
|
||||||
|
[linkCache.update_attrdict([ifname], linkattrs)
|
||||||
|
for ifname, linkattrs in linkout.items()]
|
||||||
|
|
||||||
|
def _cache_get(self, type, attrlist, refresh=False):
|
||||||
|
try:
|
||||||
|
if self.DRYRUN:
|
||||||
|
return False
|
||||||
|
if self.CACHE:
|
||||||
|
if not iproute2._cache_fill_done:
|
||||||
|
self._link_fill()
|
||||||
|
self._addr_fill()
|
||||||
|
iproute2._cache_fill_done = True
|
||||||
|
return linkCache.get_attr(attrlist)
|
||||||
|
if not refresh:
|
||||||
|
return linkCache.get_attr(attrlist)
|
||||||
|
if type == 'link':
|
||||||
|
self._link_fill(attrlist[0], refresh)
|
||||||
|
elif type == 'addr':
|
||||||
|
self._addr_fill(attrlist[0], refresh)
|
||||||
|
else:
|
||||||
|
self._link_fill(attrlist[0], refresh)
|
||||||
|
self._addr_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, type, attrlist, value, refresh=False):
|
||||||
|
try:
|
||||||
|
attrvalue = self._cache_get(type, 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 self.DRYRUN: return
|
||||||
|
try:
|
||||||
|
linkCache.add_attr(attrlist, value)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _cache_delete(self, attrlist):
|
||||||
|
if self.DRYRUN: return
|
||||||
|
try:
|
||||||
|
linkCache.del_attr(attrlist)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _cache_invalidate(self):
|
||||||
|
linkCache.invalidate()
|
||||||
|
|
||||||
|
def batch_start(self):
|
||||||
|
self.ipbatcbuf = ''
|
||||||
|
self.ipbatch = True
|
||||||
|
self.ipbatch_pause = False
|
||||||
|
|
||||||
|
def add_to_batch(self, cmd):
|
||||||
|
self.ipbatchbuf += cmd + '\n'
|
||||||
|
|
||||||
|
def batch_pause(self):
|
||||||
|
self.ipbatch_pause = True
|
||||||
|
|
||||||
|
def batch_resume(self):
|
||||||
|
self.ipbatch_pause = False
|
||||||
|
|
||||||
|
def batch_commit(self):
|
||||||
|
if not self.ipbatchbuf:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.exec_command_talk_stdin('ip -force -batch -',
|
||||||
|
stdinbuf=self.ipbatchbuf)
|
||||||
|
except Exception:
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
self.ipbatchbuf = ''
|
||||||
|
self.ipbatch = False
|
||||||
|
self.ipbatch_pause = False
|
||||||
|
|
||||||
|
def addr_show(self, ifacename=None):
|
||||||
|
if ifacename:
|
||||||
|
if not self.link_exists(ifacename):
|
||||||
|
return
|
||||||
|
return self.exec_commandl(['ip','-o', 'addr', 'show', 'dev',
|
||||||
|
'%s' %ifacename])
|
||||||
|
else:
|
||||||
|
return self.exec_commandl(['ip', '-o', 'addr', 'show'])
|
||||||
|
|
||||||
|
def link_show(self, ifacename=None):
|
||||||
|
if ifacename:
|
||||||
|
return self.exec_commandl(['ip', '-o', '-d', 'link',
|
||||||
|
'show', 'dev', '%s' %ifacename])
|
||||||
|
else:
|
||||||
|
return self.exec_commandl(['ip', '-o', '-d', 'link', 'show'])
|
||||||
|
|
||||||
|
def addr_add(self, ifacename, address, broadcast=None,
|
||||||
|
peer=None, scope=None, preferred_lifetime=None):
|
||||||
|
if not address:
|
||||||
|
return
|
||||||
|
cmd = 'addr add %s' %address
|
||||||
|
if broadcast:
|
||||||
|
cmd += ' broadcast %s' %broadcast
|
||||||
|
if peer:
|
||||||
|
cmd += ' peer %s' %peer
|
||||||
|
if scope:
|
||||||
|
cmd += ' scope %s' %scope
|
||||||
|
if preferred_lifetime:
|
||||||
|
cmd += ' preferred_lft %s' %preferred_lifetime
|
||||||
|
cmd += ' dev %s' %ifacename
|
||||||
|
if self.ipbatch and not self.ipbatch_pause:
|
||||||
|
self.add_to_batch(cmd)
|
||||||
|
else:
|
||||||
|
self.exec_command('ip ' + cmd)
|
||||||
|
self._cache_update([ifacename, 'addrs', address], {})
|
||||||
|
|
||||||
|
def addr_del(self, ifacename, address, broadcast=None,
|
||||||
|
peer=None, scope=None):
|
||||||
|
""" Delete ipv4 address """
|
||||||
|
if not address:
|
||||||
|
return
|
||||||
|
if not self._cache_get('addr', [ifacename, 'addrs', address]):
|
||||||
|
return
|
||||||
|
cmd = 'addr del %s' %address
|
||||||
|
if broadcast:
|
||||||
|
cmd += 'broadcast %s' %broadcast
|
||||||
|
if peer:
|
||||||
|
cmd += 'peer %s' %peer
|
||||||
|
if scope:
|
||||||
|
cmd += 'scope %s' %scope
|
||||||
|
cmd += ' dev %s' %ifacename
|
||||||
|
self.exec_command('ip ' + cmd)
|
||||||
|
self._cache_delete([ifacename, 'addrs', address])
|
||||||
|
|
||||||
|
def addr_flush(self, ifacename):
|
||||||
|
cmd = 'addr flush dev %s' %ifacename
|
||||||
|
if self.ipbatch and not self.ipbatch_pause:
|
||||||
|
self.add_to_batch(cmd)
|
||||||
|
else:
|
||||||
|
self.exec_command('ip ' + cmd)
|
||||||
|
self._cache_delete([ifacename, 'addrs'])
|
||||||
|
|
||||||
|
def del_addr_all(self, ifacename, skip_addrs=[]):
|
||||||
|
if not skip_addrs: skip_addrs = []
|
||||||
|
runningaddrsdict = self.addr_get(ifacename)
|
||||||
|
try:
|
||||||
|
# XXX: ignore errors. Fix this to delete secondary addresses
|
||||||
|
# first
|
||||||
|
[self.addr_del(ifacename, a) for a in
|
||||||
|
set(runningaddrsdict.keys()).difference(skip_addrs)]
|
||||||
|
except:
|
||||||
|
# ignore errors
|
||||||
|
pass
|
||||||
|
|
||||||
|
def addr_get(self, ifacename, details=True):
|
||||||
|
addrs = self._cache_get('addr', [ifacename, 'addrs'])
|
||||||
|
if not addrs:
|
||||||
|
return None
|
||||||
|
if details:
|
||||||
|
return addrs
|
||||||
|
return addrs.keys()
|
||||||
|
|
||||||
|
def addr_add_multiple(self, ifacename, addrs, purge_existing=False):
|
||||||
|
# purges address
|
||||||
|
if purge_existing:
|
||||||
|
# if perfmode is not set and also if iface has no sibling
|
||||||
|
# objects, purge addresses that are not present in the new
|
||||||
|
# config
|
||||||
|
runningaddrs = self.addr_get(ifacename, details=False)
|
||||||
|
if addrs == runningaddrs:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
# if primary address is not same, there is no need to keep any.
|
||||||
|
# reset all addresses
|
||||||
|
if (addrs and runningaddrs and
|
||||||
|
(addrs[0] != runningaddrs[0])):
|
||||||
|
self.del_addr_all(ifacename)
|
||||||
|
else:
|
||||||
|
self.del_addr_all(ifacename, addrs)
|
||||||
|
except Exception, e:
|
||||||
|
self.log_warn(str(e))
|
||||||
|
for a in addrs:
|
||||||
|
try:
|
||||||
|
self.addr_add(ifacename, a)
|
||||||
|
except Exception, e:
|
||||||
|
self.logger.error(str(e))
|
||||||
|
|
||||||
|
def _link_set_ifflag(self, ifacename, value):
|
||||||
|
# Dont look at the cache, the cache may have stale value
|
||||||
|
# because link status can be changed by external
|
||||||
|
# entity (One such entity is ifupdown main program)
|
||||||
|
cmd = 'link set dev %s %s' %(ifacename, value.lower())
|
||||||
|
if self.ipbatch:
|
||||||
|
self.add_to_batch(cmd)
|
||||||
|
else:
|
||||||
|
self.exec_command('ip ' + cmd)
|
||||||
|
|
||||||
|
def link_up(self, ifacename):
|
||||||
|
self._link_set_ifflag(ifacename, 'UP')
|
||||||
|
|
||||||
|
def link_down(self, ifacename):
|
||||||
|
self._link_set_ifflag(ifacename, 'DOWN')
|
||||||
|
|
||||||
|
def link_set(self, ifacename, key, value=None, force=False):
|
||||||
|
if not force:
|
||||||
|
if (key not in ['master', 'nomaster'] and
|
||||||
|
self._cache_check('link', [ifacename, key], value)):
|
||||||
|
return
|
||||||
|
cmd = 'link set dev %s %s' %(ifacename, key)
|
||||||
|
if value:
|
||||||
|
cmd += ' %s' %value
|
||||||
|
if self.ipbatch:
|
||||||
|
self.add_to_batch(cmd)
|
||||||
|
else:
|
||||||
|
self.exec_command('ip ' + cmd)
|
||||||
|
if key not in ['master', 'nomaster']:
|
||||||
|
self._cache_update([ifacename, key], value)
|
||||||
|
|
||||||
|
def link_set_hwaddress(self, ifacename, hwaddress, force=False):
|
||||||
|
if not force:
|
||||||
|
if self._cache_check('link', [ifacename, 'hwaddress'], hwaddress):
|
||||||
|
return
|
||||||
|
self.link_down(ifacename)
|
||||||
|
cmd = 'link set dev %s address %s' %(ifacename, hwaddress)
|
||||||
|
if self.ipbatch:
|
||||||
|
self.add_to_batch(cmd)
|
||||||
|
else:
|
||||||
|
self.exec_command('ip ' + cmd)
|
||||||
|
self.link_up(ifacename)
|
||||||
|
self._cache_update([ifacename, 'hwaddress'], hwaddress)
|
||||||
|
|
||||||
|
def link_set_alias(self, ifacename, alias):
|
||||||
|
self.exec_commandl(['ip', 'link', 'set', 'dev',
|
||||||
|
ifacename, 'alias', alias])
|
||||||
|
|
||||||
|
def link_get_alias(self, ifacename):
|
||||||
|
return self.read_file_oneline('/sys/class/net/%s/ifalias'
|
||||||
|
%ifacename)
|
||||||
|
|
||||||
|
def link_isloopback(self, ifacename):
|
||||||
|
flags = self._cache_get('link', [ifacename, 'flags'])
|
||||||
|
if not flags:
|
||||||
|
return
|
||||||
|
if 'LOOPBACK' in flags:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def link_get_status(self, ifacename):
|
||||||
|
return self._cache_get('link', [ifacename, 'ifflag'], refresh=True)
|
||||||
|
|
||||||
|
def route_add_gateway(self, ifacename, gateway, metric=None):
|
||||||
|
if not gateway:
|
||||||
|
return
|
||||||
|
cmd = 'ip route add default via %s' %gateway
|
||||||
|
# Add metric
|
||||||
|
if metric:
|
||||||
|
cmd += 'metric %s' %metric
|
||||||
|
cmd += ' dev %s' %ifacename
|
||||||
|
self.exec_command(cmd)
|
||||||
|
|
||||||
|
def route_del_gateway(self, ifacename, gateway, metric=None):
|
||||||
|
# delete default gw
|
||||||
|
if not gateway:
|
||||||
|
return
|
||||||
|
cmd = 'ip route del default via %s' %gateway
|
||||||
|
if metric:
|
||||||
|
cmd += ' metric %s' %metric
|
||||||
|
cmd += ' dev %s' %ifacename
|
||||||
|
self.exec_command(cmd)
|
||||||
|
|
||||||
|
def route6_add_gateway(self, ifacename, gateway):
|
||||||
|
if not gateway:
|
||||||
|
return
|
||||||
|
return self.exec_command('ip -6 route add default via %s' %gateway +
|
||||||
|
' dev %s' %ifacename)
|
||||||
|
|
||||||
|
def route6_del_gateway(self, ifacename, gateway):
|
||||||
|
if not gateway:
|
||||||
|
return
|
||||||
|
return self.exec_command('ip -6 route del default via %s' %gateway +
|
||||||
|
'dev %s' %ifacename)
|
||||||
|
|
||||||
|
def link_create_vlan(self, vlan_device_name, vlan_raw_device, vlanid):
|
||||||
|
if self.link_exists(vlan_device_name):
|
||||||
|
return
|
||||||
|
self.exec_command('ip link add link %s' %vlan_raw_device +
|
||||||
|
' name %s' %vlan_device_name +
|
||||||
|
' type vlan id %d' %vlanid)
|
||||||
|
self._cache_update([vlan_device_name], {})
|
||||||
|
|
||||||
|
def link_create_vlan_from_name(self, vlan_device_name):
|
||||||
|
v = vlan_device_name.split('.')
|
||||||
|
if len(v) != 2:
|
||||||
|
self.logger.warn('invalid vlan device name %s' %vlan_device_name)
|
||||||
|
return
|
||||||
|
self.link_create_vlan(vlan_device_name, v[0], v[1])
|
||||||
|
|
||||||
|
def link_create_macvlan(self, name, linkdev, mode='private'):
|
||||||
|
if self.link_exists(name):
|
||||||
|
return
|
||||||
|
cmd = ('link add link %s' %linkdev +
|
||||||
|
' name %s' %name +
|
||||||
|
' type macvlan mode %s' %mode)
|
||||||
|
if self.ipbatch and not self.ipbatch_pause:
|
||||||
|
self.add_to_batch(cmd)
|
||||||
|
else:
|
||||||
|
self.exec_command('ip %s' %cmd)
|
||||||
|
self._cache_update([name], {})
|
||||||
|
|
||||||
|
def link_create_vxlan(self, name, vxlanid,
|
||||||
|
localtunnelip=None,
|
||||||
|
svcnodeips=None,
|
||||||
|
peernodeips=None,
|
||||||
|
learning='off'):
|
||||||
|
if svcnodeips and peernodeips:
|
||||||
|
raise Exception("svcnodeip and peernodeip is mutually exclusive")
|
||||||
|
args = ''
|
||||||
|
if localtunnelip:
|
||||||
|
args += ' local %s' %localtunnelip
|
||||||
|
if svcnodeips:
|
||||||
|
for s in svcnodeips:
|
||||||
|
args += ' svcnode %s' %s
|
||||||
|
if peernodeips:
|
||||||
|
for s in peernodeips:
|
||||||
|
args += ' peernode %s' %s
|
||||||
|
if learning == 'on':
|
||||||
|
args += ' learning'
|
||||||
|
|
||||||
|
if self.link_exists(name):
|
||||||
|
cmd = 'link set dev %s type vxlan ' %(name)
|
||||||
|
else:
|
||||||
|
cmd = 'link add dev %s type vxlan id %s' %(name, vxlanid)
|
||||||
|
cmd += args
|
||||||
|
|
||||||
|
if self.ipbatch and not self.ipbatch_pause:
|
||||||
|
self.add_to_batch(cmd)
|
||||||
|
else:
|
||||||
|
self.exec_command('ip %s' %cmd)
|
||||||
|
# XXX: update linkinfo correctly
|
||||||
|
self._cache_update([name], {})
|
||||||
|
|
||||||
|
def link_exists(self, ifacename):
|
||||||
|
return os.path.exists('/sys/class/net/%s' %ifacename)
|
||||||
|
|
||||||
|
def is_vlan_device_by_name(self, ifacename):
|
||||||
|
if re.search(r'\.', ifacename):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def route_add(self, route):
|
||||||
|
self.exec_command('ip route add ' + route)
|
||||||
|
|
||||||
|
def route6_add(self, route):
|
||||||
|
self.exec_command('ip -6 route add ' + route)
|
||||||
|
|
||||||
|
def get_vlandev_attrs(self, ifacename):
|
||||||
|
return (self._cache_get('link', [ifacename, 'linkinfo', 'link']),
|
||||||
|
self._cache_get('link', [ifacename, 'linkinfo', 'vlanid']))
|
||||||
|
|
||||||
|
def link_get_mtu(self, ifacename):
|
||||||
|
return self._cache_get('link', [ifacename, 'mtu'])
|
||||||
|
|
||||||
|
def link_get_hwaddress(self, ifacename):
|
||||||
|
return self._cache_get('link', [ifacename, 'hwaddress'])
|
||||||
|
|
||||||
|
def link_create(self, ifacename, type, link=None):
|
||||||
|
if self.link_exists(ifacename):
|
||||||
|
return
|
||||||
|
cmd = 'link add'
|
||||||
|
if link:
|
||||||
|
cmd += ' link %s' %link
|
||||||
|
cmd += ' name %s type %s' %(ifacename, type)
|
||||||
|
if self.ipbatch and not self.ipbatch_pause:
|
||||||
|
self.add_to_batch(cmd)
|
||||||
|
else:
|
||||||
|
self.exec_command('ip %s' %cmd)
|
||||||
|
self._cache_update([ifacename], {})
|
||||||
|
|
||||||
|
def link_delete(self, ifacename):
|
||||||
|
if not self.link_exists(ifacename):
|
||||||
|
return
|
||||||
|
cmd = 'link del %s' %ifacename
|
||||||
|
if self.ipbatch and not self.ipbatch_pause:
|
||||||
|
self.add_to_batch(cmd)
|
||||||
|
else:
|
||||||
|
self.exec_command('ip %s' %cmd)
|
||||||
|
self._cache_invalidate()
|
||||||
|
|
||||||
|
def bridge_port_vids_add(self, bridgeportname, vids):
|
||||||
|
[self.exec_command('bridge vlan add vid %s dev %s'
|
||||||
|
%(v, bridgeportname)) for v in vids]
|
||||||
|
|
||||||
|
def bridge_port_vids_del(self, bridgeportname, vids):
|
||||||
|
if not vids:
|
||||||
|
return
|
||||||
|
[self.exec_command('bridge vlan del vid %s dev %s'
|
||||||
|
%(v, bridgeportname)) for v in vids]
|
||||||
|
|
||||||
|
def bridge_port_vids_flush(self, bridgeportname):
|
||||||
|
self.exec_command('bridge vlan del vid %s dev %s'
|
||||||
|
%(vid, bridgeportname))
|
||||||
|
|
||||||
|
def bridge_port_vids_get(self, bridgeportname):
|
||||||
|
self.exec_command('/bin/bridge vlan show %s' %bridgeportname)
|
||||||
|
bridgeout = self.exec_command('/bin/bridge vlan show dev %s'
|
||||||
|
%bridgeportname)
|
||||||
|
if not bridgeout: return []
|
||||||
|
brvlanlines = bridgeout.readlines()[2:]
|
||||||
|
vids = [l.strip() for l in brvlanlines]
|
||||||
|
return [vid for v in vids if vid]
|
||||||
|
|
||||||
|
def bridge_port_vids_get_all(self):
|
||||||
|
brvlaninfo = {}
|
||||||
|
bridgeout = self.exec_command('/bin/bridge vlan show')
|
||||||
|
if not bridgeout: return brvlaninfo
|
||||||
|
brvlanlines = bridgeout.splitlines()
|
||||||
|
brportname=None
|
||||||
|
for l in brvlanlines[1:]:
|
||||||
|
if l and l[0] not in [' ', '\t']:
|
||||||
|
brportname = None
|
||||||
|
l=l.strip()
|
||||||
|
if not l:
|
||||||
|
brportname=None
|
||||||
|
continue
|
||||||
|
if 'PVID' in l:
|
||||||
|
attrs = l.split()
|
||||||
|
brportname = attrs[0]
|
||||||
|
brvlaninfo[brportname] = {'pvid' : attrs[1],
|
||||||
|
'vlan' : []}
|
||||||
|
elif brportname:
|
||||||
|
if 'Egress Untagged' not in l:
|
||||||
|
brvlaninfo[brportname]['vlan'].append(l)
|
||||||
|
elif not brportname:
|
||||||
|
attrs = l.split()
|
||||||
|
if attrs[1] == 'None' or 'Egress Untagged' in attrs[1]:
|
||||||
|
continue
|
||||||
|
brportname = attrs[0]
|
||||||
|
brvlaninfo[brportname] = {'vlan' : [attrs[1]]}
|
||||||
|
return brvlaninfo
|
||||||
|
|
||||||
|
def bridge_port_pvid_add(self, bridgeportname, pvid):
|
||||||
|
self.exec_command('bridge vlan add vid %s untagged pvid dev %s'
|
||||||
|
%(pvid, bridgeportname))
|
||||||
|
|
||||||
|
def bridge_port_pvid_del(self, bridgeportname, pvid):
|
||||||
|
self.exec_command('bridge vlan del vid %s untagged pvid dev %s'
|
||||||
|
%(pvid, bridgeportname))
|
||||||
|
|
||||||
|
def bridge_port_pvids_get(self, bridgeportname):
|
||||||
|
return self.read_file_oneline('/sys/class/net/%s/brport/pvid'
|
||||||
|
%bridgeportname)
|
||||||
|
|
||||||
|
def bridge_vids_add(self, bridgeportname, vids, bridge=True):
|
||||||
|
target = 'self' if bridge else ''
|
||||||
|
[self.exec_command('bridge vlan add vid %s dev %s %s'
|
||||||
|
%(v, bridgeportname, target)) for v in vids]
|
||||||
|
|
||||||
|
def bridge_vids_del(self, bridgeportname, vids, bridge=True):
|
||||||
|
target = 'self' if bridge else ''
|
||||||
|
[self.exec_command('bridge vlan del vid %s dev %s %s'
|
||||||
|
%(v, bridgeportname, target)) for v in vids]
|
||||||
|
|
||||||
|
def bridge_is_vlan_aware(self, bridgename):
|
||||||
|
filename = '/sys/class/net/%s/bridge/vlan_filtering' %bridgename
|
||||||
|
if os.path.exists(filename) and self.read_file_oneline(filename) == '1':
|
||||||
|
return True
|
||||||
|
return False
|
318
ifupdownaddons/modulebase.py
Normal file
318
ifupdownaddons/modulebase.py
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
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.ifenslaveutil 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 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 or swp[1-10])')
|
||||||
|
start_index = 0
|
||||||
|
end_index = 0
|
||||||
|
try:
|
||||||
|
regexs = [re.compile(r"([A-Za-z0-9]+[A-Za-z])(\d+)\-(\d+)(.*)"),
|
||||||
|
re.compile(r"([A-Za-z0-9]+)\[(\d+)\-(\d+)\](.*)")]
|
||||||
|
for r in regexs:
|
||||||
|
m = r.match(expr)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
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])
|
||||||
|
except:
|
||||||
|
self.logger.warn(errmsg)
|
||||||
|
pass
|
||||||
|
if not start_index and not end_index:
|
||||||
|
self.logger.warn(errmsg)
|
||||||
|
yield expr
|
||||||
|
else:
|
||||||
|
for i in range(start_index, end_index + 1):
|
||||||
|
yield prefix + '%d' %i + suffix
|
||||||
|
|
||||||
|
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 == '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)
|
171
ifupdownaddons/mstpctlutil.py
Normal file
171
ifupdownaddons/mstpctlutil.py
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
from utilsbase import *
|
||||||
|
from ifupdown.iface import *
|
||||||
|
from cache import *
|
||||||
|
import re
|
||||||
|
|
||||||
|
class mstpctlutil(utilsBase):
|
||||||
|
""" This class contains helper methods to interact with mstpd using
|
||||||
|
mstputils commands """
|
||||||
|
|
||||||
|
_cache_fill_done = False
|
||||||
|
|
||||||
|
_bridgeattrmap = {'bridgeid' : 'bridge-id',
|
||||||
|
'maxage' : 'max-age',
|
||||||
|
'fdelay' : 'forward-delay',
|
||||||
|
'txholdcount' : 'tx-hold-count',
|
||||||
|
'maxhops' : 'max-hops',
|
||||||
|
'ageing' : 'ageing-time',
|
||||||
|
'hello' : 'hello-time',
|
||||||
|
'forcevers' : 'force-protocol-version'}
|
||||||
|
|
||||||
|
_bridgeportattrmap = {'portadminedge' : 'admin-edge-port',
|
||||||
|
'portp2p' : 'point-to-point',
|
||||||
|
'portrestrrole' : 'restricted-role',
|
||||||
|
'portrestrtcn' : 'restricted-TCN',
|
||||||
|
'bpduguard' : 'bpdu-guard-port',
|
||||||
|
'portautoedge' : 'auto-edge-port',
|
||||||
|
'portnetwork' : 'network-port',
|
||||||
|
'portbpdufilter' : 'bpdufilter-port'}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
utilsBase.__init__(self, *args, **kargs)
|
||||||
|
|
||||||
|
def is_mstpd_running(self):
|
||||||
|
try:
|
||||||
|
self.exec_command('/bin/pidof mstpd')
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_bridgeport_attr(self, bridgename, portname, attrname):
|
||||||
|
try:
|
||||||
|
return self.subprocess_check_output(['/sbin/mstpctl',
|
||||||
|
'showportdetail', '%s' %bridgename, '%s' %portname,
|
||||||
|
self._bridgeportattrmap[attrname]]).strip('\n')
|
||||||
|
except Exception, e:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_bridgeport_attrs(self, bridgename, portname):
|
||||||
|
bridgeattrs = {}
|
||||||
|
try:
|
||||||
|
bridgeattrs = dict((k, self.get_bridgeport_attr(bridgename, v))
|
||||||
|
for k, v in self._bridgeattrmap.items())
|
||||||
|
bridgeattrs['treeprio'] = int(bridgeattrs.get('bridgeid',
|
||||||
|
'').split('.')[0], base=16) * 4096
|
||||||
|
except Exception, e:
|
||||||
|
self.logger.warn(str(e))
|
||||||
|
pass
|
||||||
|
return bridgeattrs
|
||||||
|
|
||||||
|
def set_bridgeport_attrs(self, bridgename, bridgeportname, attrdict,
|
||||||
|
check=True):
|
||||||
|
for k, v in attrdict.iteritems():
|
||||||
|
if not v:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
self.set_bridgeport_attr(self, bridgename, bridgeportname,
|
||||||
|
k, v, check)
|
||||||
|
except Exception, e:
|
||||||
|
self.logger.warn(str(e))
|
||||||
|
|
||||||
|
def set_bridgeport_attr(self, bridgename, bridgeportname, attrname,
|
||||||
|
attrvalue, check=True):
|
||||||
|
if check:
|
||||||
|
attrvalue_curr = self.get_bridgeport_attr(bridgename,
|
||||||
|
bridgeportname, attrname)
|
||||||
|
if attrvalue_curr and attrvalue_curr == attrvalue:
|
||||||
|
return
|
||||||
|
if attrname == 'treeportcost' or attrname == 'treeportprio':
|
||||||
|
self.subprocess_check_output(['/sbin/mstpctl', 'set%s' %attrname,
|
||||||
|
'%s' %bridgename, '%s' %bridgeportname, '0', '%s' %attrvalue])
|
||||||
|
else:
|
||||||
|
self.subprocess_check_output(['/sbin/mstpctl', 'set%s' %attrname,
|
||||||
|
'%s' %bridgename, '%s' %bridgeportname, '%s' %attrvalue])
|
||||||
|
|
||||||
|
def get_bridge_attrs(self, bridgename):
|
||||||
|
bridgeattrs = {}
|
||||||
|
try:
|
||||||
|
bridgeattrs = dict((k, self.get_bridge_attr(bridgename, k))
|
||||||
|
for k in self._bridgeattrmap.keys())
|
||||||
|
bridgeattrs['treeprio'] = '%d' %(int(bridgeattrs.get('bridgeid',
|
||||||
|
'').split('.')[0], base=16) * 4096)
|
||||||
|
del bridgeattrs['bridgeid']
|
||||||
|
except Exception, e:
|
||||||
|
self.logger.info(bridgeattrs)
|
||||||
|
self.logger.warn(str(e))
|
||||||
|
pass
|
||||||
|
return bridgeattrs
|
||||||
|
|
||||||
|
def get_bridge_attr(self, bridgename, attrname):
|
||||||
|
try:
|
||||||
|
return self.subprocess_check_output(['/sbin/mstpctl',
|
||||||
|
'showbridge', '%s' %bridgename,
|
||||||
|
self._bridgeattrmap[attrname]]).strip('\n')
|
||||||
|
except Exception, e:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_bridge_attr(self, bridgename, attrname, attrvalue, check=True):
|
||||||
|
|
||||||
|
if check:
|
||||||
|
attrvalue_curr = self.get_bridge_attr(bridgename, attrname)
|
||||||
|
if attrvalue_curr and attrvalue_curr == attrvalue:
|
||||||
|
return
|
||||||
|
if attrname == 'treeprio':
|
||||||
|
self.subprocess_check_call(['/sbin/mstpctl', 'set%s' %attrname,
|
||||||
|
'%s' %bridgename, '0', '%s' %attrvalue])
|
||||||
|
else:
|
||||||
|
self.subprocess_check_call(['/sbin/mstpctl', 'set%s' %attrname,
|
||||||
|
'%s' %bridgename, '%s' %attrvalue])
|
||||||
|
|
||||||
|
def set_bridge_attrs(self, bridgename, attrdict, check=True):
|
||||||
|
for k, v in attrdict.iteritems():
|
||||||
|
if not v:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
self.set_bridge_attr(bridgename, k, v, check)
|
||||||
|
except Exception, e:
|
||||||
|
self.logger.warn('%s: %s' %(bridgename, str(e)))
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_bridge_treeprio(self, bridgename):
|
||||||
|
try:
|
||||||
|
bridgeid = subprocess.check_output(['/sbin/mstpctl',
|
||||||
|
'showbridge', '%s' %bridgename,
|
||||||
|
self._bridgeattrmap['bridgeid']]).strip('\n')
|
||||||
|
return '%d' %(int(bridgeid.split('.')[0], base=16) * 4096)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_bridge_treeprio(self, bridgename, attrvalue, check=True):
|
||||||
|
if check:
|
||||||
|
attrvalue_curr = self.get_bridge_treeprio(bridgename)
|
||||||
|
if attrvalue_curr and attrvalue_curr == attrvalue:
|
||||||
|
return
|
||||||
|
self.subprocess_check_output(['/sbin/mstpctl', 'settreeprio',
|
||||||
|
'%s' %bridgename, '0', '%s' %attrvalue])
|
||||||
|
|
||||||
|
def showbridge(self, bridgename=None):
|
||||||
|
if bridgename:
|
||||||
|
return self.exec_command('/sbin/mstpctl showbridge %s' %bridgename)
|
||||||
|
else:
|
||||||
|
return self.exec_command('/sbin/mstpctl showbridge')
|
||||||
|
|
||||||
|
def showportdetail(self, bridgename):
|
||||||
|
return self.exec_command('/sbin/mstpctl showportdetail %s' %bridgename)
|
||||||
|
|
||||||
|
def mstpbridge_exists(self, bridgename):
|
||||||
|
try:
|
||||||
|
subprocess.check_call('mstpctl showbridge %s' %bridgename)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
163
ifupdownaddons/utilsbase.py
Normal file
163
ifupdownaddons/utilsbase.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
||||||
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
||||||
|
#
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
import io
|
||||||
|
from ifupdown.iface import *
|
||||||
|
from cache import *
|
||||||
|
|
||||||
|
#import timeit
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
|
def profile(func):
|
||||||
|
def wrap(*args, **kwargs):
|
||||||
|
started_at = time.time()
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
print str(func)
|
||||||
|
print (time.time() - started_at)
|
||||||
|
return result
|
||||||
|
return wrap
|
||||||
|
|
||||||
|
class utilsBase(object):
|
||||||
|
""" Base class for ifupdown addon utilities """
|
||||||
|
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
modulename = self.__class__.__name__
|
||||||
|
self.logger = logging.getLogger('ifupdown.' + modulename)
|
||||||
|
self.FORCE = kargs.get('force', False)
|
||||||
|
self.DRYRUN = kargs.get('dryrun', False)
|
||||||
|
self.NOWAIT = kargs.get('nowait', False)
|
||||||
|
self.PERFMODE = kargs.get('perfmode', False)
|
||||||
|
self.CACHE = kargs.get('cache', False)
|
||||||
|
|
||||||
|
def exec_commandl(self, cmdl, cmdenv=None):
|
||||||
|
""" Executes command """
|
||||||
|
|
||||||
|
cmd_returncode = 0
|
||||||
|
cmdout = ''
|
||||||
|
try:
|
||||||
|
self.logger.info('executing ' + ' '.join(cmdl))
|
||||||
|
if self.DRYRUN:
|
||||||
|
return cmdout
|
||||||
|
ch = subprocess.Popen(cmdl,
|
||||||
|
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('failed to execute cmd \'%s\' (%s)'
|
||||||
|
%(' '.join(cmdl), str(e)))
|
||||||
|
if cmd_returncode != 0:
|
||||||
|
raise Exception('failed to execute cmd \'%s\''
|
||||||
|
%' '.join(cmdl) + '(' + cmdout.strip('\n ') + ')')
|
||||||
|
return cmdout
|
||||||
|
|
||||||
|
def exec_command(self, cmd, cmdenv=None):
|
||||||
|
""" Executes command given as string in the argument cmd """
|
||||||
|
|
||||||
|
return self.exec_commandl(cmd.split(), cmdenv)
|
||||||
|
|
||||||
|
def exec_command_talk_stdin(self, cmd, stdinbuf):
|
||||||
|
""" Executes command and writes to stdin of the process """
|
||||||
|
cmd_returncode = 0
|
||||||
|
cmdout = ''
|
||||||
|
try:
|
||||||
|
self.logger.info('executing %s [%s]' %(cmd, stdinbuf))
|
||||||
|
if self.DRYRUN:
|
||||||
|
return cmdout
|
||||||
|
ch = subprocess.Popen(cmd.split(),
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
shell=False,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
close_fds=True)
|
||||||
|
cmdout = ch.communicate(input=stdinbuf)[0]
|
||||||
|
cmd_returncode = ch.wait()
|
||||||
|
except OSError, e:
|
||||||
|
raise Exception('failed to execute cmd \'%s\' (%s)'
|
||||||
|
%(cmd, str(e)))
|
||||||
|
if cmd_returncode != 0:
|
||||||
|
raise Exception('failed to execute cmd \'%s [%s]\''
|
||||||
|
%(cmd, stdinbuf) + '(' + cmdout.strip('\n ') + ')')
|
||||||
|
return cmdout
|
||||||
|
|
||||||
|
def subprocess_check_output(self, cmdl):
|
||||||
|
self.logger.info('executing ' + ' '.join(cmdl))
|
||||||
|
if self.DRYRUN:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
return subprocess.check_output(cmdl, stderr=subprocess.STDOUT)
|
||||||
|
except Exception, e:
|
||||||
|
raise Exception('failed to execute cmd \'%s\' (%s)'
|
||||||
|
%(' '.join(cmdl), e.output))
|
||||||
|
|
||||||
|
def subprocess_check_call(self, cmdl):
|
||||||
|
""" subprocess check_call implementation using popen
|
||||||
|
|
||||||
|
Uses popen because it needs the close_fds argument
|
||||||
|
"""
|
||||||
|
|
||||||
|
cmd_returncode = 0
|
||||||
|
try:
|
||||||
|
self.logger.info('executing ' + ' '.join(cmdl))
|
||||||
|
if self.DRYRUN:
|
||||||
|
return
|
||||||
|
ch = subprocess.Popen(cmdl,
|
||||||
|
stdout=None,
|
||||||
|
shell=False,
|
||||||
|
stderr=None,
|
||||||
|
close_fds=True)
|
||||||
|
cmd_returncode = ch.wait()
|
||||||
|
except Exception, e:
|
||||||
|
raise Exception('failed to execute cmd \'%s\' (%s)'
|
||||||
|
%(' '.join(cmdl), str(e)))
|
||||||
|
if cmd_returncode != 0:
|
||||||
|
raise Exception('failed to execute cmd \'%s\''
|
||||||
|
%' '.join(cmdl))
|
||||||
|
return
|
||||||
|
|
||||||
|
def write_file(self, filename, strexpr):
|
||||||
|
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):
|
||||||
|
try:
|
||||||
|
self.logger.debug('reading \'%s\'' %filename)
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
return f.readlines()
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
return None
|
||||||
|
|
||||||
|
def read_file_oneline(self, filename):
|
||||||
|
try:
|
||||||
|
self.logger.debug('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):
|
||||||
|
self.exec_command('sysctl %s=' %variable + '%s' %value)
|
||||||
|
|
||||||
|
def sysctl_get(self, variable):
|
||||||
|
return self.exec_command('sysctl %s' %variable).split('=')[1].strip()
|
1079
man.rst/ifupdown-addons-interfaces.5.rst
Normal file
1079
man.rst/ifupdown-addons-interfaces.5.rst
Normal file
File diff suppressed because it is too large
Load Diff
350
sbin/ifaddon
Executable file
350
sbin/ifaddon
Executable file
@ -0,0 +1,350 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import argparse
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
lockfile="/run/network/.lock"
|
||||||
|
modules_configfile='/var/lib/ifupdownaddons/addons.conf'
|
||||||
|
modules_dir='/usr/share/ifupdownaddons'
|
||||||
|
|
||||||
|
addon_config = OrderedDict([('pre-up', []),
|
||||||
|
('up', []),
|
||||||
|
('post-up', []),
|
||||||
|
('pre-down', []),
|
||||||
|
('down', []),
|
||||||
|
('post-down', [])])
|
||||||
|
|
||||||
|
def read_modules_config():
|
||||||
|
with open(modules_configfile, 'r') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
for l in lines:
|
||||||
|
litems = l.rstrip(' \n').split(',')
|
||||||
|
operation = litems[0]
|
||||||
|
mname = litems[1]
|
||||||
|
addon_config[operation].append(mname)
|
||||||
|
|
||||||
|
def man_rst_header():
|
||||||
|
print '=========================='
|
||||||
|
print 'ifupdown-addons-interfaces'
|
||||||
|
print '=========================='
|
||||||
|
|
||||||
|
print '---------------------------------------------------------'
|
||||||
|
print 'ifupdown2 addon modules interface configuration'
|
||||||
|
print '---------------------------------------------------------'
|
||||||
|
|
||||||
|
print ':Author: roopa@cumulusnetworks.com'
|
||||||
|
print ':Date: 2013-09-25'
|
||||||
|
print ':Copyright: Copyright 2013 Cumulus Networks, Inc. All rights reserved.'
|
||||||
|
print ':Version: 0.1'
|
||||||
|
print ':Manual section: 5'
|
||||||
|
print '\n'
|
||||||
|
|
||||||
|
def man_rst_body():
|
||||||
|
|
||||||
|
print 'DESCRIPTION'
|
||||||
|
print '==========='
|
||||||
|
|
||||||
|
print (''' ifupdown2 addon modules add incremental functionality to
|
||||||
|
core ifupdown2 tool.
|
||||||
|
|
||||||
|
All installed addon modules are executed on every interface
|
||||||
|
listed in the interfaces file. Addon modules are installed under
|
||||||
|
/usr/share/ifupdownaddons. To see the list of active addon
|
||||||
|
modules, see ifaddon(8).
|
||||||
|
|
||||||
|
Addon modules add new attributes to the interfaces(5) file.
|
||||||
|
Below is a list of attribute options provided by each module.
|
||||||
|
These can be listed under each iface section in the interfaces(5)
|
||||||
|
file. ''')
|
||||||
|
|
||||||
|
print '\n'
|
||||||
|
|
||||||
|
def get_addon_modinfo(modules_dir):
|
||||||
|
""" load python modules from modules_dir
|
||||||
|
|
||||||
|
Default modules_dir is /usr/share/ifupdownmodules
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not modules_dir in sys.path:
|
||||||
|
sys.path.append(modules_dir)
|
||||||
|
read_modules_config()
|
||||||
|
modinfo = {}
|
||||||
|
try:
|
||||||
|
for op, mlist in addon_config.items():
|
||||||
|
for mname in mlist:
|
||||||
|
if mname in modinfo.keys(): continue
|
||||||
|
mpath = modules_dir + '/' + mname + '.py'
|
||||||
|
if os.path.exists(mpath):
|
||||||
|
try:
|
||||||
|
m = __import__(mname)
|
||||||
|
mclass = getattr(m, mname)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
continue
|
||||||
|
minstance = mclass()
|
||||||
|
if hasattr(minstance, 'get_modinfo'):
|
||||||
|
modinfo[mname] = minstance.get_modinfo()
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
|
||||||
|
return modinfo
|
||||||
|
|
||||||
|
def print_long_string(indent, strarg):
|
||||||
|
slen = 70 - len(indent)
|
||||||
|
tmphelpstr = strarg
|
||||||
|
l = len(strarg)
|
||||||
|
while l > 0:
|
||||||
|
rem = slen if l >= slen else l
|
||||||
|
print('%s%s' %(indent, tmphelpstr[:rem]))
|
||||||
|
tmphelpstr = tmphelpstr[rem:].strip()
|
||||||
|
l -= rem
|
||||||
|
|
||||||
|
def man_rst_examples():
|
||||||
|
print 'EXAMPLES'
|
||||||
|
print '========'
|
||||||
|
print ''' Listed below are addon modules and their supported attributes.
|
||||||
|
The attributes if applicable go under the iface section in the
|
||||||
|
interfaces(5) file.\n'''
|
||||||
|
|
||||||
|
indent = ' '
|
||||||
|
modinfo = get_addon_modinfo(modules_dir)
|
||||||
|
for m, mdict in modinfo.items():
|
||||||
|
aindent = indent + ' '
|
||||||
|
aindentplus = aindent + ' '
|
||||||
|
if not mdict:
|
||||||
|
continue
|
||||||
|
print_long_string(indent, '**%s**: %s' %(m, mdict.get('mhelp', '')))
|
||||||
|
attrdict = mdict.get('attrs')
|
||||||
|
if not attrdict:
|
||||||
|
continue
|
||||||
|
print '\n'
|
||||||
|
try:
|
||||||
|
for attrname, attrvaldict in attrdict.items():
|
||||||
|
if attrvaldict.get('compat', False):
|
||||||
|
continue
|
||||||
|
print('%s**%s**\n' %(aindent, attrname))
|
||||||
|
print_long_string(aindentplus, '**help**: %s'
|
||||||
|
%(attrvaldict.get('help', '')))
|
||||||
|
print '\n'
|
||||||
|
print('%s**required**: %s\n' %(aindentplus,
|
||||||
|
attrvaldict.get('required', False)))
|
||||||
|
default = attrvaldict.get('default')
|
||||||
|
if default:
|
||||||
|
print('%s**default**: %s\n' %(aindentplus, default))
|
||||||
|
validrange = attrvaldict.get('validrange')
|
||||||
|
if validrange:
|
||||||
|
print('%svalidrange: %s\n'
|
||||||
|
%(aindentplus, '-'.join(validrange)))
|
||||||
|
validvals = attrvaldict.get('validvals')
|
||||||
|
if validvals:
|
||||||
|
print('%s**validvals**: %s\n'
|
||||||
|
%(aindentplus, ','.join(validvals)))
|
||||||
|
examples = attrvaldict.get('example')
|
||||||
|
if not examples:
|
||||||
|
continue
|
||||||
|
print '%s**example**:' %(aindentplus)
|
||||||
|
for e in examples:
|
||||||
|
print '%s%s\n' %(aindentplus + indent, e)
|
||||||
|
print ''
|
||||||
|
except Exception, e:
|
||||||
|
print "Roopa: m = %s, str(e) = %s\n" %(m, str(e))
|
||||||
|
pass
|
||||||
|
print ''
|
||||||
|
|
||||||
|
def man_rst_see_also():
|
||||||
|
print 'SEE ALSO'
|
||||||
|
print '========'
|
||||||
|
print ''' interfaces(5),
|
||||||
|
ifup(8),
|
||||||
|
ip(8),
|
||||||
|
mstpctl(8),
|
||||||
|
brctl(8),
|
||||||
|
ethtool(8)'''
|
||||||
|
|
||||||
|
def show_man_rst():
|
||||||
|
man_rst_header()
|
||||||
|
man_rst_body()
|
||||||
|
man_rst_examples()
|
||||||
|
man_rst_see_also()
|
||||||
|
|
||||||
|
def show():
|
||||||
|
for operation, mlist in addon_config.items():
|
||||||
|
postion = 1
|
||||||
|
for m in mlist:
|
||||||
|
print '%d. %s' %(postion, m)
|
||||||
|
postion += 1
|
||||||
|
|
||||||
|
def write_modules_config():
|
||||||
|
with open(modules_configfile, 'w') as f:
|
||||||
|
for op, mlist in addon_config.items():
|
||||||
|
[f.write('%s,%s\n' %(op, m)) for m in mlist]
|
||||||
|
|
||||||
|
def process_add_cmd(args):
|
||||||
|
op = args.operation
|
||||||
|
module = args.module
|
||||||
|
position = args.position
|
||||||
|
if not op:
|
||||||
|
for k, vlist in addon_config.items():
|
||||||
|
if module not in vlist:
|
||||||
|
addon_config[k].append(module)
|
||||||
|
else:
|
||||||
|
print '%s: module %s already present' %(k, module)
|
||||||
|
return
|
||||||
|
if module in addon_config.get(op):
|
||||||
|
print 'module already present'
|
||||||
|
return
|
||||||
|
if position:
|
||||||
|
try:
|
||||||
|
addon_config[op].insert(position, module)
|
||||||
|
except Exception, e:
|
||||||
|
print ('error inserting module %s at postion %s (%s)'
|
||||||
|
%(module, position, str(e)))
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
addon_config[op].append(module)
|
||||||
|
|
||||||
|
|
||||||
|
def process_del_cmd(args):
|
||||||
|
op = args.operation
|
||||||
|
module = args.module
|
||||||
|
|
||||||
|
if op:
|
||||||
|
del addon_config[op]
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
[addon_config[op].remove(module) for op in addon_config.keys()]
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def process_move_cmd(args):
|
||||||
|
op = args.operation
|
||||||
|
module = args.module
|
||||||
|
pos = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
pos = int(args.position)
|
||||||
|
if pos < 0 or pos > len(addon_config.get(op)):
|
||||||
|
raise Exception('invalid value for position')
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
|
||||||
|
if addon_config[op].index(module) == pos:
|
||||||
|
print '%s module %s already at location %d' %(op, module, pos)
|
||||||
|
return
|
||||||
|
|
||||||
|
addon_config[op].remove(module)
|
||||||
|
addon_config[op].insert(pos, module)
|
||||||
|
|
||||||
|
def print_mlist(mlist, indent):
|
||||||
|
for idx, val in enumerate(mlist):
|
||||||
|
print '%s%d. %s' %(indent, idx, val)
|
||||||
|
|
||||||
|
def process_show_cmd(args):
|
||||||
|
indent = ' '
|
||||||
|
op = args.operation
|
||||||
|
|
||||||
|
if args.man:
|
||||||
|
show_man_rst()
|
||||||
|
return
|
||||||
|
|
||||||
|
if op:
|
||||||
|
mlist = addon_config[op]
|
||||||
|
print '%s:' %op
|
||||||
|
print_mlist(mlist, indent)
|
||||||
|
else:
|
||||||
|
for op, mlist in addon_config.items():
|
||||||
|
print '%s:' %op
|
||||||
|
print_mlist(mlist, indent)
|
||||||
|
print ''
|
||||||
|
|
||||||
|
cmdhandlers = {'add' : process_add_cmd,
|
||||||
|
'del' : process_del_cmd,
|
||||||
|
'move' : process_move_cmd,
|
||||||
|
'show' : process_show_cmd}
|
||||||
|
|
||||||
|
def update_subparser_add(subparser):
|
||||||
|
subparser.add_argument('module', metavar='MODULE', help='module name')
|
||||||
|
subparser.add_argument('operation', metavar='OPERATION',
|
||||||
|
choices=['pre-up', 'up', 'post-up',
|
||||||
|
'pre-down', 'down', 'post-down'],
|
||||||
|
help='operations', nargs='?')
|
||||||
|
subparser.add_argument('position', metavar='POSITION', nargs='?',
|
||||||
|
help='position')
|
||||||
|
subparser.set_defaults(func=process_add_cmd)
|
||||||
|
|
||||||
|
def update_subparser_del(subparser):
|
||||||
|
subparser.add_argument('module', metavar='MODULE', help='module name')
|
||||||
|
subparser.add_argument('operation', metavar='OPERATION',
|
||||||
|
choices=['pre-up', 'up', 'post-up',
|
||||||
|
'pre-down', 'down', 'post-down'],
|
||||||
|
help='operations', nargs='?')
|
||||||
|
subparser.add_argument('position', metavar='POSITION', nargs='?',
|
||||||
|
help='position')
|
||||||
|
subparser.set_defaults(func=process_del_cmd)
|
||||||
|
|
||||||
|
def update_subparser_move(subparser):
|
||||||
|
subparser.add_argument('module', metavar='MODULE', help='module name')
|
||||||
|
subparser.add_argument('operation', metavar='OPERATION',
|
||||||
|
choices=['pre-up', 'up', 'post-up',
|
||||||
|
'pre-down', 'down', 'post-down'],
|
||||||
|
help='operations')
|
||||||
|
subparser.add_argument('position', metavar='POSITION',
|
||||||
|
help='position')
|
||||||
|
subparser.set_defaults(func=process_move_cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def update_subparser_show(subparser):
|
||||||
|
subparser.add_argument('--man', action='store_true',
|
||||||
|
help=argparse.SUPPRESS)
|
||||||
|
subparser.add_argument('operation', metavar='OPERATION',
|
||||||
|
choices=addon_config.keys(),
|
||||||
|
help='operations %s' %str(addon_config.keys()),
|
||||||
|
nargs='?')
|
||||||
|
subparser.set_defaults(func=process_show_cmd)
|
||||||
|
|
||||||
|
def update_argparser(argparser):
|
||||||
|
subparsers = argparser.add_subparsers(help='sub-command help')
|
||||||
|
|
||||||
|
parser_add = subparsers.add_parser('add')
|
||||||
|
update_subparser_add(parser_add)
|
||||||
|
|
||||||
|
parser_del = subparsers.add_parser('del', help='del help')
|
||||||
|
update_subparser_del(parser_del)
|
||||||
|
|
||||||
|
parser_move = subparsers.add_parser('move', help='move help')
|
||||||
|
update_subparser_move(parser_move)
|
||||||
|
|
||||||
|
parser_show = subparsers.add_parser('show', help='show help')
|
||||||
|
update_subparser_show(parser_show)
|
||||||
|
|
||||||
|
def parse_args(argsv):
|
||||||
|
descr = 'ifupdown addon modules management command.\n \
|
||||||
|
This command helps add/del/display/reorder modules \n \
|
||||||
|
in all ifupdown module categories'
|
||||||
|
|
||||||
|
argparser = argparse.ArgumentParser(description=descr)
|
||||||
|
update_argparser(argparser)
|
||||||
|
|
||||||
|
args = argparser.parse_args(argsv)
|
||||||
|
return args
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
""" main function """
|
||||||
|
try:
|
||||||
|
# Command line arg parser
|
||||||
|
args = parse_args(argv[1:])
|
||||||
|
read_modules_config()
|
||||||
|
args.func(args)
|
||||||
|
write_modules_config()
|
||||||
|
except Exception, e:
|
||||||
|
print 'error processing command (%s)' %str(e)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if not os.geteuid() == 0:
|
||||||
|
print 'Error: Must be root to run this command'
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
main(sys.argv)
|
15
setup.py
15
setup.py
@ -6,13 +6,13 @@ setup(name='ifupdown2',
|
|||||||
author='Roopa Prabhu',
|
author='Roopa Prabhu',
|
||||||
author_email='roopa@cumulusnetworks.com',
|
author_email='roopa@cumulusnetworks.com',
|
||||||
url='cumulusnetworks.com',
|
url='cumulusnetworks.com',
|
||||||
packages=['ifupdown'],
|
packages=['ifupdown', 'ifupdownaddons'],
|
||||||
scripts = ['sbin/ifupdown'],
|
scripts = ['sbin/ifupdown'],
|
||||||
install_requires = ['python-gvgen'],
|
install_requires = ['python-gvgen'],
|
||||||
data_files=[('share/man/man8/',
|
data_files=[('share/man/man8/',
|
||||||
['man/ifup.8', 'man/ifquery.8', 'man/ifreload.8']),
|
['man/ifup.8', 'man/ifquery.8', 'man/ifreload.8']),
|
||||||
('share/man/man5/',
|
('share/man/man5/',
|
||||||
['man/interfaces.5']),
|
['man/interfaces.5', 'man/ifupdown-addons-interfaces.5']),
|
||||||
('/etc/init.d/',
|
('/etc/init.d/',
|
||||||
['init.d/networking']),
|
['init.d/networking']),
|
||||||
('/sbin/', ['sbin/ifupdown']),
|
('/sbin/', ['sbin/ifupdown']),
|
||||||
@ -24,5 +24,14 @@ setup(name='ifupdown2',
|
|||||||
['docs/examples/interfaces',
|
['docs/examples/interfaces',
|
||||||
'docs/examples/interfaces_bridge_template_func',
|
'docs/examples/interfaces_bridge_template_func',
|
||||||
'docs/examples/interfaces_with_template']),
|
'docs/examples/interfaces_with_template']),
|
||||||
('/etc/bash_completion.d/', ['completion/ifup'])]
|
('/etc/bash_completion.d/', ['completion/ifup']),
|
||||||
|
('/usr/share/ifupdownaddons/', ['addons/bridge.py',
|
||||||
|
'addons/ifenslave.py', 'addons/vlan.py',
|
||||||
|
'addons/mstpctl.py', 'addons/address.py',
|
||||||
|
'addons/dhcp.py', 'addons/usercmds.py',
|
||||||
|
'addons/ethtool.py', 'addons/loopback.py',
|
||||||
|
'addons/addressvirtual.py', 'addons/vxlan.py',
|
||||||
|
'addons/bridgevlanaware.py']),
|
||||||
|
('/var/lib/ifupdownaddons/', ['config/addons.conf'])
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
Reference in New Issue
Block a user