Make Sync Phase config-centric

What this means is that the synchronization will run based on loops over
the configuration, rather than over the VPP state. The benefit of this
approach is that no additional API calls have to be made after the
initial VPP configuration is read, making pre-flight checks and diffing
more obvious.

Further, make sync_link_mtu() direction-aware; first we will raise the
link_mtu for interfaces that are growing, and after setting all the
children up/down with sync_mtu_direction() we'll finally shrink
sync_link_mtu() where needed.

We can now do an entire reconciliation run with only one API read at the
beginning!
This commit is contained in:
Pim van Pelt
2022-03-26 22:18:54 +00:00
parent 619c579561
commit a2d2476710

View File

@ -721,67 +721,94 @@ class Reconciler():
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)
for ifname in bondethernet.get_bondethernets(self.cfg):
if ifname in self.vpp.config['interface_names']:
vpp_bond_sw_if_index = self.vpp.config['interface_names'][ifname].sw_if_index
vpp_members = [self.vpp.config['interfaces'][x].interface_name for x in self.vpp.config['bondethernet_members'][vpp_bond_sw_if_index]]
else:
## New BondEthernet
vpp_members = []
config_bond_ifname, config_bond_iface = bondethernet.get_by_name(self.cfg, 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
config_ifname, config_iface = interface.get_by_name(self.cfg, ifname)
bondmac = None
for member_ifname in sorted(config_bond_iface['interfaces']):
member_ifname, member_iface = interface.get_by_name(self.cfg, member_ifname)
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
if not member_ifname in vpp_members:
if len(vpp_members) == 0:
bondmac = member_iface.l2_address
self.logger.info("1> bond add %s %s" % (vpp_ifname, member_iface.interface_name))
config_ifname, config_iface = interface.get_by_name(self.cfg, vpp_ifname)
if bondmac_changed and 'lcp' in config_iface:
self.logger.info("1> bond add %s %s" % (config_bond_ifname, member_iface.interface_name))
if bondmac 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("5> comment { ip link set %s address %s }" % (config_iface['lcp'], str(bondmac)))
self.logger.info("2> comment { ip link set %s address %s }" % (config_iface['lcp'], str(bondmac)))
return True
def sync_bridgedomains(self):
for idx, bridge in self.vpp.config['bridgedomains'].items():
bridge_sw_if_index_list = [x.sw_if_index for x in bridge.sw_if_details]
config_bridge_ifname, config_bridge_iface = bridgedomain.get_by_name(self.cfg, "bd%d"%idx)
for ifname in bridgedomain.get_bridgedomains(self.cfg):
instance = int(ifname[2:])
if instance in self.vpp.config['bridgedomains']:
vpp_bridge = self.vpp.config['bridgedomains'][instance]
bvi_sw_if_index = vpp_bridge.bvi_sw_if_index
bridge_sw_if_index_list = [x.sw_if_index for x in vpp_bridge.sw_if_details]
bridge_members = [self.vpp.config['interfaces'][x].interface_name for x in bridge_sw_if_index_list]
else:
## New BridgeDomain
bvi_sw_if_index = -1
bridge_members = []
config_bridge_ifname, config_bridge_iface = bridgedomain.get_by_name(self.cfg, "bd%d"%instance)
if 'lcp' in config_bridge_iface:
bviname = "bvi%d" % idx
bvi_iface = self.vpp.config['interface_names'][bviname]
if bvi_iface.sw_if_index != bridge.bvi_sw_if_index:
self.logger.info("1> set interface l2 bridge bvi%d %d bvi" % (idx, idx))
bviname = "bvi%d" % instance
if not bviname in bridge_members:
self.logger.info("1> set interface l2 bridge bvi%d %d bvi" % (instance, instance))
if not 'interfaces' in config_bridge_iface:
continue
for member_ifname in config_bridge_iface['interfaces']:
member_iface = self.vpp.config['interface_names'][member_ifname]
if not member_iface.sw_if_index in bridge_sw_if_index_list:
self.logger.info("2> set interface l2 bridge %s %d" % (member_ifname, idx))
if member_iface.sub_number_of_tags > 0:
self.logger.info("3> set interface l2 tag-rewrite %s pop %d" % (member_ifname, member_iface.sub_number_of_tags))
member_ifname, member_iface = interface.get_by_name(self.cfg, member_ifname)
if not member_ifname in bridge_members:
self.logger.info("2> set interface l2 bridge %s %d" % (member_ifname, instance))
if interface.is_qinx(self.cfg, member_ifname):
self.logger.info("3> set interface l2 tag-rewrite %s pop 2" % (member_ifname))
elif interface.is_sub(self.cfg, member_ifname):
self.logger.info("3> set interface l2 tag-rewrite %s pop 1" % (member_ifname))
return True
def sync_l2xcs(self):
for ifname in interface.get_l2xc_interfaces(self.cfg):
config_ifname, config_iface = interface.get_by_name(self.cfg, ifname)
rx_iface = self.vpp.config['interface_names'][config_ifname]
tx_iface = self.vpp.config['interface_names'][config_iface['l2xc']]
config_rx_ifname, config_rx_iface = interface.get_by_name(self.cfg, ifname)
config_tx_ifname, config_tx_iface = interface.get_by_name(self.cfg, config_rx_iface['l2xc'])
vpp_rx_iface = None
vpp_rx_iface = None
if config_rx_ifname in self.vpp.config['interface_names']:
vpp_rx_iface = self.vpp.config['interface_names'][config_rx_ifname]
if config_tx_ifname in self.vpp.config['interface_names']:
vpp_tx_iface = self.vpp.config['interface_names'][config_tx_ifname]
l2xc_changed = False
if not rx_iface.sw_if_index in self.vpp.config['l2xcs']:
self.logger.info("1> set interface l2 xconnect %s %s" % (rx_iface.interface_name, tx_iface.interface_name))
if not vpp_rx_iface or not vpp_tx_iface:
self.logger.info("1> set interface l2 xconnect %s %s" % (config_rx_ifname, config_tx_ifname))
l2xc_changed = True
elif not tx_iface.sw_if_index == self.vpp.config['l2xcs'][rx_iface.sw_if_index].tx_sw_if_index:
self.logger.info("2> set interface l2 xconnect %s %s" % (rx_iface.interface_name, tx_iface.interface_name))
elif not vpp_rx_iface.sw_if_index in self.vpp.config['l2xcs']:
self.logger.info("2> set interface l2 xconnect %s %s" % (config_rx_ifname, config_tx_ifname))
l2xc_changed = True
if l2xc_changed and rx_iface.sub_number_of_tags > 0:
self.logger.info("3> set interface l2 tag-rewrite %s pop %d" % (rx_iface.interface_name, rx_iface.sub_number_of_tags))
elif not vpp_tx_iface.sw_if_index == self.vpp.config['l2xcs'][vpp_rx_iface.sw_if_index].tx_sw_if_index:
self.logger.info("3> set interface l2 xconnect %s %s" % (config_rx_ifname, config_tx_ifname))
l2xc_changed = True
if l2xc_changed:
tags = 0
if interface.is_qinx(self.cfg, config_rx_ifname):
tags = 2
elif interface.is_sub(self.cfg, config_rx_ifname):
tags = 1
self.logger.info("4> set interface l2 tag-rewrite %s pop %d" % (config_rx_ifname, tags))
return True
def sync_mtu_direction(self, shrink=True):
@ -791,36 +818,49 @@ class Reconciler():
tag_list = [ 0, 1, 2 ]
for numtags in tag_list:
for idx, vpp_iface in self.vpp.config['interfaces'].items():
if vpp_iface.sub_number_of_tags != numtags:
for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks(self.cfg) + bridgedomain.get_bridgedomains(self.cfg):
if numtags == 0 and interface.is_sub(self.cfg, ifname):
continue
if vpp_iface.interface_dev_type=='local':
if numtags == 1 and not interface.is_sub(self.cfg, ifname):
continue
if self.__tap_is_lcp(vpp_iface.sw_if_index):
if numtags == 1 and interface.is_qinx(self.cfg, ifname):
continue
if vpp_iface.interface_dev_type=='Loopback':
config_ifname, config_iface = loopback.get_by_name(self.cfg, vpp_iface.interface_name)
elif vpp_iface.interface_dev_type=='BVI':
config_ifname, config_iface = bridgedomain.get_by_bvi_name(self.cfg, vpp_iface.interface_name)
else:
config_ifname, config_iface = interface.get_by_name(self.cfg, vpp_iface.interface_name)
if not config_iface:
self.logger.warning("Interface %s exists in VPP but not in config, this is dangerous" % vpp_iface.interface_name)
if numtags == 2 and not interface.is_qinx(self.cfg, ifname):
continue
config_mtu = 1500
vpp_mtu = 9000
if ifname.startswith("loop"):
if ifname in self.vpp.config['interface_names']:
vpp_mtu = self.vpp.config['interface_names'][ifname].mtu[0]
config_ifname, config_iface = loopback.get_by_name(self.cfg, ifname)
if 'mtu' in config_iface:
config_mtu = config_iface['mtu']
vpp_ifname = config_ifname
elif ifname.startswith("bd"):
instance=int(ifname[2:])
bviname="bvi%d" % instance
if bviname in self.vpp.config['interface_names']:
vpp_mtu = self.vpp.config['interface_names'][bviname].mtu[0]
config_ifname, config_iface = bridgedomain.get_by_name(self.cfg, ifname)
if 'mtu' in config_iface:
config_mtu = config_iface['mtu']
vpp_ifname = bviname
else:
if numtags > 0:
vpp_mtu = 0
if ifname in self.vpp.config['interface_names']:
vpp_mtu = self.vpp.config['interface_names'][ifname].mtu[0]
config_ifname, config_iface = interface.get_by_name(self.cfg, ifname)
config_mtu = interface.get_mtu(self.cfg, ifname)
vpp_ifname = config_ifname
if shrink and config_mtu < vpp_iface.mtu[0]:
self.logger.info("1> set interface mtu packet %d %s" % (config_mtu, vpp_iface.interface_name))
elif not shrink and config_mtu > vpp_iface.mtu[0]:
self.logger.info("2> set interface mtu packet %d %s" % (config_mtu, vpp_iface.interface_name))
if shrink and config_mtu < vpp_mtu:
self.logger.info("1> set interface mtu packet %d %s" % (config_mtu, vpp_ifname))
elif not shrink and config_mtu > vpp_mtu:
self.logger.info("2> set interface mtu packet %d %s" % (config_mtu, vpp_ifname))
return True
def sync_link_mtu(self):
def sync_link_mtu_direction(self, shrink=True):
for idx, vpp_iface in self.vpp.config['interfaces'].items():
if vpp_iface.sub_number_of_tags != 0:
continue
@ -831,6 +871,8 @@ class Reconciler():
if not config_iface:
self.logger.warning("Interface %s exists in VPP but not in config, this is dangerous" % vpp_iface.interface_name)
continue
if not interface.is_phy(self.cfg, vpp_iface.interface_name):
continue
config_mtu = interface.get_mtu(self.cfg, vpp_iface.interface_name)
if vpp_iface.interface_dev_type=='bond' and vpp_iface.link_mtu < config_mtu:
@ -838,21 +880,30 @@ class Reconciler():
(vpp_iface.interface_name, vpp_iface.link_mtu, config_mtu))
continue
if interface.is_phy(self.cfg, vpp_iface.interface_name) and config_mtu != vpp_iface.link_mtu:
if shrink and config_mtu < vpp_iface.link_mtu:
## If the interface is up, temporarily down it in order to change the Max Frame Size
if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP
self.logger.info("2> set interface state %s down" % (vpp_iface.interface_name))
self.logger.info("1> set interface state %s down" % (vpp_iface.interface_name))
self.logger.info("2> set interface mtu %d %s" % (config_mtu, vpp_iface.interface_name))
self.logger.info("1> set interface mtu %d %s" % (config_mtu, vpp_iface.interface_name))
if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP
self.logger.info("2> set interface state %s up" % (vpp_iface.interface_name))
self.logger.info("1> set interface state %s up" % (vpp_iface.interface_name))
elif not shrink and config_mtu > vpp_iface.link_mtu:
## If the interface is up, temporarily down it in order to change the Max Frame Size
if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP
self.logger.info("1> set interface state %s down" % (vpp_iface.interface_name))
self.logger.info("1> set interface mtu %d %s" % (config_mtu, vpp_iface.interface_name))
if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP
self.logger.info("1> set interface state %s up" % (vpp_iface.interface_name))
return True
def sync_mtu(self):
ret = True
if not self.sync_link_mtu():
self.logger.warning("Could not sync interface Max Frame Size in VPP")
if not self.sync_link_mtu_direction(shrink=False):
self.logger.warning("Could not sync growing interface Max Frame Size in VPP")
ret = False
if not self.sync_mtu_direction(shrink=True):
self.logger.warning("Could not sync shrinking interface MTU in VPP")
@ -860,6 +911,9 @@ class Reconciler():
if not self.sync_mtu_direction(shrink=False):
self.logger.warning("Could not sync growing interface MTU in VPP")
ret = False
if not self.sync_link_mtu_direction(shrink=True):
self.logger.warning("Could not sync shrinking interface Max Frame Size in VPP")
ret = False
return ret
def sync_addresses(self):