Merge pull request #13 from pimvanpelt/mpls_iface

Support MPLS Interfaces
This commit is contained in:
Pim van Pelt
2023-06-23 00:17:57 +02:00
committed by GitHub
12 changed files with 106 additions and 9 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"))

View File

@ -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"))

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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(

View File

@ -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()