Implement pruning in config as well

After pruning elements from the VPP configuration, also remove them
from the configuration. The purpose of this is to leave a reasonable
representation of the VPP state in the configuration, so that subsequent
creates and syncs do not have to query the VPP API repeatedly.

The goal of this change is to be able to plan a complete path from
prune, create and sync, with only reading the initial VPP configuration
once, not multiple times.
This commit is contained in:
Pim van Pelt
2022-03-25 23:04:28 +00:00
parent 1d7fd268e0
commit 661c7fc16c
2 changed files with 178 additions and 6 deletions

View File

@ -78,6 +78,24 @@ class Reconciler():
if not self.prune_phys():
self.logger.warning("Could not prune PHYs from VPP")
ret = False
## Report on what is left in the configuration after pruning.
self.logger.debug("After pruning, the following config is left:")
for idx, lcp in self.vpp.config['lcps'].items():
self.logger.debug("LCP[%d]: %s" % (idx, lcp))
for ifname, iface in self.vpp.config['interface_names'].items():
self.logger.debug("Interface[%s]: %s" % (ifname, iface))
for idx, iface in self.vpp.config['interfaces'].items():
self.logger.debug("Interface[%d]: %s" % (idx, iface))
for idx, iface in self.vpp.config['bondethernets'].items():
self.logger.debug("bondethernets[%d]: %s" % (idx, iface))
for idx, iface in self.vpp.config['bondethernet_members'].items():
self.logger.debug("bondethernet_members[%d]: %s" % (idx, iface))
for idx, iface in self.vpp.config['vxlan_tunnels'].items():
self.logger.debug("vxlan_tunnels[%d]: %s" % (idx, iface))
for idx, iface in self.vpp.config['l2xcs'].items():
self.logger.debug("l2xcs[%d]: %s" % (idx, iface))
return ret
def prune_addresses(self, ifname, address_list):
@ -85,14 +103,19 @@ class Reconciler():
which may be an empty list, in which case all addresses are removed.
"""
idx = self.vpp.config['interface_names'][ifname].sw_if_index
removed_addresses = []
for a in self.vpp.config['interface_addresses'][idx]:
if not a in address_list:
self.logger.info("1> set interface ip address del %s %s" % (ifname, a))
removed_addresses.append(a)
else:
self.logger.debug("Address OK: %s %s" % (ifname, a))
for a in removed_addresses:
self.vpp.config['interface_addresses'][idx].remove(a)
def prune_loopbacks(self):
""" Remove loopbacks from VPP, if they do not occur in the config. """
removed_interfaces=[]
for numtags in [ 2, 1, 0 ]:
for idx, vpp_iface in self.vpp.config['interfaces'].items():
if vpp_iface.interface_dev_type!='Loopback':
@ -104,18 +127,25 @@ class Reconciler():
self.prune_addresses(vpp_iface.interface_name, [])
if numtags == 0:
self.logger.info("1> delete loopback interface intfc %s" % vpp_iface.interface_name)
removed_interfaces.append(vpp_iface.interface_name)
else:
self.logger.info("1> delete sub %s" % vpp_iface.interface_name)
removed_interfaces.append(vpp_iface.interface_name)
continue
self.logger.debug("Loopback OK: %s" % (vpp_iface.interface_name))
addresses = []
if 'addresses' in config_iface:
addresses = config_iface['addresses']
self.prune_addresses(vpp_iface.interface_name, addresses)
for ifname in removed_interfaces:
self.vpp.remove_interface(ifname)
return True
def prune_bvis(self):
""" Remove BVIs (bridge-domain virtual interfaces) from VPP, if they do not occur in the config. """
removed_interfaces=[]
for numtags in [ 2, 1, 0 ]:
for idx, vpp_iface in self.vpp.config['interfaces'].items():
if vpp_iface.interface_dev_type!='BVI':
@ -127,16 +157,23 @@ class Reconciler():
self.prune_addresses(vpp_iface.interface_name, [])
if numtags == 0:
self.logger.info("1> bvi delete %s" % vpp_iface.interface_name)
removed_interfaces.append(vpp_iface.interface_name)
else:
self.logger.info("1> delete sub %s" % vpp_iface.interface_name)
removed_interfaces.append(vpp_iface.interface_name)
continue
self.logger.debug("BVI OK: %s" % (vpp_iface.interface_name))
addresses = []
if 'addresses' in config_iface:
addresses = config_iface['addresses']
self.prune_addresses(vpp_iface.interface_name, addresses)
for ifname in removed_interfaces:
self.vpp.remove_interface(ifname)
return True
def prune_bridgedomains(self):
""" Remove bridge-domains from VPP, if they do not occur in the config. If any interfaces are
found in to-be removed bridge-domains, they are returned to L3 mode, and tag-rewrites removed. """
@ -166,6 +203,7 @@ class Reconciler():
""" Remove all L2XC source interfaces from VPP, if they do not occur in the config. If they occur,
but are crossconnected to a different interface name, also remove them. Interfaces are put
back into L3 mode, and their tag-rewrites removed. """
removed_l2xcs=[]
for idx, l2xc in self.vpp.config['l2xcs'].items():
vpp_rx_ifname = self.vpp.config['interfaces'][l2xc.rx_sw_if_index].interface_name
config_rx_ifname, config_rx_iface = interface.get_by_name(self.cfg, vpp_rx_ifname)
@ -173,69 +211,96 @@ class Reconciler():
if self.vpp.config['interfaces'][l2xc.rx_sw_if_index].sub_id > 0:
self.logger.info("1> set interface l2 tag-rewrite %s disable" % vpp_rx_ifname)
self.logger.info("1> set interface l3 %s" % vpp_rx_ifname)
removed_l2xcs.append(vpp_rx_ifname)
continue
if not interface.is_l2xc_interface(self.cfg, config_rx_ifname):
if interface.is_sub(self.cfg, config_rx_ifname):
self.logger.info("2> set interface l2 tag-rewrite %s disable" % vpp_rx_ifname)
self.logger.info("2> set interface l3 %s" % vpp_rx_ifname)
removed_l2xcs.append(vpp_rx_ifname)
continue
vpp_tx_ifname = self.vpp.config['interfaces'][l2xc.tx_sw_if_index].interface_name
if vpp_tx_ifname != config_rx_iface['l2xc']:
if interface.is_sub(self.cfg, config_rx_ifname):
self.logger.info("3> set interface l2 tag-rewrite %s disable" % vpp_rx_ifname)
self.logger.info("3> set interface l3 %s" % vpp_rx_ifname)
removed_l2xcs.append(vpp_rx_ifname)
continue
self.logger.debug("L2XC OK: %s -> %s" % (vpp_rx_ifname, vpp_tx_ifname))
for l2xc in removed_l2xcs:
self.vpp.remove_l2xc(l2xc)
return True
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. """
removed_interfaces=[]
removed_bondethernet_members=[]
for idx, bond in self.vpp.config['bondethernets'].items():
vpp_ifname = bond.interface_name
config_ifname, config_iface = bondethernet.get_by_name(self.cfg, vpp_ifname)
if not config_iface:
self.prune_addresses(vpp_ifname, [])
for member in self.vpp.config['bondethernet_members'][idx]:
self.logger.info("1> bond del %s" % self.vpp.config['interfaces'][member].interface_name)
member_ifname = self.vpp.config['interfaces'][member].interface_name
self.logger.info("1> bond del %s" % member_ifname)
removed_bondethernet_members.append(member_ifname)
self.logger.info("1> delete bond %s" % (vpp_ifname))
removed_interfaces.append(vpp_ifname)
continue
for member in self.vpp.config['bondethernet_members'][idx]:
member_ifname = self.vpp.config['interfaces'][member].interface_name
if 'interfaces' in config_iface and not member_ifname in config_iface['interfaces']:
self.logger.info("2> bond del %s" % member_ifname)
removed_bondethernet_members.append(member_ifname)
addresses = []
if 'addresses' in config_iface:
addresses = config_iface['addresses']
self.prune_addresses(vpp_ifname, addresses)
self.logger.debug("BondEthernet OK: %s" % (vpp_ifname))
for ifname in removed_bondethernet_members:
self.vpp.remove_bondethernet_member(ifname)
for ifname in removed_interfaces:
self.vpp.remove_interface(ifname)
return True
def prune_vxlan_tunnels(self):
""" Remove all VXLAN Tunnels from VPP, if they are not in the config. If they are in the config
but with differing attributes, remove them also. """
removed_interfaces=[]
for idx, vpp_vxlan in self.vpp.config['vxlan_tunnels'].items():
vpp_ifname = self.vpp.config['interfaces'][idx].interface_name
config_ifname, config_iface = vxlan_tunnel.get_by_name(self.cfg, vpp_ifname)
if not config_iface:
self.logger.info("1> create vxlan tunnel instance %d src %s dst %s vni %d del" % (vpp_vxlan.instance,
vpp_vxlan.src_address, vpp_vxlan.dst_address, vpp_vxlan.vni))
removed_interfaces.append(vpp_ifname)
continue
if config_iface['local'] != str(vpp_vxlan.src_address) or config_iface['remote'] != str(vpp_vxlan.dst_address) or config_iface['vni'] != vpp_vxlan.vni:
self.logger.info("2> create vxlan tunnel instance %d src %s dst %s vni %d del" % (vpp_vxlan.instance,
vpp_vxlan.src_address, vpp_vxlan.dst_address, vpp_vxlan.vni))
removed_interfaces.append(vpp_ifname)
continue
addresses = []
if 'addresses' in config_iface:
addresses = config_iface['addresses']
self.prune_addresses(vpp_ifname, addresses)
self.logger.debug("VXLAN Tunnel OK: %s" % (vpp_ifname))
for ifname in removed_interfaces:
self.vpp.remove_vxlan_tunnel(ifname)
self.vpp.remove_interface(ifname)
return True
def prune_sub_interfaces(self):
""" Remove interfaces from VPP if they are not in the config. Start with inner-most (QinQ/QinAD), then
Dot1Q/Dot1AD."""
removed_interfaces=[]
for numtags in [ 2, 1 ]:
for vpp_ifname in self.vpp.get_sub_interfaces():
vpp_iface = self.vpp.config['interface_names'][vpp_ifname]
@ -245,12 +310,17 @@ class Reconciler():
if not config_iface:
self.prune_addresses(vpp_ifname, [])
self.logger.info("1> delete sub %s" % vpp_ifname)
removed_interfaces.append(vpp_ifname)
continue
addresses = []
if 'addresses' in config_iface:
addresses = config_iface['addresses']
self.prune_addresses(vpp_ifname, addresses)
self.logger.debug("Sub Interface OK: %s" % (vpp_ifname))
for ifname in removed_interfaces:
self.vpp.remove_interface(ifname)
return True
def prune_phys(self):
@ -316,6 +386,7 @@ class Reconciler():
"""
lcps = self.vpp.config['lcps']
removed_lcps = []
## Remove LCPs for QinX interfaces
for idx, lcp in lcps.items():
vpp_iface = self.vpp.config['interfaces'][lcp.phy_sw_if_index]
@ -325,10 +396,12 @@ class Reconciler():
if not config_iface:
## QinX doesn't exist in the config
self.logger.info("1> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
if not 'lcp' in config_iface:
## QinX doesn't have an LCP
self.logger.info("2> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
vpp_parent_idx = self.__parent_iface_by_encap(vpp_iface.sup_sw_if_index, vpp_iface.sub_outer_vlan_id, vpp_iface.sub_if_flags&8)
vpp_parent_iface = self.vpp.config['interfaces'][vpp_parent_idx]
@ -337,14 +410,17 @@ class Reconciler():
if not config_parent_iface:
## QinX's parent doesn't exist in the config
self.logger.info("3> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
if not 'lcp' in config_parent_iface:
## QinX's parent doesn't have an LCP
self.logger.info("4> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
if parent_lcp.host_if_name != config_parent_iface['lcp']:
## QinX's parent LCP name mismatch
self.logger.info("5> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
phy_lcp = lcps[vpp_iface.sup_sw_if_index]
@ -352,14 +428,17 @@ class Reconciler():
if not config_phy_iface:
## QinX's phy doesn't exist in the config
self.logger.info("6> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
if not 'lcp' in config_phy_iface:
## QinX's phy doesn't have an LCP
self.logger.info("6> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
if phy_lcp.host_if_name != config_phy_iface['lcp']:
## QinX's phy LCP name mismatch
self.logger.info("7> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
config_encap = interface.get_encapsulation(self.cfg, config_ifname)
@ -369,10 +448,12 @@ class Reconciler():
if config_encap != vpp_encap:
## QinX's encapsulation mismatch
self.logger.info("8> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
if config_parent_encap != vpp_parent_encap:
## QinX's parent encapsulation mismatch
self.logger.info("9> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
self.logger.debug("QinX LCP OK: %s -> (vpp=%s, config=%s)" % (lcp.host_if_name, vpp_iface.interface_name, config_ifname))
@ -385,10 +466,12 @@ class Reconciler():
if not config_iface:
## Sub doesn't exist in the config
self.logger.info("11> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
if not 'lcp' in config_iface:
## Sub doesn't have an LCP
self.logger.info("12> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
phy_lcp = lcps[vpp_iface.sup_sw_if_index]
@ -396,22 +479,27 @@ class Reconciler():
if not config_phy_iface:
## Sub's phy doesn't exist in the config
self.logger.info("13> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
if not 'lcp' in config_phy_iface:
## Sub's phy doesn't have an LCP
self.logger.info("14> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
if phy_lcp.host_if_name != config_phy_iface['lcp']:
## Sub's phy LCP name mismatch
self.logger.info("15> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
config_encap = interface.get_encapsulation(self.cfg, config_ifname)
vpp_encap = self.__get_encapsulation(vpp_iface)
if config_encap != vpp_encap:
## Sub's encapsulation mismatch
self.logger.info("10> lcp delete %s" % vpp_iface.interface_name)
self.logger.info("16> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
self.logger.debug("Dot1Q/Dot1AD LCP OK: %s -> (vpp=%s, config=%s)" % (lcp.host_if_name, vpp_iface.interface_name, config_ifname))
## Remove LCPs for interfaces, bonds, tunnels, loops, bvis
@ -430,12 +518,17 @@ class Reconciler():
if not config_iface:
## Interface doesn't exist in the config
self.logger.info("21> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
if not 'lcp' in config_iface:
## Interface doesn't have an LCP
self.logger.info("22> lcp delete %s" % vpp_iface.interface_name)
removed_lcps.append(lcp.host_if_name)
continue
self.logger.debug("LCP OK: %s -> (vpp=%s, config=%s)" % (lcp.host_if_name, vpp_iface.interface_name, config_ifname))
for lcpname in removed_lcps:
self.vpp.remove_lcp(lcpname)
return True
def prune_interfaces_down(self):
@ -443,6 +536,15 @@ class Reconciler():
for ifname in self.vpp.get_qinx_interfaces() + self.vpp.get_dot1x_interfaces() + self.vpp.get_bondethernets() + self.vpp.get_phys() + self.vpp.get_vxlan_tunnels() + self.vpp.get_bvis() + self.vpp.get_loopbacks():
if not ifname in interface.get_interfaces(self.cfg):
iface = self.vpp.config['interface_names'][ifname]
## Skip TAP interfaces belonging to an LCP
skip = False
for idx, lcp in self.vpp.config['lcps'].items():
if iface.sw_if_index == lcp.host_sw_if_index:
skip = True
if skip:
continue
if iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP
self.logger.info("1> set interface state %s down" % ifname)

View File

@ -64,6 +64,76 @@ class VPPApi():
"bondethernets": {}, "bondethernet_members": {},
"bridgedomains": {}, "vxlan_tunnels": {}, "l2xcs": {}}
def remove_lcp(self, lcpname):
""" Removes the LCP and TAP interface, identified by lcpname, from the config. """
self.logger.info("Removing %s" % lcpname)
for idx, lcp in self.config['lcps'].items():
if lcp.host_if_name == lcpname:
found = True
break
if not found:
self.logger.warning("Trying to remove an LCP which is not in the config: %s" % lcpname)
return False
ifname = self.config['interfaces'][lcp.host_sw_if_index].interface_name
del self.config['interface_names'][ifname]
del self.config['interface_addresses'][lcp.host_sw_if_index]
del self.config['interfaces'][lcp.host_sw_if_index]
del self.config['lcps'][lcp.phy_sw_if_index]
return True
def remove_bondethernet_member(self, ifname):
""" Removes the bonderthernet member interface, identified by name, from the config. """
if not ifname in self.config['interface_names']:
self.logger.warning("Trying to remove a bondethernet member interface which is not in the config: %s" % ifname)
return False
iface = self.config['interface_names'][ifname]
for bond_idx, members in self.config['bondethernet_members'].items():
if iface.sw_if_index in members:
self.config['bondethernet_members'][bond_idx].remove(iface.sw_if_index)
return True
def remove_l2xc(self, ifname):
if not ifname in self.config['interface_names']:
self.logger.warning("Trying to remove an L2XC which is not in the config: %s" % ifname)
return False
iface = self.config['interface_names'][ifname]
self.config['l2xcs'].pop(iface.sw_if_index, None)
return True
def remove_vxlan_tunnel(self, ifname):
if not ifname in self.config['interface_names']:
self.logger.warning("Trying to remove a VXLAN Tunnel which is not in the config: %s" % ifname)
return False
iface = self.config['interface_names'][ifname]
self.config['vxlan_tunnels'].pop(iface.sw_if_index, None)
return True
def remove_interface(self, ifname):
""" Removes the interface, identified by name, from the config. """
if not ifname in self.config['interface_names']:
self.logger.warning("Trying to remove an interface which is not in the config: %s" % ifname)
return False
iface = self.config['interface_names'][ifname]
del self.config['interfaces'][iface.sw_if_index]
del self.config['interface_addresses'][iface.sw_if_index]
del self.config['interface_names'][ifname]
## Use my_dict.pop('key', None), as it allows 'key' to be absent
if iface.sw_if_index in self.config['bondethernet_members']:
if len(self.config['bondethernet_members'][iface.sw_if_index]) != 0:
self.logger.warning("When removing BondEthernet %s, its members are not empty: %s" % (ifname, self.config['bondethernet_members'][iface.sw_if_index]))
else:
del self.config['bondethernet_members'][iface.sw_if_index]
self.config['bondethernets'].pop(iface.sw_if_index, None)
return True
def readconfig(self):
if not self.connected and not self.connect():
self.logger.error("Could not connect to VPP")
@ -144,15 +214,15 @@ class VPPApi():
self.dump_subints()
def get_sub_interfaces(self):
subints = [self.config['interfaces'][x].interface_name for x in self.config['interfaces'] if self.config['interfaces'][x].interface_dev_type in ['dpdk','bond'] and self.config['interfaces'][x].sub_id>0 and self.config['interfaces'][x].sub_number_of_tags > 0]
subints = [self.config['interfaces'][x].interface_name for x in self.config['interfaces'] if self.config['interfaces'][x].sub_id>0 and self.config['interfaces'][x].sub_number_of_tags > 0]
return subints
def get_qinx_interfaces(self):
qinx_subints = [self.config['interfaces'][x].interface_name for x in self.config['interfaces'] if self.config['interfaces'][x].interface_dev_type in ['dpdk','bond'] and self.config['interfaces'][x].sub_id>0 and self.config['interfaces'][x].sub_inner_vlan_id>0]
qinx_subints = [self.config['interfaces'][x].interface_name for x in self.config['interfaces'] if self.config['interfaces'][x].sub_id>0 and self.config['interfaces'][x].sub_inner_vlan_id>0]
return qinx_subints
def get_dot1x_interfaces(self):
dot1x_subints = [self.config['interfaces'][x].interface_name for x in self.config['interfaces'] if self.config['interfaces'][x].interface_dev_type in ['dpdk','bond'] and self.config['interfaces'][x].sub_id>0 and self.config['interfaces'][x].sub_inner_vlan_id==0]
dot1x_subints = [self.config['interfaces'][x].interface_name for x in self.config['interfaces'] if self.config['interfaces'][x].sub_id>0 and self.config['interfaces'][x].sub_inner_vlan_id==0]
return dot1x_subints
def get_loopbacks(self):
@ -164,7 +234,7 @@ class VPPApi():
return bvis
def get_phys(self):
phys = [self.config['interfaces'][x].interface_name for x in self.config['interfaces'] if self.config['interfaces'][x].interface_dev_type=='dpdk' and self.config['interfaces'][x].sub_id==0]
phys = [self.config['interfaces'][x].interface_name for x in self.config['interfaces'] if self.config['interfaces'][x].interface_dev_type=='dpdk' and self.config['interfaces'][x].sw_if_index == self.config['interfaces'][x].sup_sw_if_index]
return phys
def get_bondethernets(self):