Add the ability to set any mode/lb on bonds

This requires a schema change, adding 'mode' and 'load-balance' fields,
a semantic invariant that 'load-balance' can only be set in the case of
LACP and XOR bonds, a mapper from the mode/lb strings, ie.
"round-robin" to their VPP numeric counterparts, a bunch of unit tests.

Then in the reconciler, changing bonds (__bond_has_diff()) will
invalidate any LCP or sub-interfaces built on them, so those will have
to be pruned. create_bondethernet() will now create (or re-create)
the bond with the correct flags.

Unit-tests, YAML tests and the integration test all pass.

Updated config-guide.
This commit is contained in:
Pim van Pelt
2022-04-05 15:05:03 +00:00
parent 289138da94
commit 2360d28d0a
11 changed files with 241 additions and 13 deletions

View File

@ -236,6 +236,33 @@ class Reconciler():
self.vpp.cache_remove_l2xc(l2xc)
return True
def __bond_has_diff(self, ifname):
""" Returns True if the given ifname (BondEthernet0) have different attributes,
or if either does not exist.
Returns False if they are identical.
"""
if not ifname in self.vpp.cache['interface_names']:
return True
vpp_iface = self.vpp.cache['interface_names'][ifname]
if not vpp_iface.sw_if_index in self.vpp.cache['bondethernets']:
return True
config_ifname, config_iface = bondethernet.get_by_name(self.cfg, ifname)
if not config_iface:
return True
vpp_bond = self.vpp.cache['bondethernets'][vpp_iface.sw_if_index]
mode = bondethernet.mode_to_int(bondethernet.get_mode(self.cfg, config_ifname))
if mode != vpp_bond.mode:
return True
lb = bondethernet.lb_to_int(bondethernet.get_lb(self.cfg, config_ifname))
if lb != vpp_bond.lb:
return True
return False
def prune_bondethernets(self):
""" Remove all BondEthernets from VPP, if they are not in the config. If the bond has members,
remove those from the bond before removing the bond. """
@ -244,7 +271,8 @@ class Reconciler():
for idx, bond in self.vpp.cache['bondethernets'].items():
vpp_ifname = bond.interface_name
config_ifname, config_iface = bondethernet.get_by_name(self.cfg, vpp_ifname)
if not config_iface:
if self.__bond_has_diff(vpp_ifname):
self.prune_addresses(vpp_ifname, [])
for member in self.vpp.cache['bondethernet_members'][idx]:
member_ifname = self.vpp.cache['interfaces'][member].interface_name
@ -255,6 +283,7 @@ class Reconciler():
self.cli['prune'].append(cli);
removed_interfaces.append(vpp_ifname)
continue
for member in self.vpp.cache['bondethernet_members'][idx]:
member_ifname = self.vpp.cache['interfaces'][member].interface_name
if 'interfaces' in config_iface and not member_ifname in config_iface['interfaces']:
@ -326,7 +355,8 @@ class Reconciler():
return match
def prune_sub_interfaces(self):
""" Remove interfaces from VPP if they are not in the config, or if their encapsulation is different.
""" Remove interfaces from VPP if they are not in the config, if their encapsulation is different,
or if the BondEthernet they reside on is different.
Start with inner-most (QinQ/QinAD), then Dot1Q/Dot1AD."""
removed_interfaces=[]
for numtags in [ 2, 1 ]:
@ -338,21 +368,27 @@ class Reconciler():
if self.__tap_is_lcp(vpp_iface.sw_if_index):
continue
prune=False
config_ifname, config_iface = interface.get_by_name(self.cfg, vpp_ifname)
if not config_iface:
self.prune_addresses(vpp_ifname, [])
cli="delete sub %s" % (vpp_ifname)
self.cli['prune'].append(cli);
removed_interfaces.append(vpp_ifname)
continue
prune = True
elif vpp_iface.interface_dev_type=='bond' and vpp_iface.sub_number_of_tags > 0:
config_parent_ifname, config_parent_iface = interface.get_parent_by_name(self.cfg, vpp_ifname)
if self.__bond_has_diff(config_parent_ifname):
prune = True
config_encap = interface.get_encapsulation(self.cfg, vpp_ifname)
vpp_encap = self.__get_encapsulation(vpp_iface)
if config_encap != vpp_encap:
prune = True
if prune:
self.prune_addresses(vpp_ifname, [])
cli="delete sub %s" % (vpp_ifname)
self.cli['prune'].append(cli);
removed_interfaces.append(vpp_ifname)
continue
addresses = []
if 'addresses' in config_iface:
addresses = config_iface['addresses']
@ -482,7 +518,7 @@ class Reconciler():
removed_lcps.append(lcp.host_if_name)
continue
if vpp_iface.sub_number_of_tags > 1:
if vpp_iface.sub_number_of_tags > 0:
config_encap = interface.get_encapsulation(self.cfg, config_ifname)
vpp_encap = self.__get_encapsulation(vpp_iface)
if config_encap != vpp_encap:
@ -495,6 +531,14 @@ class Reconciler():
if vpp_iface.interface_dev_type=='Loopback':
## Loopbacks will not have a PHY to check.
continue
if vpp_iface.interface_dev_type=='bond':
bond_iface = self.vpp.cache['interfaces'][vpp_iface.sup_sw_if_index]
if self.__bond_has_diff(bond_iface.interface_name):
## If BondEthernet changed, it has to be re-created, so all LCPs must be removed.
cli="lcp delete %s" % (vpp_iface.interface_name)
self.cli['prune'].append(cli);
removed_lcps.append(lcp.host_if_name)
continue
phy_lcp = lcps[vpp_iface.sup_sw_if_index]
config_phy_ifname, config_phy_iface = interface.get_by_lcp_name(self.cfg, phy_lcp.host_if_name)
@ -578,7 +622,11 @@ class Reconciler():
continue
ifname, iface = bondethernet.get_by_name(self.cfg, ifname)
instance = int(ifname[12:])
cli="create bond mode lacp load-balance l34 id %d" % (instance)
mode = bondethernet.get_mode(self.cfg, ifname)
cli="create bond id %d mode %s" % (instance, mode)
lb = bondethernet.get_lb(self.cfg, ifname)
if lb:
cli += " load-balance %s" % lb
self.cli['create'].append(cli);
return True