mirror of
https://github.com/CumulusNetworks/ifupdown2.git
synced 2024-05-06 15:54:50 +00:00
943 lines
42 KiB
Python
943 lines
42 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
|
|
# Authors:
|
|
# Roopa Prabhu, roopa@cumulusnetworks.com
|
|
# Julien Fortin, julien@cumulusnetworks.com
|
|
#
|
|
|
|
import os
|
|
|
|
try:
|
|
from ifupdown2.lib.addon import Addon
|
|
from ifupdown2.nlmanager.nlmanager import Link
|
|
|
|
from ifupdown2.ifupdown.iface import *
|
|
from ifupdown2.ifupdown.utils import utils
|
|
from ifupdown2.ifupdown.statemanager import statemanager_api as statemanager
|
|
|
|
import ifupdown2.ifupdown.policymanager as policymanager
|
|
import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
|
|
|
|
from ifupdown2.ifupdownaddons.modulebase import moduleBase
|
|
except (ImportError, ModuleNotFoundError):
|
|
from lib.addon import Addon
|
|
from nlmanager.nlmanager import Link
|
|
|
|
from ifupdown.iface import *
|
|
from ifupdown.utils import utils
|
|
from ifupdown.statemanager import statemanager_api as statemanager
|
|
|
|
from ifupdownaddons.modulebase import moduleBase
|
|
|
|
import ifupdown.policymanager as policymanager
|
|
import ifupdown.ifupdownflags as ifupdownflags
|
|
|
|
class bond(Addon, moduleBase):
|
|
""" ifupdown2 addon module to configure bond interfaces """
|
|
|
|
overrides_ifupdown_scripts = ['ifenslave', ]
|
|
|
|
_modinfo = {
|
|
"mhelp": "bond configuration module",
|
|
"attrs": {
|
|
"bond-use-carrier": {
|
|
"help": "bond use carrier",
|
|
"validvals": ["yes", "no", "0", "1"],
|
|
"default": "yes",
|
|
"example": ["bond-use-carrier yes"]},
|
|
"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": [
|
|
"0", "layer2",
|
|
"1", "layer3+4",
|
|
"2", "layer2+3",
|
|
"3", "encap2+3",
|
|
"4", "encap3+4",
|
|
"5", "vlan+srcmac"
|
|
],
|
|
"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": [
|
|
"0", "balance-rr",
|
|
"1", "active-backup",
|
|
"2", "balance-xor",
|
|
"3", "broadcast",
|
|
"4", "802.3ad",
|
|
"5", "balance-tlb",
|
|
"6", "balance-alb"
|
|
],
|
|
"default": "balance-rr",
|
|
"example": ["bond-mode 802.3ad"]
|
|
},
|
|
"bond-lacp-rate": {
|
|
"help": "bond lacp rate",
|
|
"validvals": ["0", "slow", "1", "fast"],
|
|
"default": "0",
|
|
"example": ["bond-lacp-rate 0"]
|
|
},
|
|
"bond-min-links": {
|
|
"help": "bond min links",
|
|
"default": "0",
|
|
"validrange": ["0", "255"],
|
|
"example": ["bond-min-links 0"]
|
|
},
|
|
"bond-ad-sys-priority": {
|
|
"help": "802.3ad system priority",
|
|
"default": "65535",
|
|
"validrange": ["0", "65535"],
|
|
"example": ["bond-ad-sys-priority 65535"],
|
|
"deprecated": True,
|
|
"new-attribute": "bond-ad-actor-sys-prio"
|
|
},
|
|
"bond-ad-actor-sys-prio": {
|
|
"help": "802.3ad system priority",
|
|
"default": "65535",
|
|
"validrange": ["0", "65535"],
|
|
"example": ["bond-ad-actor-sys-prio 65535"]
|
|
},
|
|
"bond-ad-sys-mac-addr": {
|
|
"help": "802.3ad system mac address",
|
|
"validvals": ["<mac>", ],
|
|
"example": ["bond-ad-sys-mac-addr 00:00:00:00:00:00"],
|
|
"deprecated": True,
|
|
"new-attribute": "bond-ad-actor-system"
|
|
},
|
|
"bond-ad-actor-system": {
|
|
"help": "802.3ad system mac address",
|
|
"validvals": ["<mac>", ],
|
|
"example": ["bond-ad-actor-system 00:00:00:00:00:00"],
|
|
},
|
|
"bond-lacp-bypass-allow": {
|
|
"help": "allow lacp bypass",
|
|
"validvals": ["yes", "no", "0", "1"],
|
|
"default": "no",
|
|
"example": ["bond-lacp-bypass-allow no"]
|
|
},
|
|
"bond-slaves": {
|
|
"help": "bond slaves",
|
|
"required": True,
|
|
"multivalue": True,
|
|
"validvals": ["<interface-list>"],
|
|
"example": [
|
|
"bond-slaves swp1 swp2",
|
|
"bond-slaves glob swp1-2",
|
|
"bond-slaves regex (swp[1|2])"
|
|
],
|
|
"aliases": ["bond-ports"]
|
|
},
|
|
"bond-updelay": {
|
|
"help": "bond updelay",
|
|
"default": "0",
|
|
"validrange": ["0", "65535"],
|
|
"example": ["bond-updelay 100"]
|
|
},
|
|
"bond-downdelay": {
|
|
"help": "bond downdelay",
|
|
"default": "0",
|
|
"validrange": ["0", "65535"],
|
|
"example": ["bond-downdelay 100"]
|
|
},
|
|
"bond-primary": {
|
|
"help": "Control which slave interface is "
|
|
"preferred active member",
|
|
"example": ["bond-primary swp1"]
|
|
},
|
|
"bond-primary-reselect": {
|
|
"help": "bond primary reselect",
|
|
"validvals": [
|
|
"0", "always",
|
|
"1", "better",
|
|
"2", "failure",
|
|
],
|
|
"example": ["bond-primary-reselect failure"]
|
|
},
|
|
"es-sys-mac": {
|
|
"help": "evpn-mh: system mac address",
|
|
"validvals": ["<mac>", ],
|
|
"example": ["es-sys-mac 00:00:00:00:00:42"],
|
|
}
|
|
}
|
|
}
|
|
|
|
_bond_attr_netlink_map = {
|
|
'bond-mode': Link.IFLA_BOND_MODE,
|
|
'bond-miimon': Link.IFLA_BOND_MIIMON,
|
|
'bond-use-carrier': Link.IFLA_BOND_USE_CARRIER,
|
|
'bond-lacp-rate': Link.IFLA_BOND_AD_LACP_RATE,
|
|
'bond-xmit-hash-policy': Link.IFLA_BOND_XMIT_HASH_POLICY,
|
|
'bond-min-links': Link.IFLA_BOND_MIN_LINKS,
|
|
'bond-num-grat-arp': Link.IFLA_BOND_NUM_PEER_NOTIF,
|
|
'bond-num-unsol-na': Link.IFLA_BOND_NUM_PEER_NOTIF,
|
|
'es-sys-mac': Link.IFLA_BOND_AD_ACTOR_SYSTEM,
|
|
'bond-ad-sys-mac-addr': Link.IFLA_BOND_AD_ACTOR_SYSTEM,
|
|
'bond-ad-actor-system': Link.IFLA_BOND_AD_ACTOR_SYSTEM,
|
|
'bond-ad-sys-priority': Link.IFLA_BOND_AD_ACTOR_SYS_PRIO,
|
|
'bond-ad-actor-sys-prio': Link.IFLA_BOND_AD_ACTOR_SYS_PRIO,
|
|
'bond-lacp-bypass-allow': Link.IFLA_BOND_AD_LACP_BYPASS,
|
|
'bond-updelay': Link.IFLA_BOND_UPDELAY,
|
|
'bond-downdelay': Link.IFLA_BOND_DOWNDELAY,
|
|
'bond-primary': Link.IFLA_BOND_PRIMARY,
|
|
'bond-primary-reselect': Link.IFLA_BOND_PRIMARY_RESELECT
|
|
|
|
}
|
|
|
|
# ifquery-check attr dictionary with callable object to translate user data to netlink format
|
|
_bond_attr_ifquery_check_translate_func = {
|
|
Link.IFLA_BOND_MODE: lambda x: Link.ifla_bond_mode_tbl[x],
|
|
Link.IFLA_BOND_MIIMON: int,
|
|
Link.IFLA_BOND_USE_CARRIER: utils.get_boolean_from_string,
|
|
Link.IFLA_BOND_AD_LACP_RATE: lambda x: int(utils.get_boolean_from_string(x)),
|
|
Link.IFLA_BOND_XMIT_HASH_POLICY: lambda x: Link.ifla_bond_xmit_hash_policy_tbl[x],
|
|
Link.IFLA_BOND_MIN_LINKS: int,
|
|
Link.IFLA_BOND_NUM_PEER_NOTIF: int,
|
|
Link.IFLA_BOND_AD_ACTOR_SYSTEM: str,
|
|
Link.IFLA_BOND_AD_ACTOR_SYS_PRIO: int,
|
|
Link.IFLA_BOND_AD_LACP_BYPASS: lambda x: int(utils.get_boolean_from_string(x)),
|
|
Link.IFLA_BOND_UPDELAY: int,
|
|
Link.IFLA_BOND_DOWNDELAY: int,
|
|
Link.IFLA_BOND_PRIMARY_RESELECT: lambda x: Link.ifla_bond_primary_reselect_tbl[x],
|
|
# Link.IFLA_BOND_PRIMARY: self.netlink.get_ifname is added in __init__()
|
|
}
|
|
|
|
# ifup attr list with callable object to translate user data to netlink format
|
|
# in the future this can be moved to a dictionary, whenever we detect that some
|
|
# netlink capabilities are missing we can dynamically remove them from the dict.
|
|
_bond_attr_set_list = (
|
|
('bond-mode', Link.IFLA_BOND_MODE, lambda x: Link.ifla_bond_mode_tbl[x]),
|
|
('bond-xmit-hash-policy', Link.IFLA_BOND_XMIT_HASH_POLICY, lambda x: Link.ifla_bond_xmit_hash_policy_tbl[x]),
|
|
('bond-miimon', Link.IFLA_BOND_MIIMON, int),
|
|
('bond-min-links', Link.IFLA_BOND_MIN_LINKS, int),
|
|
('bond-num-grat-arp', Link.IFLA_BOND_NUM_PEER_NOTIF, int),
|
|
('bond-num-unsol-na', Link.IFLA_BOND_NUM_PEER_NOTIF, int),
|
|
('bond-ad-sys-priority', Link.IFLA_BOND_AD_ACTOR_SYS_PRIO, int),
|
|
('bond-ad-actor-sys-prio', Link.IFLA_BOND_AD_ACTOR_SYS_PRIO, int),
|
|
('bond-updelay', Link.IFLA_BOND_UPDELAY, int),
|
|
('bond-downdelay', Link.IFLA_BOND_DOWNDELAY, int),
|
|
('bond-use-carrier', Link.IFLA_BOND_USE_CARRIER, lambda x: int(utils.get_boolean_from_string(x))),
|
|
('bond-lacp-rate', Link.IFLA_BOND_AD_LACP_RATE, lambda x: int(utils.get_boolean_from_string(x))),
|
|
('bond-lacp-bypass-allow', Link.IFLA_BOND_AD_LACP_BYPASS, lambda x: int(utils.get_boolean_from_string(x))),
|
|
('es-sys-mac', Link.IFLA_BOND_AD_ACTOR_SYSTEM, str),
|
|
('bond-ad-sys-mac-addr', Link.IFLA_BOND_AD_ACTOR_SYSTEM, str),
|
|
('bond-ad-actor-system', Link.IFLA_BOND_AD_ACTOR_SYSTEM, str),
|
|
('bond-primary-reselect', Link.IFLA_BOND_PRIMARY_RESELECT, lambda x: Link.ifla_bond_primary_reselect_tbl[x])
|
|
# ('bond-primary', Link.IFLA_BOND_PRIMARY, self.cache.get_ifindex) added in __init__()
|
|
)
|
|
|
|
def __init__(self, *args, **kargs):
|
|
Addon.__init__(self)
|
|
moduleBase.__init__(self, *args, **kargs)
|
|
|
|
if not os.path.exists('/sys/class/net/bonding_masters'):
|
|
try:
|
|
utils.exec_command('modprobe -q bonding')
|
|
except Exception as e:
|
|
self.logger.info("bond: error while loading bonding module: %s" % str(e))
|
|
|
|
self._bond_attr_ifquery_check_translate_func[Link.IFLA_BOND_PRIMARY] = self.cache.get_ifindex
|
|
self._bond_attr_set_list = self._bond_attr_set_list + (('bond-primary', Link.IFLA_BOND_PRIMARY, self.cache.get_ifindex),)
|
|
|
|
self.bond_mac_mgmt = utils.get_boolean_from_string(
|
|
policymanager.policymanager_api.get_module_globals(
|
|
module_name=self.__class__.__name__,
|
|
attr="bond_mac_mgmt"),
|
|
True
|
|
)
|
|
|
|
def get_bond_slaves(self, ifaceobj):
|
|
# bond-ports aliases should be translated to bond-slaves
|
|
return ifaceobj.get_attr_value_first('bond-slaves')
|
|
|
|
def _is_bond(self, ifaceobj):
|
|
# at first link_kind is not set but once ifupdownmain
|
|
# calls get_dependent_ifacenames link_kind is set to BOND
|
|
return ifaceobj.link_kind & ifaceLinkKind.BOND or self.get_bond_slaves(ifaceobj)
|
|
|
|
def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None, old_ifaceobjs=False):
|
|
""" Returns list of interfaces dependent on ifaceobj """
|
|
|
|
if not self._is_bond(ifaceobj):
|
|
return None
|
|
slave_list = self.parse_port_list(ifaceobj.name,
|
|
self.get_bond_slaves(ifaceobj),
|
|
ifacenames_all)
|
|
ifaceobj.dependency_type = ifaceDependencyType.MASTER_SLAVE
|
|
# Also save a copy for future use
|
|
ifaceobj.priv_data = list(slave_list)
|
|
if ifaceobj.link_type != ifaceLinkType.LINK_NA:
|
|
ifaceobj.link_type = ifaceLinkType.LINK_MASTER
|
|
ifaceobj.link_kind |= ifaceLinkKind.BOND
|
|
ifaceobj.role |= ifaceRole.MASTER
|
|
|
|
if ifaceobj.get_attr_value("es-sys-mac"):
|
|
ifaceobj.link_privflags |= ifaceLinkPrivFlags.ES_BOND
|
|
|
|
return slave_list
|
|
|
|
def syntax_check(self, ifaceobj, ifaceobj_getfunc):
|
|
return self.syntax_check_updown_delay(ifaceobj)
|
|
|
|
def get_dependent_ifacenames_running(self, ifaceobj):
|
|
return self.cache.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 = self.get_bond_slaves(ifaceobj)
|
|
if slaves:
|
|
return self.parse_port_list(ifaceobj.name, slaves)
|
|
else:
|
|
return None
|
|
|
|
def enable_ipv6_if_prev_brport(self, ifname):
|
|
"""
|
|
If the intf was previously enslaved to a bridge it is possible ipv6 is still disabled.
|
|
"""
|
|
try:
|
|
for ifaceobj in statemanager.get_ifaceobjs(ifname) or []:
|
|
if ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT:
|
|
self.write_file("/proc/sys/net/ipv6/conf/%s/disable_ipv6" % ifname, "0")
|
|
return
|
|
except Exception as e:
|
|
self.logger.info(str(e))
|
|
|
|
def _is_clag_bond(self, ifaceobj):
|
|
if self.get_bond_slaves(ifaceobj):
|
|
attrval = ifaceobj.get_attr_value_first('clag-id')
|
|
if attrval and attrval != '0':
|
|
return True
|
|
return False
|
|
|
|
def _add_slaves(self, ifaceobj, runningslaves, ifaceobj_getfunc=None):
|
|
slaves = self._get_slave_list(ifaceobj)
|
|
if not slaves:
|
|
self.logger.debug('%s: no slaves found' %ifaceobj.name)
|
|
return
|
|
|
|
clag_bond = self._is_clag_bond(ifaceobj)
|
|
|
|
# remove duplicates and devices that are already enslaved
|
|
devices_to_enslave = []
|
|
for s in slaves:
|
|
if s not in runningslaves and s not in devices_to_enslave:
|
|
devices_to_enslave.append(s)
|
|
|
|
for slave in devices_to_enslave:
|
|
if (not ifupdownflags.flags.PERFMODE and
|
|
not self.cache.link_exists(slave)):
|
|
self.log_error('%s: skipping slave %s, does not exist'
|
|
%(ifaceobj.name, slave), ifaceobj,
|
|
raise_error=False)
|
|
continue
|
|
link_up = False
|
|
if self.cache.link_is_up(slave):
|
|
self.netlink.link_down_force(slave)
|
|
link_up = True
|
|
|
|
# if clag or ES bond: place the slave in a protodown state;
|
|
# (clagd will proto-up it when it is ready)
|
|
if clag_bond or ifaceobj.link_privflags & ifaceLinkPrivFlags.ES_BOND:
|
|
try:
|
|
self.netlink.link_set_protodown_on(slave)
|
|
except Exception as e:
|
|
self.logger.error('%s: %s' % (ifaceobj.name, str(e)))
|
|
|
|
self.enable_ipv6_if_prev_brport(slave)
|
|
self.netlink.link_set_master(slave, ifaceobj.name)
|
|
runningslaves.append(slave)
|
|
# TODO: if this fail we should switch to iproute2
|
|
# start a batch: down - set master - up
|
|
if link_up or ifaceobj.link_type != ifaceLinkType.LINK_NA:
|
|
try:
|
|
if (ifaceobj_getfunc(slave)[0].link_privflags &
|
|
ifaceLinkPrivFlags.KEEP_LINK_DOWN):
|
|
self.netlink.link_down_force(slave)
|
|
else:
|
|
self.netlink.link_up_force(slave)
|
|
except Exception as e:
|
|
self.logger.debug('%s: %s' % (ifaceobj.name, str(e)))
|
|
pass
|
|
|
|
if runningslaves:
|
|
removed_slave = []
|
|
|
|
for s in runningslaves:
|
|
# make sure that slaves are not in protodown since we are not in the clag-bond or es-bond case
|
|
if not clag_bond and not ifaceobj.link_privflags & ifaceLinkPrivFlags.ES_BOND and self.cache.get_link_protodown(s):
|
|
self.netlink.link_set_protodown_off(s)
|
|
|
|
if s not in slaves:
|
|
self.sysfs.bond_remove_slave(ifaceobj.name, s)
|
|
removed_slave.append(s)
|
|
if clag_bond:
|
|
try:
|
|
self.netlink.link_set_protodown_off(s)
|
|
except Exception as e:
|
|
self.logger.error('%s: %s' % (ifaceobj.name, str(e)))
|
|
|
|
# ip link set $slave nomaster will set the slave admin down
|
|
# if the slave has an auto stanza, we should keep it admin up
|
|
# unless link-down yes is set
|
|
slave_class_auto = False
|
|
slave_link_down = False
|
|
for obj in ifaceobj_getfunc(s) or []:
|
|
if obj.auto:
|
|
slave_class_auto = True
|
|
if obj.link_privflags & ifaceLinkPrivFlags.KEEP_LINK_DOWN:
|
|
slave_link_down = True
|
|
if slave_class_auto and not slave_link_down:
|
|
self.netlink.link_up_force(s)
|
|
else:
|
|
# apply link-down config changes on running slaves
|
|
try:
|
|
link_up = self.cache.link_is_up(s)
|
|
config_link_down = (ifaceobj_getfunc(s)[0].link_privflags &
|
|
ifaceLinkPrivFlags.KEEP_LINK_DOWN)
|
|
if (config_link_down and link_up):
|
|
self.netlink.link_down_force(s)
|
|
elif (not config_link_down and not link_up):
|
|
self.netlink.link_up_force(s)
|
|
except Exception as e:
|
|
self.logger.warning('%s: %s' % (ifaceobj.name, str(e)))
|
|
|
|
for s in removed_slave:
|
|
try:
|
|
runningslaves.remove(s)
|
|
except:
|
|
pass
|
|
|
|
return runningslaves
|
|
|
|
def _check_updown_delay_log(self, ifaceobj, attr_name, value):
|
|
ifaceobj.status = ifaceStatus.ERROR
|
|
self.logger.error('%s: unable to set %s %s as MII link monitoring is '
|
|
'disabled' % (ifaceobj.name, attr_name, value))
|
|
# return False to notify syntax_check that an error has been logged
|
|
return False
|
|
|
|
def syntax_check_updown_delay(self, ifaceobj):
|
|
result = True
|
|
updelay = ifaceobj.get_attr_value_first('bond-updelay')
|
|
downdelay = ifaceobj.get_attr_value_first('bond-downdelay')
|
|
|
|
if not updelay and not downdelay:
|
|
return True
|
|
|
|
try:
|
|
miimon = int(ifaceobj.get_attr_value_first('bond-miimon'))
|
|
except Exception:
|
|
try:
|
|
miimon = int(policymanager.policymanager_api.get_iface_default(
|
|
module_name=self.__class__.__name__,
|
|
ifname=ifaceobj.name,
|
|
attr='bond-miimon'))
|
|
except Exception:
|
|
miimon = 0
|
|
|
|
if not miimon:
|
|
# self._check_updown_delay_log returns False no matter what
|
|
if updelay and int(updelay):
|
|
result = self._check_updown_delay_log(ifaceobj, 'bond-updelay', updelay)
|
|
if downdelay and int(downdelay):
|
|
result = self._check_updown_delay_log(ifaceobj, 'bond-downdelay', downdelay)
|
|
|
|
return result
|
|
|
|
_bond_updown_delay_nl_list = (
|
|
(Link.IFLA_BOND_UPDELAY, 'bond-updelay'),
|
|
(Link.IFLA_BOND_DOWNDELAY, 'bond-downdelay')
|
|
)
|
|
|
|
def check_updown_delay_nl(self, link_exists, ifaceobj, ifla_info_data):
|
|
"""
|
|
IFLA_BOND_MIIMON
|
|
Specifies the time, in milliseconds, to wait before enabling a slave
|
|
after a link recovery has been detected. This option is only valid
|
|
for the miimon link monitor. The updelay value should be a multiple
|
|
of the miimon value; if not, it will be rounded down to the nearest
|
|
multiple. The default value is 0.
|
|
|
|
This ifla_bond_miimon code should be move to get_ifla_bond_attr_from_user_config
|
|
but we need to know if the operation was successful to update the cache accordingly
|
|
"""
|
|
ifla_bond_miimon = ifla_info_data.get(Link.IFLA_BOND_MIIMON)
|
|
if link_exists and ifla_bond_miimon is None:
|
|
ifla_bond_miimon = self.cache.get_link_info_data_attribute(ifaceobj.name, Link.IFLA_BOND_MIIMON)
|
|
|
|
if ifla_bond_miimon == 0:
|
|
for nl_attr, attr_name in self._bond_updown_delay_nl_list:
|
|
delay = ifla_info_data.get(nl_attr)
|
|
# if up-down-delay exists we need to remove it, if non zero log error
|
|
if delay is not None:
|
|
if delay > 0:
|
|
self._check_updown_delay_log(ifaceobj, attr_name, delay)
|
|
del ifla_info_data[nl_attr]
|
|
return True
|
|
return False
|
|
|
|
_bond_lacp_attrs = (
|
|
(Link.IFLA_BOND_AD_LACP_RATE, 'bond-lacp-rate'),
|
|
(Link.IFLA_BOND_AD_LACP_BYPASS, 'bond-lacp-bypass')
|
|
)
|
|
|
|
def _check_bond_mode_user_config(self, ifname, link_exists, ifla_info_data):
|
|
ifla_bond_mode = ifla_info_data.get(Link.IFLA_BOND_MODE)
|
|
if ifla_bond_mode is None and link_exists:
|
|
ifla_bond_mode = self.cache.get_link_info_data_attribute(ifname, Link.IFLA_BOND_MODE)
|
|
# in this case the link already exists (we have a cached value):
|
|
# if IFLA_BOND_MODE is not present in ifla_info_data it means:
|
|
# - that bond-mode was present in the user config and didn't change
|
|
# - never was in the user config so bond mode should be the system default value
|
|
# - was removed from the stanza so we might have to reset it to default value
|
|
# nevertheless we need to add it back to the ifla_info_data dict to check
|
|
# if we need to reset the mode to system default
|
|
ifla_info_data[Link.IFLA_BOND_MODE] = ifla_bond_mode
|
|
|
|
if ifla_bond_mode == 4: # 802.3ad
|
|
min_links = ifla_info_data.get(Link.IFLA_BOND_MIN_LINKS)
|
|
if min_links is None:
|
|
min_links = self.cache.get_link_info_data_attribute(ifname, Link.IFLA_BOND_MIN_LINKS)
|
|
# get_min_links_nl may return None so we need to strictly check 0
|
|
if min_links == 0:
|
|
self.logger.warning('%s: attribute bond-min-links is set to \'0\'' % ifname)
|
|
else:
|
|
# IFLA_BOND_AD_LACP_RATE and IFLA_BOND_AD_LACP_BYPASS only for 802.3ad mode (4)
|
|
for nl_attr, attr_name in self._bond_lacp_attrs:
|
|
if nl_attr in ifla_info_data:
|
|
self.logger.info('%s: ignoring %s: only available for 802.3ad mode (4)' % (ifname, attr_name))
|
|
del ifla_info_data[nl_attr]
|
|
|
|
@staticmethod
|
|
def get_saved_ifaceobj(link_exists, ifname):
|
|
if link_exists:
|
|
old_config = statemanager.get_ifaceobjs(ifname)
|
|
if old_config:
|
|
return old_config[0]
|
|
return None
|
|
|
|
def get_ifla_bond_attr_from_user_config(self, ifaceobj, link_exists):
|
|
"""
|
|
Potential issue: if a user load the bond driver with custom
|
|
default values (say bond-mode 3), ifupdown2 has no knowledge
|
|
of these default values.
|
|
At bond creation everything should work, bonds will be created
|
|
with mode 3 (even if not specified under the stanza).
|
|
But, for example: if the user specifies a value under bond-mode
|
|
and later on the user removes the bond-mode line from the stanza
|
|
we will detect it and reset to MODINFO: BOND-MODE: DEFAULT aka 0
|
|
which is not the real default value that the user may expect.
|
|
"""
|
|
ifname = ifaceobj.name
|
|
ifla_info_data = OrderedDict()
|
|
old_config = self.get_saved_ifaceobj(link_exists, ifname)
|
|
|
|
# for each bond attribute we fetch the user configuration
|
|
# if no configuration is provided we look for a config in policy files
|
|
for attr_name, netlink_attr, func_ptr in self._bond_attr_set_list:
|
|
cached_value = None
|
|
user_config = ifaceobj.get_attr_value_first(attr_name)
|
|
|
|
if not user_config:
|
|
user_config = policymanager.policymanager_api.get_iface_default(
|
|
module_name=self.__class__.__name__,
|
|
ifname=ifname,
|
|
attr=attr_name)
|
|
if user_config:
|
|
self.logger.debug('%s: %s %s: extracted from policy files'
|
|
% (ifname, attr_name, user_config))
|
|
|
|
# no policy override, do we need to reset an attr to default value?
|
|
if not user_config and old_config and old_config.get_attr_value_first(attr_name):
|
|
# if the link already exists but the value is set
|
|
# (potentially removed from the stanza, we need to reset it to default)
|
|
# might not work for specific cases, see explanation at the top of this function :)
|
|
user_config = self.get_attr_default_value(attr_name)
|
|
if user_config:
|
|
self.logger.debug('%s: %s: removed from stanza, resetting to default value: %s'
|
|
% (ifname, attr_name, user_config))
|
|
|
|
if user_config:
|
|
try:
|
|
nl_value = func_ptr(user_config.lower())
|
|
|
|
if link_exists:
|
|
cached_value = self.cache.get_link_info_data_attribute(ifname, netlink_attr)
|
|
|
|
if link_exists and cached_value is None:
|
|
# the link already exists but we don't have any value
|
|
# cached for this attr, it probably means that the
|
|
# capability is not available on this system (i.e old kernel)
|
|
self.logger.debug('%s: ignoring %s %s: capability '
|
|
'probably not supported on this system'
|
|
% (ifname, attr_name, user_config))
|
|
continue
|
|
elif link_exists:
|
|
# there should be a cached value if the link already exists
|
|
if cached_value == nl_value:
|
|
# if the user value is already cached: continue
|
|
continue
|
|
|
|
# else: the link doesn't exist so we create the bond with
|
|
# all the user/policy defined values without extra checks
|
|
ifla_info_data[netlink_attr] = nl_value
|
|
|
|
if cached_value is not None:
|
|
self.logger.info('%s: set %s %s (cache %s)' % (ifname, attr_name, user_config, cached_value))
|
|
else:
|
|
self.logger.info('%s: set %s %s' % (ifname, attr_name, user_config))
|
|
|
|
except KeyError:
|
|
self.logger.warning('%s: invalid %s value %s' % (ifname, attr_name, user_config))
|
|
|
|
self._check_bond_mode_user_config(ifname, link_exists, ifla_info_data)
|
|
return ifla_info_data
|
|
|
|
_bond_down_nl_attributes_list = (
|
|
Link.IFLA_BOND_MODE,
|
|
Link.IFLA_BOND_XMIT_HASH_POLICY,
|
|
Link.IFLA_BOND_AD_LACP_RATE,
|
|
Link.IFLA_BOND_MIN_LINKS
|
|
)
|
|
|
|
def _should_down_bond(self, ifla_info_data):
|
|
for nl_attr in self._bond_down_nl_attributes_list:
|
|
if nl_attr in ifla_info_data:
|
|
return True
|
|
return False
|
|
|
|
def should_update_bond_mode(self, ifaceobj, ifname, is_link_up, ifla_info_data, bond_slaves):
|
|
# if bond-mode was changed the bond needs to be brought
|
|
# down and slaves un-slaved before bond mode is changed.
|
|
cached_bond_mode = self.cache.get_link_info_data_attribute(ifname, Link.IFLA_BOND_MODE)
|
|
ifla_bond_mode = ifla_info_data.get(Link.IFLA_BOND_MODE)
|
|
|
|
# bond-mode was changed or is not specified
|
|
if ifla_bond_mode is not None:
|
|
if ifla_bond_mode != cached_bond_mode:
|
|
self.logger.info('%s: bond mode changed to %s: running ops on bond and slaves'
|
|
% (ifname, ifla_bond_mode))
|
|
if is_link_up:
|
|
self.netlink.link_down(ifname)
|
|
is_link_up = False
|
|
|
|
for lower_dev in ifaceobj.lowerifaces:
|
|
self.netlink.link_set_nomaster(lower_dev)
|
|
|
|
# when unslaving a device from an ES bond we need to set
|
|
# protodown off
|
|
if ifaceobj.link_privflags & ifaceLinkPrivFlags.ES_BOND:
|
|
self.netlink.link_set_protodown_off(lower_dev)
|
|
|
|
try:
|
|
bond_slaves.remove(lower_dev)
|
|
except Exception:
|
|
pass
|
|
|
|
else:
|
|
# bond-mode user config value is the current running(cached) value
|
|
# no need to reset it again we can ignore this attribute
|
|
del ifla_info_data[Link.IFLA_BOND_MODE]
|
|
|
|
return is_link_up, bond_slaves
|
|
|
|
def create_or_set_bond_config(self, ifaceobj):
|
|
ifname = ifaceobj.name
|
|
link_exists, is_link_up = self.cache.link_exists_and_up(ifname)
|
|
ifla_info_data = self.get_ifla_bond_attr_from_user_config(ifaceobj, link_exists)
|
|
|
|
remove_delay_from_cache = self.check_updown_delay_nl(link_exists, ifaceobj, ifla_info_data)
|
|
|
|
# if link exists: down link if specific attributes are specified
|
|
if link_exists:
|
|
# did bond-mode changed?
|
|
is_link_up, bond_slaves = self.should_update_bond_mode(
|
|
ifaceobj,
|
|
ifname,
|
|
is_link_up,
|
|
ifla_info_data,
|
|
self.cache.get_slaves(ifname)
|
|
)
|
|
|
|
# if specific attributes need to be set we need to down the bond first
|
|
if ifla_info_data and is_link_up:
|
|
if self._should_down_bond(ifla_info_data):
|
|
self.netlink.link_down_force(ifname)
|
|
is_link_up = False
|
|
else:
|
|
bond_slaves = []
|
|
|
|
if link_exists and not ifla_info_data:
|
|
# if the bond already exists and no attrs need to be set
|
|
# ignore the netlink call
|
|
self.logger.info('%s: already exists, no change detected' % ifname)
|
|
else:
|
|
try:
|
|
self.netlink.link_add_bond_with_info_data(ifname, ifla_info_data)
|
|
except Exception as e:
|
|
# defensive code
|
|
# if anything happens, we try to set up the bond with the sysfs api
|
|
self.logger.debug('%s: bond setup: %s' % (ifname, str(e)))
|
|
self.create_or_set_bond_config_sysfs(ifaceobj, ifla_info_data)
|
|
|
|
if remove_delay_from_cache:
|
|
# making sure up/down delay attributes are set to 0 before caching
|
|
# this can be removed when moving to a nllistener/live cache
|
|
ifla_info_data[Link.IFLA_BOND_UPDELAY] = 0
|
|
ifla_info_data[Link.IFLA_BOND_DOWNDELAY] = 0
|
|
|
|
if link_exists and ifla_info_data and not is_link_up:
|
|
self.netlink.link_up_force(ifname)
|
|
|
|
return link_exists, bond_slaves
|
|
|
|
def create_or_set_bond_config_sysfs(self, ifaceobj, ifla_info_data):
|
|
if len(ifaceobj.name) > 15:
|
|
self.log_error("%s: cannot create bond: interface name exceeds max length of 15" % ifaceobj.name, ifaceobj)
|
|
return
|
|
|
|
if not self.cache.link_exists(ifaceobj.name):
|
|
self.sysfs.bond_create(ifaceobj.name)
|
|
self.sysfs.bond_set_attrs_nl(ifaceobj.name, ifla_info_data)
|
|
|
|
def _up(self, ifaceobj, ifaceobj_getfunc=None):
|
|
try:
|
|
link_exists, bond_slaves = self.create_or_set_bond_config(ifaceobj)
|
|
bond_slaves = self._add_slaves(
|
|
ifaceobj,
|
|
bond_slaves,
|
|
ifaceobj_getfunc,
|
|
)
|
|
|
|
if not self.bond_mac_mgmt or not link_exists or ifaceobj.get_attr_value_first("hwaddress"):
|
|
return
|
|
|
|
# check if the bond mac address is correctly inherited from it's
|
|
# first slave. There's a case where that might not be happening:
|
|
# $ ip link show swp1 | grep ether
|
|
# link/ether 08:00:27:04:d8:01 brd ff:ff:ff:ff:ff:ff
|
|
# $ ip link show swp2 | grep ether
|
|
# link/ether 08:00:27:04:d8:02 brd ff:ff:ff:ff:ff:ff
|
|
# $ ip link add dev bond0 type bond
|
|
# $ ip link set dev swp1 master bond0
|
|
# $ ip link set dev swp2 master bond0
|
|
# $ ip link show bond0 | grep ether
|
|
# link/ether 08:00:27:04:d8:01 brd ff:ff:ff:ff:ff:ff
|
|
# $ ip link add dev bond1 type bond
|
|
# $ ip link set dev swp1 master bond1
|
|
# $ ip link show swp1 | grep ether
|
|
# link/ether 08:00:27:04:d8:01 brd ff:ff:ff:ff:ff:ff
|
|
# $ ip link show swp2 | grep ether
|
|
# link/ether 08:00:27:04:d8:01 brd ff:ff:ff:ff:ff:ff
|
|
# $ ip link show bond0 | grep ether
|
|
# link/ether 08:00:27:04:d8:01 brd ff:ff:ff:ff:ff:ff
|
|
# $ ip link show bond1 | grep ether
|
|
# link/ether 08:00:27:04:d8:01 brd ff:ff:ff:ff:ff:ff
|
|
# $
|
|
# ifupdown2 will automatically correct and fix this unexpected behavior
|
|
bond_mac = self.cache.get_link_address(ifaceobj.name)
|
|
|
|
if bond_slaves:
|
|
first_slave_ifname = bond_slaves[0]
|
|
first_slave_mac = self.cache.get_link_info_slave_data_attribute(
|
|
first_slave_ifname,
|
|
Link.IFLA_BOND_SLAVE_PERM_HWADDR
|
|
)
|
|
|
|
if first_slave_mac and bond_mac != first_slave_mac:
|
|
self.logger.info(
|
|
"%s: invalid bond mac detected - resetting to %s's mac (%s)"
|
|
% (ifaceobj.name, first_slave_ifname, first_slave_mac)
|
|
)
|
|
self.netlink.link_set_address(ifaceobj.name, first_slave_mac, utils.mac_str_to_int(first_slave_mac))
|
|
except Exception as e:
|
|
self.log_error(str(e), ifaceobj)
|
|
|
|
def _down(self, ifaceobj, ifaceobj_getfunc=None):
|
|
try:
|
|
self.netlink.link_del(ifaceobj.name)
|
|
except Exception as e:
|
|
self.log_warn('%s: %s' % (ifaceobj.name, str(e)))
|
|
|
|
def _query_check_bond_slaves(self, ifaceobjcurr, attr, user_bond_slaves, running_bond_slaves):
|
|
query = 1
|
|
|
|
if user_bond_slaves and running_bond_slaves:
|
|
if not set(user_bond_slaves).symmetric_difference(running_bond_slaves):
|
|
query = 0
|
|
|
|
# we want to display the same bond-slaves list as provided
|
|
# in the interfaces file but if this list contains regexes or
|
|
# globs, for now, we won't try to change it.
|
|
if 'regex' in user_bond_slaves or 'glob' in user_bond_slaves:
|
|
user_bond_slaves = running_bond_slaves
|
|
else:
|
|
ordered = []
|
|
for slave in user_bond_slaves:
|
|
if slave in running_bond_slaves:
|
|
ordered.append(slave)
|
|
user_bond_slaves = ordered
|
|
ifaceobjcurr.update_config_with_status(attr, ' '.join(user_bond_slaves) if user_bond_slaves else 'None', query)
|
|
|
|
def _query_check(self, ifaceobj, ifaceobjcurr, ifaceobj_getfunc=None):
|
|
if not self.cache.bond_exists(ifaceobj.name):
|
|
self.logger.debug('bond iface %s does not exist' % ifaceobj.name)
|
|
return
|
|
|
|
iface_attrs = self.dict_key_subset(ifaceobj.config, self.get_mod_attrs())
|
|
if not iface_attrs:
|
|
return
|
|
|
|
# remove bond-slaves and bond-ports from the list,
|
|
# because there aren't any ifla_info_data netlink attr for slaves
|
|
# an exception is raised when index is not found, so query_slaves will stay False
|
|
query_slaves = False
|
|
|
|
user_bond_slaves = None
|
|
running_bond_slaves = None
|
|
try:
|
|
del iface_attrs[iface_attrs.index('bond-slaves')]
|
|
|
|
# if user specified bond-slaves we need to display it
|
|
query_slaves = True
|
|
if not user_bond_slaves:
|
|
user_bond_slaves = self._get_slave_list(ifaceobj)
|
|
running_bond_slaves = self.cache.get_slaves(ifaceobj.name)
|
|
|
|
self._query_check_bond_slaves(ifaceobjcurr, 'bond-slaves', user_bond_slaves, running_bond_slaves)
|
|
except Exception:
|
|
pass
|
|
try:
|
|
del iface_attrs[iface_attrs.index('bond-ports')]
|
|
|
|
# if user specified bond-ports we need to display it
|
|
if not query_slaves and not user_bond_slaves: # if get_slave_list was already called for slaves
|
|
user_bond_slaves = self._get_slave_list(ifaceobj)
|
|
running_bond_slaves = self.cache.get_slaves(ifaceobj.name)
|
|
|
|
self._query_check_bond_slaves(ifaceobjcurr, 'bond-ports', user_bond_slaves, running_bond_slaves)
|
|
except Exception:
|
|
pass
|
|
|
|
for attr in iface_attrs:
|
|
nl_attr = self._bond_attr_netlink_map[attr]
|
|
translate_func = self._bond_attr_ifquery_check_translate_func[nl_attr]
|
|
current_config = self.cache.get_link_info_data_attribute(ifaceobj.name, nl_attr)
|
|
user_config = ifaceobj.get_attr_value_first(attr)
|
|
|
|
if current_config == translate_func(user_config):
|
|
ifaceobjcurr.update_config_with_status(attr, user_config, 0)
|
|
else:
|
|
ifaceobjcurr.update_config_with_status(attr, str(current_config), 1)
|
|
|
|
@staticmethod
|
|
def translate_nl_value_yesno(value):
|
|
return 'yes' if value else 'no'
|
|
|
|
@staticmethod
|
|
def translate_nl_value_slowfast(value):
|
|
return 'fast' if value else 'slow'
|
|
|
|
def _query_running_attrs(self, bondname):
|
|
cached_vxlan_ifla_info_data = self.cache.get_link_info_data(bondname)
|
|
|
|
bond_attrs = {
|
|
'bond-mode': Link.ifla_bond_mode_pretty_tbl.get(cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_MODE)),
|
|
'bond-miimon': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_MIIMON),
|
|
'bond-use-carrier': self.translate_nl_value_yesno(cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_USE_CARRIER)),
|
|
'bond-lacp-rate': self.translate_nl_value_slowfast(cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_AD_LACP_RATE)),
|
|
'bond-min-links': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_MIN_LINKS),
|
|
'bond-ad-actor-system': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_AD_ACTOR_SYSTEM),
|
|
'es-sys-mac': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_AD_ACTOR_SYSTEM),
|
|
'bond-ad-actor-sys-prio': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_AD_ACTOR_SYS_PRIO),
|
|
'bond-xmit-hash-policy': Link.ifla_bond_xmit_hash_policy_pretty_tbl.get(cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_XMIT_HASH_POLICY)),
|
|
'bond-lacp-bypass-allow': self.translate_nl_value_yesno(cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_AD_LACP_BYPASS)),
|
|
'bond-num-unsol-na': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_NUM_PEER_NOTIF),
|
|
'bond-num-grat-arp': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_NUM_PEER_NOTIF),
|
|
'bond-updelay': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_UPDELAY),
|
|
'bond-downdelay': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_DOWNDELAY)
|
|
}
|
|
|
|
cached_bond_primary = cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_PRIMARY)
|
|
if cached_bond_primary:
|
|
bond_attrs['bond-primary'] = self.cache.get_ifname(cached_bond_primary)
|
|
|
|
slaves = self.cache.get_slaves(bondname)
|
|
if slaves:
|
|
bond_attrs['bond-slaves'] = slaves
|
|
return bond_attrs
|
|
|
|
def _query_running(self, ifaceobjrunning, ifaceobj_getfunc=None):
|
|
if not self.cache.bond_exists(ifaceobjrunning.name):
|
|
return
|
|
bond_attrs = self._query_running_attrs(ifaceobjrunning.name)
|
|
if bond_attrs.get('bond-slaves'):
|
|
bond_attrs['bond-slaves'] = ' '.join(bond_attrs.get('bond-slaves'))
|
|
|
|
[ifaceobjrunning.update_config(k, str(v))
|
|
for k, v in list(bond_attrs.items())
|
|
if v is not None]
|
|
|
|
_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 list(self._run_ops.keys())
|
|
|
|
def run(self, ifaceobj, operation, query_ifaceobj=None,
|
|
ifaceobj_getfunc=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
|
|
if operation == 'query-checkcurr':
|
|
op_handler(self, ifaceobj, query_ifaceobj,
|
|
ifaceobj_getfunc=ifaceobj_getfunc)
|
|
else:
|
|
op_handler(self, ifaceobj, ifaceobj_getfunc=ifaceobj_getfunc)
|