diff --git a/vpp/reconciler.py b/vpp/reconciler.py index 9acbc36..db252af 100644 --- a/vpp/reconciler.py +++ b/vpp/reconciler.py @@ -30,12 +30,14 @@ class Reconciler(): self.vpp = VPPApi() self.cfg = cfg - def readconfig(self): - return self.vpp.readconfig() - def phys_exist(self, ifname_list): """ Return True if all interfaces in the `ifname_list` exist as physical interface names in VPP. Return False otherwise.""" + + if not self.vpp.config_read and not self.vpp.readconfig(): + self.logger.error("Could not read configuration from VPP") + return False + ret = True for ifname in ifname_list: if not ifname in self.vpp.config['interface_names']: @@ -47,8 +49,12 @@ class Reconciler(): """ Remove all objects from VPP that do not occur in the config. For an indepth explanation of how and why this particular pruning order is chosen, see README.md section on Reconciling. """ + if not self.vpp.config_read and not self.vpp.readconfig(): + self.logger.error("Could not read configuration from VPP") + return False + ret = True - if not self.prune_interfaces_down(): + if not self.prune_admin_state(): self.logger.warning("Could not set interfaces down in VPP") ret = False if not self.prune_lcps(): @@ -338,7 +344,7 @@ class Reconciler(): vpp_iface = self.vpp.config['interface_names'][vpp_ifname] config_ifname, config_iface = interface.get_by_name(self.cfg, vpp_ifname) if not config_iface: - ## Interfaces were sent DOWN in the prune_interfaces_down() step previously + ## Interfaces were sent DOWN in the prune_admin_state() step previously self.prune_addresses(vpp_ifname, []) if vpp_iface.link_mtu != 9000: self.logger.info("1> set interface mtu 9000 %s" % vpp_ifname) @@ -540,7 +546,7 @@ class Reconciler(): self.vpp.remove_lcp(lcpname) return True - def prune_interfaces_down(self): + def prune_admin_state(self): """ Set admin-state down for all interfaces that are not in the config. """ 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): @@ -563,6 +569,10 @@ class Reconciler(): """ Create all objects in VPP that occur in the config but not in VPP. For an indepth explanation of how and why this particular pruning order is chosen, see README.md section on Reconciling. """ + if not self.vpp.config_read and not self.vpp.readconfig(): + self.logger.error("Could not read configuration from VPP") + return False + ret = True if not self.create_loopbacks(): self.logger.warning("Could not create Loopbacks in VPP") @@ -688,4 +698,89 @@ class Reconciler(): return True def sync(self): + if not self.vpp.readconfig(): + self.logger.error("Could not (re)read config from VPP") + return False + + ret = True + if not self.sync_bondethernets(): + self.logger.warning("Could not sync bondethernets in VPP") + ret = False + if not self.sync_bridgedomains(): + self.logger.warning("Could not sync bridgedomains in VPP") + ret = False + if not self.sync_l2xcs(): + self.logger.warning("Could not sync L2 Cross Connects in VPP") + ret = False + if not self.sync_mtu(): + self.logger.warning("Could not sync interface MTU in VPP") + ret = False + if not self.sync_addresses(): + self.logger.warning("Could not sync interface addresses in VPP") + ret = False + if not self.sync_admin_state(): + self.logger.warning("Could not sync interface adminstate in VPP") + ret = False + + return ret + + def sync_bondethernets(self): + for idx, bond in self.vpp.config['bondethernets'].items(): + vpp_ifname = bond.interface_name + config_bond_ifname, config_bond_iface = bondethernet.get_by_name(self.cfg, vpp_ifname) + if not 'interfaces' in config_bond_iface: + continue + bond_iface = self.vpp.config['interfaces'][bond.sw_if_index] + config_mtu = interface.get_mtu(self.cfg, config_bond_ifname) + bondmac = bond_iface.l2_address + bondmac_changed = False + for member_ifname in sorted(config_bond_iface['interfaces']): + member_iface = self.vpp.config['interface_names'][member_ifname] + if not member_iface.sw_if_index in self.vpp.config['bondethernet_members'][bond.sw_if_index]: + if bond.members == 0 and member_iface.l2_address != bondmac: + bondmac_changed = True + bondmac = member_iface.l2_address + self.logger.info("2> bond add %s %s" % (vpp_ifname, member_iface.interface_name)) + if member_iface.link_mtu != config_mtu: + self.logger.info("1> set interface state %s down" % (member_iface.interface_name)) + self.logger.info("1> set interface mtu %d %s" % (config_mtu, member_iface.interface_name)) + + if not member_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP + self.logger.info("2> set interface state %s up" % (member_iface.interface_name)) + if bond_iface.link_mtu < config_mtu: + self.logger.warning("%s has a Max Frame Size (%d) lower than desired MTU (%d), this is unsupported" % + (vpp_ifname, bond_iface.link_mtu, config_mtu)) + + if bond_iface.mtu[0] != config_mtu: + ## NOTE(pim) - VPP does not allow the link_mtu to change on a BondEthernet, so it can be the + ## case that packet MTU > link_mtu if the desired MTU > 9000. This is because BondEthernets + ## are always created with link_mtu 9000. + self.logger.info("3> set interface mtu packet %d %s" % (config_mtu, bond_iface.interface_name)) + + config_ifname, config_iface = interface.get_by_name(self.cfg, vpp_ifname) + if bondmac_changed and 'lcp' in config_iface: + ## TODO(pim) - Ensure LCP has the same MAC as the BondEthernet + ## VPP, when creating a BondEthernet, will give it an ephemeral MAC. Then, when the + ## first member is enslaved, the MAC address changes to that of the first member. + ## However, LinuxCP does not propagate this change to the Linux side (because there + ## is no API callback for MAC address changes). To ensure consistency, every time we + ## sync members, we ought to ensure the Linux device has the same MAC as its BondEthernet. + self.logger.info("1> comment { ip link set %s address %s }" % (config_iface['lcp'], str(bondmac))) + + return True + + def sync_bridgedomains(self): return False + + def sync_l2xcs(self): + return False + + def sync_mtu(self): + return False + + def sync_addresses(self): + return False + + def sync_admin_state(self): + return False + diff --git a/vpp/vppapi.py b/vpp/vppapi.py index 1b89e22..50af1ab 100644 --- a/vpp/vppapi.py +++ b/vpp/vppapi.py @@ -19,7 +19,7 @@ class VPPApi(): self.clientname = clientname self.vpp = None self.config = self.clearconfig() - + self.config_read = False def connect(self): if self.connected: @@ -60,6 +60,7 @@ class VPPApi(): return True def clearconfig(self): + self.config_read = False return {"lcps": {}, "interface_names": {}, "interfaces": {}, "interface_addresses": {}, "bondethernets": {}, "bondethernet_members": {}, "bridgedomains": {}, "vxlan_tunnels": {}, "l2xcs": {}} @@ -133,12 +134,12 @@ class VPPApi(): 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") return False + self.config_read = False self.logger.debug("Retrieving LCPs") r = self.vpp.api.lcp_itf_pair_get() if isinstance(r, tuple) and r[0].retval == 0: @@ -183,7 +184,8 @@ class VPPApi(): for l2xc in r: self.config['l2xcs'][l2xc.rx_sw_if_index] = l2xc - return True + self.config_read = True + return self.config_read def get_encapsulation(self, iface): """ Return a string with the encapsulation of a subint """ diff --git a/vppcfg b/vppcfg index 934a323..f6f6bed 100755 --- a/vppcfg +++ b/vppcfg @@ -60,30 +60,26 @@ def main(): sys.exit(-2) r = Reconciler(cfg) - if not r.readconfig(): - logging.error("Couldn't read config from VPP") - sys.exit(-3) - if not r.phys_exist(interface.get_phys(cfg)): logging.error("Not all PHYs in the config exist in VPP") - sys.exit(-4) + sys.exit(-3) if not r.prune(): if not args.force: logging.error("Reconciliation prune failure") - sys.exit(-5) + sys.exit(-4) logging.warning("Reconciliation prune failure, continuing due to --force") if not r.create(): if not args.force: logging.error("Reconciliation create failure") - sys.exit(-6) + sys.exit(-5) logging.warning("Reconciliation create failure, continuing due to --force") if not r.sync(): if not args.force: logging.error("Reconciliation sync failure") - sys.exit(-7) + sys.exit(-6) logging.warning("Reconciliation sync failure, continuing due to --force")