diff --git a/vpp/reconciler.py b/vpp/reconciler.py index db9868c..c317399 100644 --- a/vpp/reconciler.py +++ b/vpp/reconciler.py @@ -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) diff --git a/vpp/vppapi.py b/vpp/vppapi.py index 3f803af..ba4c627 100644 --- a/vpp/vppapi.py +++ b/vpp/vppapi.py @@ -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):