diff --git a/intest/hippo11.yaml b/intest/hippo11.yaml index 45d8caf..82035b5 100644 --- a/intest/hippo11.yaml +++ b/intest/hippo11.yaml @@ -11,7 +11,8 @@ interfaces: description: "LAG #2" HundredGigabitEthernet12/0/0: - description: Not Used + mtu: 1500 + description: "bridged with tap" HundredGigabitEthernet12/0/1: description: Not Used @@ -42,3 +43,23 @@ interfaces: encapsulation: dot1ad: 501 exact-match: False + tap100: + mtu: 1500 + +loopbacks: + loop100: + lcp: "bvi100" + addresses: [ 10.1.2.1/24 ] + +bridgedomains: + bd100: + description: "Bridge Domain 100" + mtu: 1500 + bvi: loop100 + interfaces: [ HundredGigabitEthernet12/0/0, tap100 ] + +taps: + tap100: + host: + name: vpp-tap100 + mtu: 1500 diff --git a/intest/hippo12.yaml b/intest/hippo12.yaml index d198bc3..e51e19b 100644 --- a/intest/hippo12.yaml +++ b/intest/hippo12.yaml @@ -66,6 +66,8 @@ interfaces: exact-match: False vxlan_tunnel1: mtu: 2000 + tap100: + mtu: 9000 loopbacks: loop0: @@ -90,3 +92,9 @@ vxlan_tunnels: local: 192.0.2.1 remote: 192.0.2.2 vni: 101 + +taps: + tap100: + host: + name: vpp-tap + mtu: 9000 diff --git a/intest/hippo14.yaml b/intest/hippo14.yaml new file mode 100644 index 0000000..6cb0927 --- /dev/null +++ b/intest/hippo14.yaml @@ -0,0 +1,34 @@ +interfaces: + GigabitEthernet3/0/0: + mtu: 9000 + state: up + sub-interfaces: + 100: + mtu: 9000 + l2xc: tap100 + GigabitEthernet3/0/1: + mtu: 1500 + mac: 00:25:90:0c:05:01 + state: down + description: Not Used + HundredGigabitEthernet12/0/0: + mtu: 1500 + mac: b4:96:91:b3:b1:10 + state: down + description: Not Used + HundredGigabitEthernet12/0/1: + mtu: 1500 + mac: b4:96:91:b3:b1:11 + state: down + description: Not Used + + tap100: + mtu: 9000 + l2xc: GigabitEthernet3/0/0.100 + +taps: + tap100: + host: + name: vpp-tap100 + mac: 02:01:be:ef:ca:fe + mtu: 9000 diff --git a/vpp/dumper.py b/vpp/dumper.py index 8424294..a0057c2 100644 --- a/vpp/dumper.py +++ b/vpp/dumper.py @@ -28,7 +28,7 @@ class Dumper(VPPApi): self.logger.info("Wrote YAML config to %s" % (outfile)) def cache_to_config(self): - config = {"loopbacks": {}, "bondethernets": {}, "interfaces": {}, "bridgedomains": {}, "vxlan_tunnels": {} } + config = {"loopbacks": {}, "bondethernets": {}, "interfaces": {}, "bridgedomains": {}, "vxlan_tunnels": {}, "taps": {} } for idx, bond_iface in self.cache['bondethernets'].items(): bond = {"description": ""} if bond_iface.sw_if_index in self.cache['bondethernet_members']: @@ -62,7 +62,7 @@ class Dumper(VPPApi): if len(self.cache['interface_addresses'][iface.sw_if_index]) > 0: loop['addresses'] = self.cache['interface_addresses'][iface.sw_if_index] config['loopbacks'][iface.interface_name] = loop - elif iface.interface_dev_type in ['bond', 'VXLAN', 'dpdk']: + elif iface.interface_dev_type in ['bond', 'VXLAN', 'dpdk', 'virtio']: i = {"description": "" } if iface.sw_if_index in self.cache['lcps']: i['lcp'] = self.cache['lcps'][iface.sw_if_index].host_if_name @@ -77,6 +77,10 @@ class Dumper(VPPApi): if iface.interface_dev_type == 'dpdk': i['mac'] = str(iface.l2_address) + + if self.tap_is_lcp(iface.interface_name): + continue + i['mtu'] = iface.mtu[0] if iface.sub_number_of_tags == 0: config['interfaces'][iface.interface_name] = i @@ -109,6 +113,25 @@ class Dumper(VPPApi): "remote": str(iface.dst_address) } config['vxlan_tunnels'][vpp_iface.interface_name] = vxlan + for idx, iface in self.cache['taps'].items(): + vpp_tap = self.cache['taps'][iface.sw_if_index] + vpp_iface = self.cache['interfaces'][vpp_tap.sw_if_index] + + tap = { "description": "", + "tx-ring-size": vpp_tap.tx_ring_sz, + "rx-ring-size": vpp_tap.rx_ring_sz, + "host": { + "mac": str(vpp_tap.host_mac_addr), + "name": vpp_tap.host_if_name, + } } + if vpp_tap.host_mtu_size > 0: + tap['host']['mtu'] = vpp_tap.host_mtu_size + if vpp_tap.host_namespace: + tap['host']['namespace'] = vpp_tap.host_namespace + if vpp_tap.host_bridge: + tap['host']['bridge'] = vpp_tap.host_bridge + config['taps'][vpp_iface.interface_name] = tap + for idx, iface in self.cache['bridgedomains'].items(): # self.logger.info("%d: %s" % (idx, iface)) bridge_name = "bd%d" % idx diff --git a/vpp/reconciler.py b/vpp/reconciler.py index 78e0246..0b6086f 100644 --- a/vpp/reconciler.py +++ b/vpp/reconciler.py @@ -21,6 +21,7 @@ import config.bondethernet as bondethernet import config.bridgedomain as bridgedomain import config.vxlan_tunnel as vxlan_tunnel import config.lcp as lcp +import config.tap as tap from vpp.vppapi import VPPApi class Reconciler(): @@ -95,6 +96,9 @@ class Reconciler(): if not self.prune_sub_interfaces(): self.logger.warning("Could not prune Sub Interfaces from VPP") ret = False + if not self.prune_taps(): + self.logger.warning("Could not prune TAPs from VPP") + ret = False if not self.prune_vxlan_tunnels(): self.logger.warning("Could not prune VXLAN Tunnels from VPP") ret = False @@ -236,6 +240,41 @@ class Reconciler(): self.vpp.cache_remove_l2xc(l2xc) return True + def __tap_has_diff(self, ifname): + """ Returns True if the given ifname (tap0) has different attributes between VPP + and the given configuration, or if either does not exist. + + Returns False if the TAP is a Linux Control Plane LIP. + Returns False if they are identical.""" + if not ifname in self.vpp.cache['interface_names']: + return True + vpp_iface = self.vpp.cache['interface_names'][ifname] + vpp_tap = self.vpp.cache['taps'][vpp_iface.sw_if_index] + + config_ifname, config_iface = tap.get_by_name(self.cfg, ifname) + if not config_iface: + return True + + if self.vpp.tap_is_lcp(ifname): + return False + + if 'name' in config_iface['host'] and config_iface['host']['name'] != vpp_tap.host_if_name: + self.logger.info("TAP diff: hostname") + return True + if 'mtu' in config_iface['host'] and config_iface['host']['mtu'] != vpp_tap.host_mtu_size: + return True + if 'mac' in config_iface['host'] and config_iface['host']['mac'] != str(vpp_tap.host_mac_addr): + self.logger.info("TAP diff: mac") + return True + if 'bridge' in config_iface['host'] and config_iface['host']['bridge'] != vpp_tap.host_bridge: + self.logger.info("TAP diff: bridge") + return True + if 'namespace' in config_iface['host'] and config_iface['host']['namespace'] != vpp_tap.host_namespace: + self.logger.info("TAP diff: namespace") + return True + + return False + def __bond_has_diff(self, ifname): """ Returns True if the given ifname (BondEthernet0) have different attributes, or if either does not exist. @@ -263,6 +302,28 @@ class Reconciler(): return False + def prune_taps(self): + """ Remove all TAPs from VPP, if they are not in the config. As an exception, + TAPs which are a part of Linux Control Plane, are left alone, to be handled + by prune_lcps() later. """ + removed_taps = [] + for idx, vpp_tap in self.vpp.cache['taps'].items(): + vpp_iface = self.vpp.cache['interfaces'][vpp_tap.sw_if_index] + vpp_ifname = vpp_iface.interface_name + if self.vpp.tap_is_lcp(vpp_ifname): + continue + if self.__tap_has_diff(vpp_ifname): + removed_taps.append(vpp_ifname) + continue + self.logger.debug("TAP OK: %s" % (vpp_ifname)) + + for ifname in removed_taps: + cli = "delete tap %s" % ifname + self.cli['prune'].append(cli) + self.vpp.cache_remove_tap(ifname) + self.vpp.cache_remove_interface(ifname) + 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. """ @@ -338,22 +399,6 @@ class Reconciler(): return True - def __tap_is_lcp(self, sw_if_index): - """ Returns True if the given sw_if_index is a TAP interface belonging to an LCP, - or False otherwise.""" - if not sw_if_index in self.vpp.cache['interfaces']: - return False - - vpp_iface = self.vpp.cache['interfaces'][sw_if_index] - if not vpp_iface.interface_dev_type=="virtio": - return False - - match = False - for idx, lcp in self.vpp.cache['lcps'].items(): - if vpp_iface.sw_if_index == lcp.host_sw_if_index: - match = True - return match - def prune_sub_interfaces(self): """ Remove interfaces from VPP if they are not in the config, if their encapsulation is different, or if the BondEthernet they reside on is different. @@ -365,7 +410,7 @@ class Reconciler(): if vpp_iface.sub_number_of_tags != numtags: continue - if self.__tap_is_lcp(vpp_iface.sw_if_index): + if self.vpp.tap_is_lcp(vpp_ifname): continue prune=False @@ -573,7 +618,7 @@ class Reconciler(): if not ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks(self.cfg): vpp_iface = self.vpp.cache['interface_names'][ifname] - if self.__tap_is_lcp(vpp_iface.sw_if_index): + if self.vpp.tap_is_lcp(ifname): continue if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP @@ -596,6 +641,9 @@ class Reconciler(): if not self.create_vxlan_tunnels(): self.logger.warning("Could not create VXLAN Tunnels in VPP") ret = False + if not self.create_taps(): + self.logger.warning("Could not create TAPs in VPP") + ret = False if not self.create_sub_interfaces(): self.logger.warning("Could not create Sub Interfaces in VPP") ret = False @@ -672,6 +720,29 @@ class Reconciler(): self.cli['create'].append(cli); return True + def create_taps(self): + for ifname in tap.get_taps(self.cfg): + ifname, iface = tap.get_by_name(self.cfg, ifname) + if ifname in self.vpp.cache['interface_names']: + continue + instance=int(ifname[3:]) + cli="create tap id %d host-if-name %s" % (instance, iface['host']['name']) + if 'mac' in iface['host']: + cli+=" host-mac-addr %s" % iface['host']['mac'] + if 'namespace' in iface['host']: + cli+=" host-ns %d" % iface['host']['namespace'] + if 'bridge' in iface['host']: + cli+=" host-bridge %s" % iface['host']['bridge'] + if 'mtu' in iface['host']: + cli+=" host-mtu-size %d" % iface['host']['mtu'] + if 'rx-ring-size' in iface: + cli+=" rx-ring-size %d" % iface['rx-ring-size'] + if 'tx-ring-size' in iface: + cli+=" tx-ring-size %d" % iface['tx-ring-size'] + self.cli['create'].append(cli) + + return True + def create_bridgedomains(self): for ifname in bridgedomain.get_bridgedomains(self.cfg): ifname, iface = bridgedomain.get_by_name(self.cfg, ifname) diff --git a/vpp/vppapi.py b/vpp/vppapi.py index 4402691..78c81ee 100644 --- a/vpp/vppapi.py +++ b/vpp/vppapi.py @@ -66,7 +66,7 @@ class VPPApi(): self.cache_read = False return {"lcps": {}, "interface_names": {}, "interfaces": {}, "interface_addresses": {}, "bondethernets": {}, "bondethernet_members": {}, - "bridgedomains": {}, "vxlan_tunnels": {}, "l2xcs": {}} + "bridgedomains": {}, "vxlan_tunnels": {}, "l2xcs": {}, "taps": {}} def cache_remove_lcp(self, lcpname): """ Removes the LCP and TAP interface, identified by lcpname, from the config. """ @@ -79,11 +79,10 @@ class VPPApi(): return False ifname = self.cache['interfaces'][lcp.host_sw_if_index].interface_name - del self.cache['interface_names'][ifname] - del self.cache['interface_addresses'][lcp.host_sw_if_index] - del self.cache['interfaces'][lcp.host_sw_if_index] del self.cache['lcps'][lcp.phy_sw_if_index] - return True + + # Remove the TAP interface and its dependencies + return self.cache_remove_interface(ifname) def cache_remove_bondethernet_member(self, ifname): """ Removes the bonderthernet member interface, identified by name, from the config. """ @@ -106,6 +105,15 @@ class VPPApi(): self.cache['l2xcs'].pop(iface.sw_if_index, None) return True + def cache_remove_tap(self, ifname): + if not ifname in self.cache['interface_names']: + self.logger.warning("Trying to remove a TAP which is not in the config: %s" % ifname) + return False + + iface = self.cache['interface_names'][ifname] + self.cache['taps'].pop(iface.sw_if_index, None) + return True + def cache_remove_vxlan_tunnel(self, ifname): if not ifname in self.cache['interface_names']: self.logger.warning("Trying to remove a VXLAN Tunnel which is not in the config: %s" % ifname) @@ -135,6 +143,8 @@ class VPPApi(): else: del self.cache['bondethernet_members'][iface.sw_if_index] self.cache['bondethernets'].pop(iface.sw_if_index, None) + + self.cache['taps'].pop(iface.sw_if_index, None) return True def readconfig(self): @@ -201,6 +211,11 @@ class VPPApi(): for l2xc in r: self.cache['l2xcs'][l2xc.rx_sw_if_index] = l2xc + self.logger.debug("Retrieving TAPs") + r = self.vpp.api.sw_interface_tap_v2_dump() + for tap in r: + self.cache['taps'][tap.sw_if_index] = tap + self.cache_read = True return self.cache_read @@ -247,3 +262,18 @@ class VPPApi(): if lcp.phy_sw_if_index == sw_if_index: return lcp return None + + def tap_is_lcp(self, tap_ifname): + """ Returns True if the given tap_ifname is a TAP interface belonging to an LCP, + or False otherwise.""" + if not tap_ifname in self.cache['interface_names']: + return False + + vpp_iface = self.cache['interface_names'][tap_ifname] + if not vpp_iface.interface_dev_type=="virtio": + return False + + for idx, lcp in self.cache['lcps'].items(): + if vpp_iface.sw_if_index == lcp.host_sw_if_index: + return True + return False