diff --git a/docs/config-guide.md b/docs/config-guide.md index c1902bc..a718560 100644 --- a/docs/config-guide.md +++ b/docs/config-guide.md @@ -73,6 +73,9 @@ following fields: in CIDR format. VPP requires IP addresses to be unique in the entire dataplane, with one notable exception: Multiple IP addresses in the same prefix/len can be added on one and the same interface. +* ***mpls***: An optional boolean that configures MPLS on the interface or sub-interface. The + default value is `false`, if the field is not specified, which means MPLS will not be enabled. + This allows BVIs, represented by Loopbacks, to participate in MPLS . Although VPP would allow it, `vppcfg` does not allow for loopbacks to have sub-interfaces. @@ -87,6 +90,7 @@ loopbacks: lcp: bvi1 mtu: 9000 addresses: [ 10.0.1.1/24, 10.0.1.2/24, 2001:db8:1::1/64 ] + mpls: true ``` ### Bridge Domains @@ -306,6 +310,8 @@ exist as a PHY in VPP (ie. `HundredGigabitEthernet12/0/0`) or as a specified `Bo If it is not specified, the link is considered admin 'up'. * ***device-type***: An optional interface type in VPP. Currently the only supported vlaue is `dpdk`, and it is used to generate correct mock interfaces if the `--novpp` flag is used. +* ***mpls***: An optional boolean that configures MPLS on the interface or sub-interface. The + default value is `false`, if the field is not specified, which means MPLS will not be enabled. Further, top-level interfaces, that is to say those that do not have an encapsulation, are permitted to have any number of sub-interfaces specified by `subid`, an integer between [0,2G), which further @@ -330,6 +336,7 @@ interfaces: lcp: "ice0" mtu: 9000 addresses: [ 192.0.2.1/30, 2001:db8:1::1/64 ] + mpls: true sub-interfaces: 1234: mtu: 9000 @@ -338,6 +345,7 @@ interfaces: 1235: mtu: 1500 lcp: "ice0.qinq" + mpls: true addresses: [ 192.0.2.9/30, 2001:db8:3::1/64 ] encapsulation: dot1q: 1234 diff --git a/vppcfg/config/interface.py b/vppcfg/config/interface.py index 5d73560..ac77adf 100644 --- a/vppcfg/config/interface.py +++ b/vppcfg/config/interface.py @@ -701,3 +701,14 @@ def validate_interfaces(yaml): result = False return result, msgs + + +def is_mpls(yaml, ifname): + """Returns True if the interface exists and has mpls enabled. Returns false otherwise.""" + ifname, iface = get_by_name(yaml, ifname) + try: + if iface["mpls"] == True: + return True + except: + pass + return False diff --git a/vppcfg/config/loopback.py b/vppcfg/config/loopback.py index a5f1d2d..4bf6294 100644 --- a/vppcfg/config/loopback.py +++ b/vppcfg/config/loopback.py @@ -96,3 +96,14 @@ def validate_loopbacks(yaml): result = False return result, msgs + + +def is_mpls(yaml, ifname): + """Returns True if the loopback exists and has mpls enabled. Returns false otherwise.""" + ifname, iface = get_by_name(yaml, ifname) + try: + if iface["mpls"] == True: + return True + except: + pass + return False diff --git a/vppcfg/config/test_interface.py b/vppcfg/config/test_interface.py index c677874..3d765c4 100644 --- a/vppcfg/config/test_interface.py +++ b/vppcfg/config/test_interface.py @@ -277,3 +277,10 @@ class TestInterfaceMethods(unittest.TestCase): self.assertFalse( interface.get_admin_state(self.cfg, "GigabitEthernet1/0/0.102") ) + + def test_is_mpls(self): + self.assertTrue(interface.is_mpls(self.cfg, "GigabitEthernet1/0/1")) + self.assertTrue(interface.is_mpls(self.cfg, "GigabitEthernet1/0/1.101")) + self.assertFalse(interface.is_mpls(self.cfg, "GigabitEthernet1/0/0")) + self.assertFalse(interface.is_mpls(self.cfg, "GigabitEthernet1/0/0.100")) + self.assertFalse(interface.is_mpls(self.cfg, "notexist")) diff --git a/vppcfg/config/test_loopback.py b/vppcfg/config/test_loopback.py index 9c148c5..7b0345c 100644 --- a/vppcfg/config/test_loopback.py +++ b/vppcfg/config/test_loopback.py @@ -50,3 +50,8 @@ class TestLoopbackMethods(unittest.TestCase): self.assertIn("loop1", ifs) self.assertIn("loop2", ifs) self.assertNotIn("loop-noexist", ifs) + + def test_is_mpls(self): + self.assertTrue(loopback.is_mpls(self.cfg, "loop1")) + self.assertFalse(loopback.is_mpls(self.cfg, "loop2")) + self.assertFalse(loopback.is_mpls(self.cfg, "loop-noexist")) diff --git a/vppcfg/example.yaml b/vppcfg/example.yaml index d28b773..c6126c2 100644 --- a/vppcfg/example.yaml +++ b/vppcfg/example.yaml @@ -20,7 +20,8 @@ interfaces: lcp: "ice12-0-0" mac: f2:01:00:12:00:00 mtu: 9000 - addresses: [ 192.0.2.17/30, 2001:DB8:3::1/64 ] + addresses: [ 192.0.2.17/30, 2001:db8:3::1/64 ] + mpls: true sub-interfaces: 1234: mtu: 1200 @@ -90,6 +91,7 @@ loopbacks: mtu: 1500 mac: 02:de:ad:11:be:ef addresses: [ 10.0.2.1/24, 2001:db8:2::1/64 ] + mpls: true bridgedomains: bd1: diff --git a/vppcfg/schema.yaml b/vppcfg/schema.yaml index bc1892b..d615fca 100644 --- a/vppcfg/schema.yaml +++ b/vppcfg/schema.yaml @@ -33,6 +33,7 @@ loopback: lcp: str(max=15,matches='[a-z]+[a-z0-9-]*',required=False) mtu: int(min=128,max=9216,required=False) addresses: list(ip_interface(),min=1,max=6,required=False) + mpls: bool(required=False) --- bondethernet: description: str(exclude='\'"',len=64,required=False) @@ -50,6 +51,7 @@ interface: sub-interfaces: map(include('sub-interface'),key=int(min=1,max=4294967295),required=False) l2xc: str(required=False) state: enum('up', 'down', required=False) + mpls: bool(required=False) device-type: enum('dpdk', required=False) --- sub-interface: @@ -60,6 +62,7 @@ sub-interface: encapsulation: include('encapsulation',required=False) l2xc: str(required=False) state: enum('up', 'down', required=False) + mpls: bool(required=False) --- encapsulation: dot1q: int(min=1,max=4095,required=False) diff --git a/vppcfg/unittest/test_interface.yaml b/vppcfg/unittest/test_interface.yaml index 648c2eb..b48811c 100644 --- a/vppcfg/unittest/test_interface.yaml +++ b/vppcfg/unittest/test_interface.yaml @@ -23,6 +23,7 @@ interfaces: mtu: 9216 lcp: "e1" addresses: [ "192.0.2.1/30", "2001:db8:1::1/64" ] + mpls: true sub-interfaces: 100: lcp: "foo" @@ -33,6 +34,7 @@ interfaces: exact-match: True lcp: "e1.100" addresses: [ "10.0.2.1/30" ] + mpls: true 102: encapsulation: dot1ad: 100 diff --git a/vppcfg/unittest/test_loopback.yaml b/vppcfg/unittest/test_loopback.yaml index 2030dfb..413fbb6 100644 --- a/vppcfg/unittest/test_loopback.yaml +++ b/vppcfg/unittest/test_loopback.yaml @@ -6,6 +6,7 @@ loopbacks: mtu: 2000 lcp: "loop56789012345" addresses: [ 192.0.2.1/29, 2001:db8::1/64 ] + mpls: true loop2: description: "Loopback, invalid because it has an address but no LCP" mtu: 2000 diff --git a/vppcfg/vpp/dumper.py b/vppcfg/vpp/dumper.py index 1d31721..c8b14de 100644 --- a/vppcfg/vpp/dumper.py +++ b/vppcfg/vpp/dumper.py @@ -108,6 +108,8 @@ class Dumper(VPPApi): loop["addresses"] = self.cache["interface_addresses"][ iface.sw_if_index ] + if iface.sw_if_index in self.cache["interface_mpls"]: + loop["mpls"] = self.cache["interface_mpls"][iface.sw_if_index] config["loopbacks"][iface.interface_name] = loop elif iface.interface_dev_type in [ "bond", diff --git a/vppcfg/vpp/reconciler.py b/vppcfg/vpp/reconciler.py index e3d0b7f..9e8b757 100644 --- a/vppcfg/vpp/reconciler.py +++ b/vppcfg/vpp/reconciler.py @@ -947,6 +947,9 @@ class Reconciler: if not self.__sync_phys(): self.logger.warning("Could not sync PHYs in VPP") ret = False + if not self.__sync_mpls_state(): + self.logger.warning("Could not sync interface MPLS state in VPP") + ret = False if not self.__sync_admin_state(): self.logger.warning("Could not sync interface adminstate in VPP") ret = False @@ -1291,6 +1294,36 @@ class Reconciler: ret = False return ret + def __sync_mpls_state(self): + """Synchronize the VPP Dataplane configuration for interface and loopback MPLS state""" + for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks( + self.cfg + ): + if ifname.startswith("loop"): + vpp_ifname, config_iface = loopback.get_by_name(self.cfg, ifname) + else: + vpp_ifname, config_iface = interface.get_by_name(self.cfg, ifname) + + try: + config_mpls = config_iface["mpls"] + except KeyError: + config_mpls = False + + vpp_mpls = False + if vpp_ifname in self.vpp.cache["interface_names"]: + sw_if_index = self.vpp.cache["interface_names"][vpp_ifname] + try: + vpp_mpls = self.vpp.cache["interface_mpls"][sw_if_index] + except KeyError: + pass + if vpp_mpls != config_mpls: + state = "disable" + if config_mpls: + state = "enable" + cli = f"set interface mpls {vpp_ifname} {state}" + self.cli["sync"].append(cli) + return True + def __sync_addresses(self): """Synchronize the VPP Dataplane configuration for interface addresses""" for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks( diff --git a/vppcfg/vpp/vppapi.py b/vppcfg/vpp/vppapi.py index 942b9f6..f19df19 100644 --- a/vppcfg/vpp/vppapi.py +++ b/vppcfg/vpp/vppapi.py @@ -119,6 +119,7 @@ class VPPApi: "interface_names": {}, "interfaces": {}, "interface_addresses": {}, + "interface_mpls": {}, "bondethernets": {}, "bondethernet_members": {}, "bridgedomains": {}, @@ -215,7 +216,7 @@ class VPPApi: enumerating the 'interfaces' scope from yaml_config""" if not "interfaces" in yaml_config: - self.logger.error(f"YAML config does not contain any interfaces") + self.logger.error("YAML config does not contain any interfaces") return False self.logger.debug(f"config: {yaml_config['interfaces']}") @@ -305,9 +306,7 @@ class VPPApi: self.cache["lcps"][lcp.phy_sw_if_index] = lcp self.lcp_enabled = True except AttributeError as err: - self.logger.warning( - f"linux-cp not found, will not reconcile Linux Control Plane: {err}" - ) + self.logger.warning(f"LinuxCP API not found - missing plugin: {err}") self.logger.debug("Retrieving interfaces") api_response = self.vpp.api.sw_interface_dump() @@ -332,6 +331,16 @@ class VPPApi: str(addr.prefix) ) + try: ## TODO(pim): Remove after 23.10 release + self.logger.debug("Retrieving interface MPLS state") + api_response = self.vpp.api.mpls_interface_dump() + for iface in api_response: + self.cache["interface_mpls"][iface.sw_if_index] = True + except AttributeError: + self.logger.warning( + f"MPLS state retrieval requires https://gerrit.fd.io/r/c/vpp/+/39022" + ) + self.logger.debug("Retrieving bondethernets") api_response = self.vpp.api.sw_bond_interface_dump() for iface in api_response: @@ -349,10 +358,13 @@ class VPPApi: for bridge in api_response: self.cache["bridgedomains"][bridge.bd_id] = bridge - self.logger.debug("Retrieving vxlan_tunnels") - api_response = self.vpp.api.vxlan_tunnel_v2_dump() - for vxlan in api_response: - self.cache["vxlan_tunnels"][vxlan.sw_if_index] = vxlan + try: + self.logger.debug("Retrieving vxlan_tunnels") + api_response = self.vpp.api.vxlan_tunnel_v2_dump() + for vxlan in api_response: + self.cache["vxlan_tunnels"][vxlan.sw_if_index] = vxlan + except AttributeError as err: + self.logger.warning(f"VXLAN API not found - missing plugin: {err}") self.logger.debug("Retrieving L2 Cross Connects") api_response = self.vpp.api.l2_xconnect_dump()