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 return ret
def sync_bondethernets(self): def sync_bondethernets(self):
for idx, bond in self.vpp.config['bondethernets'].items(): for ifname in bondethernet.get_bondethernets(self.cfg):
vpp_ifname = bond.interface_name if ifname in self.vpp.config['interface_names']:
config_bond_ifname, config_bond_iface = bondethernet.get_by_name(self.cfg, vpp_ifname) 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: if not 'interfaces' in config_bond_iface:
continue continue
bond_iface = self.vpp.config['interfaces'][bond.sw_if_index] config_ifname, config_iface = interface.get_by_name(self.cfg, ifname)
config_mtu = interface.get_mtu(self.cfg, config_bond_ifname) bondmac = None
bondmac = bond_iface.l2_address
bondmac_changed = False
for member_ifname in sorted(config_bond_iface['interfaces']): 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] 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 not member_ifname in vpp_members:
if bond.members == 0 and member_iface.l2_address != bondmac: if len(vpp_members) == 0:
bondmac_changed = True
bondmac = member_iface.l2_address bondmac = member_iface.l2_address
self.logger.info("1> bond add %s %s" % (vpp_ifname, member_iface.interface_name)) self.logger.info("1> bond add %s %s" % (config_bond_ifname, member_iface.interface_name))
if bondmac and 'lcp' in config_iface:
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 ## 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 ## 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. ## 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 ## 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 ## 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. ## 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 return True
def sync_bridgedomains(self): def sync_bridgedomains(self):
for idx, bridge in self.vpp.config['bridgedomains'].items(): for ifname in bridgedomain.get_bridgedomains(self.cfg):
bridge_sw_if_index_list = [x.sw_if_index for x in bridge.sw_if_details] instance = int(ifname[2:])
config_bridge_ifname, config_bridge_iface = bridgedomain.get_by_name(self.cfg, "bd%d"%idx) 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: if 'lcp' in config_bridge_iface:
bviname = "bvi%d" % idx bviname = "bvi%d" % instance
bvi_iface = self.vpp.config['interface_names'][bviname] if not bviname in bridge_members:
if bvi_iface.sw_if_index != bridge.bvi_sw_if_index: self.logger.info("1> set interface l2 bridge bvi%d %d bvi" % (instance, instance))
self.logger.info("1> set interface l2 bridge bvi%d %d bvi" % (idx, idx))
if not 'interfaces' in config_bridge_iface: if not 'interfaces' in config_bridge_iface:
continue continue
for member_ifname in config_bridge_iface['interfaces']: for member_ifname in config_bridge_iface['interfaces']:
member_iface = self.vpp.config['interface_names'][member_ifname] member_ifname, member_iface = interface.get_by_name(self.cfg, member_ifname)
if not member_iface.sw_if_index in bridge_sw_if_index_list: if not member_ifname in bridge_members:
self.logger.info("2> set interface l2 bridge %s %d" % (member_ifname, idx)) self.logger.info("2> set interface l2 bridge %s %d" % (member_ifname, instance))
if member_iface.sub_number_of_tags > 0: if interface.is_qinx(self.cfg, member_ifname):
self.logger.info("3> set interface l2 tag-rewrite %s pop %d" % (member_ifname, member_iface.sub_number_of_tags)) 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 return True
def sync_l2xcs(self): def sync_l2xcs(self):
for ifname in interface.get_l2xc_interfaces(self.cfg): for ifname in interface.get_l2xc_interfaces(self.cfg):
config_ifname, config_iface = interface.get_by_name(self.cfg, ifname) config_rx_ifname, config_rx_iface = interface.get_by_name(self.cfg, ifname)
rx_iface = self.vpp.config['interface_names'][config_ifname] config_tx_ifname, config_tx_iface = interface.get_by_name(self.cfg, config_rx_iface['l2xc'])
tx_iface = self.vpp.config['interface_names'][config_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 l2xc_changed = False
if not rx_iface.sw_if_index in self.vpp.config['l2xcs']: if not vpp_rx_iface or not vpp_tx_iface:
self.logger.info("1> set interface l2 xconnect %s %s" % (rx_iface.interface_name, tx_iface.interface_name)) self.logger.info("1> set interface l2 xconnect %s %s" % (config_rx_ifname, config_tx_ifname))
l2xc_changed = True l2xc_changed = True
elif not tx_iface.sw_if_index == self.vpp.config['l2xcs'][rx_iface.sw_if_index].tx_sw_if_index: elif not vpp_rx_iface.sw_if_index in self.vpp.config['l2xcs']:
self.logger.info("2> set interface l2 xconnect %s %s" % (rx_iface.interface_name, tx_iface.interface_name)) self.logger.info("2> set interface l2 xconnect %s %s" % (config_rx_ifname, config_tx_ifname))
l2xc_changed = True l2xc_changed = True
if l2xc_changed and rx_iface.sub_number_of_tags > 0: 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 tag-rewrite %s pop %d" % (rx_iface.interface_name, rx_iface.sub_number_of_tags)) 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 return True
def sync_mtu_direction(self, shrink=True): def sync_mtu_direction(self, shrink=True):
@ -791,36 +818,49 @@ class Reconciler():
tag_list = [ 0, 1, 2 ] tag_list = [ 0, 1, 2 ]
for numtags in tag_list: for numtags in tag_list:
for idx, vpp_iface in self.vpp.config['interfaces'].items(): for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks(self.cfg) + bridgedomain.get_bridgedomains(self.cfg):
if vpp_iface.sub_number_of_tags != numtags: if numtags == 0 and interface.is_sub(self.cfg, ifname):
continue continue
if vpp_iface.interface_dev_type=='local': if numtags == 1 and not interface.is_sub(self.cfg, ifname):
continue continue
if self.__tap_is_lcp(vpp_iface.sw_if_index): if numtags == 1 and interface.is_qinx(self.cfg, ifname):
continue continue
if numtags == 2 and not interface.is_qinx(self.cfg, ifname):
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)
continue continue
config_mtu = 1500 config_mtu = 1500
if 'mtu' in config_iface: vpp_mtu = 9000
config_mtu = config_iface['mtu'] if ifname.startswith("loop"):
if ifname in self.vpp.config['interface_names']:
if shrink and config_mtu < vpp_iface.mtu[0]: vpp_mtu = self.vpp.config['interface_names'][ifname].mtu[0]
self.logger.info("1> set interface mtu packet %d %s" % (config_mtu, vpp_iface.interface_name)) config_ifname, config_iface = loopback.get_by_name(self.cfg, ifname)
elif not shrink and config_mtu > vpp_iface.mtu[0]: if 'mtu' in config_iface:
self.logger.info("2> set interface mtu packet %d %s" % (config_mtu, vpp_iface.interface_name)) 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_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 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(): for idx, vpp_iface in self.vpp.config['interfaces'].items():
if vpp_iface.sub_number_of_tags != 0: if vpp_iface.sub_number_of_tags != 0:
continue continue
@ -831,6 +871,8 @@ class Reconciler():
if not config_iface: if not config_iface:
self.logger.warning("Interface %s exists in VPP but not in config, this is dangerous" % vpp_iface.interface_name) self.logger.warning("Interface %s exists in VPP but not in config, this is dangerous" % vpp_iface.interface_name)
continue continue
if not interface.is_phy(self.cfg, vpp_iface.interface_name):
continue
config_mtu = interface.get_mtu(self.cfg, vpp_iface.interface_name) 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: 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)) (vpp_iface.interface_name, vpp_iface.link_mtu, config_mtu))
continue 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 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 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 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 return True
def sync_mtu(self): def sync_mtu(self):
ret = True ret = True
if not self.sync_link_mtu(): if not self.sync_link_mtu_direction(shrink=False):
self.logger.warning("Could not sync interface Max Frame Size in VPP") self.logger.warning("Could not sync growing interface Max Frame Size in VPP")
ret = False ret = False
if not self.sync_mtu_direction(shrink=True): if not self.sync_mtu_direction(shrink=True):
self.logger.warning("Could not sync shrinking interface MTU in VPP") 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): if not self.sync_mtu_direction(shrink=False):
self.logger.warning("Could not sync growing interface MTU in VPP") self.logger.warning("Could not sync growing interface MTU in VPP")
ret = False 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 return ret
def sync_addresses(self): def sync_addresses(self):