Sync Phase: Implement bondethernets
Include special caveat on LCP MAC changes, for which I'll put in a TODO for now with a VPP comment {} with the to be run command. Also make the user aware of a quick in BondEthernets not being able to have link_mtu != 9000 so if a packet MTU > 9000 is set, this will work but is an undesirable configuration. Issue a warning in this case.
This commit is contained in:
@ -30,12 +30,14 @@ class Reconciler():
|
|||||||
self.vpp = VPPApi()
|
self.vpp = VPPApi()
|
||||||
self.cfg = cfg
|
self.cfg = cfg
|
||||||
|
|
||||||
def readconfig(self):
|
|
||||||
return self.vpp.readconfig()
|
|
||||||
|
|
||||||
def phys_exist(self, ifname_list):
|
def phys_exist(self, ifname_list):
|
||||||
""" Return True if all interfaces in the `ifname_list` exist as physical interface names
|
""" Return True if all interfaces in the `ifname_list` exist as physical interface names
|
||||||
in VPP. Return False otherwise."""
|
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
|
ret = True
|
||||||
for ifname in ifname_list:
|
for ifname in ifname_list:
|
||||||
if not ifname in self.vpp.config['interface_names']:
|
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
|
""" 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
|
of how and why this particular pruning order is chosen, see README.md section on
|
||||||
Reconciling. """
|
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
|
ret = True
|
||||||
if not self.prune_interfaces_down():
|
if not self.prune_admin_state():
|
||||||
self.logger.warning("Could not set interfaces down in VPP")
|
self.logger.warning("Could not set interfaces down in VPP")
|
||||||
ret = False
|
ret = False
|
||||||
if not self.prune_lcps():
|
if not self.prune_lcps():
|
||||||
@ -338,7 +344,7 @@ class Reconciler():
|
|||||||
vpp_iface = self.vpp.config['interface_names'][vpp_ifname]
|
vpp_iface = self.vpp.config['interface_names'][vpp_ifname]
|
||||||
config_ifname, config_iface = interface.get_by_name(self.cfg, vpp_ifname)
|
config_ifname, config_iface = interface.get_by_name(self.cfg, vpp_ifname)
|
||||||
if not config_iface:
|
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, [])
|
self.prune_addresses(vpp_ifname, [])
|
||||||
if vpp_iface.link_mtu != 9000:
|
if vpp_iface.link_mtu != 9000:
|
||||||
self.logger.info("1> set interface mtu 9000 %s" % vpp_ifname)
|
self.logger.info("1> set interface mtu 9000 %s" % vpp_ifname)
|
||||||
@ -540,7 +546,7 @@ class Reconciler():
|
|||||||
self.vpp.remove_lcp(lcpname)
|
self.vpp.remove_lcp(lcpname)
|
||||||
return True
|
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. """
|
""" 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():
|
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):
|
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
|
""" 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
|
explanation of how and why this particular pruning order is chosen, see README.md
|
||||||
section on Reconciling. """
|
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
|
ret = True
|
||||||
if not self.create_loopbacks():
|
if not self.create_loopbacks():
|
||||||
self.logger.warning("Could not create Loopbacks in VPP")
|
self.logger.warning("Could not create Loopbacks in VPP")
|
||||||
@ -688,4 +698,89 @@ class Reconciler():
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def sync(self):
|
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
|
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
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ class VPPApi():
|
|||||||
self.clientname = clientname
|
self.clientname = clientname
|
||||||
self.vpp = None
|
self.vpp = None
|
||||||
self.config = self.clearconfig()
|
self.config = self.clearconfig()
|
||||||
|
self.config_read = False
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
if self.connected:
|
if self.connected:
|
||||||
@ -60,6 +60,7 @@ class VPPApi():
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def clearconfig(self):
|
def clearconfig(self):
|
||||||
|
self.config_read = False
|
||||||
return {"lcps": {}, "interface_names": {}, "interfaces": {}, "interface_addresses": {},
|
return {"lcps": {}, "interface_names": {}, "interfaces": {}, "interface_addresses": {},
|
||||||
"bondethernets": {}, "bondethernet_members": {},
|
"bondethernets": {}, "bondethernet_members": {},
|
||||||
"bridgedomains": {}, "vxlan_tunnels": {}, "l2xcs": {}}
|
"bridgedomains": {}, "vxlan_tunnels": {}, "l2xcs": {}}
|
||||||
@ -133,12 +134,12 @@ class VPPApi():
|
|||||||
self.config['bondethernets'].pop(iface.sw_if_index, None)
|
self.config['bondethernets'].pop(iface.sw_if_index, None)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def readconfig(self):
|
def readconfig(self):
|
||||||
if not self.connected and not self.connect():
|
if not self.connected and not self.connect():
|
||||||
self.logger.error("Could not connect to VPP")
|
self.logger.error("Could not connect to VPP")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
self.config_read = False
|
||||||
self.logger.debug("Retrieving LCPs")
|
self.logger.debug("Retrieving LCPs")
|
||||||
r = self.vpp.api.lcp_itf_pair_get()
|
r = self.vpp.api.lcp_itf_pair_get()
|
||||||
if isinstance(r, tuple) and r[0].retval == 0:
|
if isinstance(r, tuple) and r[0].retval == 0:
|
||||||
@ -183,7 +184,8 @@ class VPPApi():
|
|||||||
for l2xc in r:
|
for l2xc in r:
|
||||||
self.config['l2xcs'][l2xc.rx_sw_if_index] = l2xc
|
self.config['l2xcs'][l2xc.rx_sw_if_index] = l2xc
|
||||||
|
|
||||||
return True
|
self.config_read = True
|
||||||
|
return self.config_read
|
||||||
|
|
||||||
def get_encapsulation(self, iface):
|
def get_encapsulation(self, iface):
|
||||||
""" Return a string with the encapsulation of a subint """
|
""" Return a string with the encapsulation of a subint """
|
||||||
|
12
vppcfg
12
vppcfg
@ -60,30 +60,26 @@ def main():
|
|||||||
sys.exit(-2)
|
sys.exit(-2)
|
||||||
|
|
||||||
r = Reconciler(cfg)
|
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)):
|
if not r.phys_exist(interface.get_phys(cfg)):
|
||||||
logging.error("Not all PHYs in the config exist in VPP")
|
logging.error("Not all PHYs in the config exist in VPP")
|
||||||
sys.exit(-4)
|
sys.exit(-3)
|
||||||
|
|
||||||
if not r.prune():
|
if not r.prune():
|
||||||
if not args.force:
|
if not args.force:
|
||||||
logging.error("Reconciliation prune failure")
|
logging.error("Reconciliation prune failure")
|
||||||
sys.exit(-5)
|
sys.exit(-4)
|
||||||
logging.warning("Reconciliation prune failure, continuing due to --force")
|
logging.warning("Reconciliation prune failure, continuing due to --force")
|
||||||
|
|
||||||
if not r.create():
|
if not r.create():
|
||||||
if not args.force:
|
if not args.force:
|
||||||
logging.error("Reconciliation create failure")
|
logging.error("Reconciliation create failure")
|
||||||
sys.exit(-6)
|
sys.exit(-5)
|
||||||
logging.warning("Reconciliation create failure, continuing due to --force")
|
logging.warning("Reconciliation create failure, continuing due to --force")
|
||||||
|
|
||||||
if not r.sync():
|
if not r.sync():
|
||||||
if not args.force:
|
if not args.force:
|
||||||
logging.error("Reconciliation sync failure")
|
logging.error("Reconciliation sync failure")
|
||||||
sys.exit(-7)
|
sys.exit(-6)
|
||||||
logging.warning("Reconciliation sync failure, continuing due to --force")
|
logging.warning("Reconciliation sync failure, continuing due to --force")
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user