unnumbered: Add syntax and semantic checking and unit tests

This commit is contained in:
Pim van Pelt
2024-04-07 14:17:32 +02:00
parent e427d93197
commit 476d909904
9 changed files with 362 additions and 3 deletions

View File

@ -152,6 +152,23 @@ def get_l2xc_interfaces(yaml):
return ret
def get_unnumbered_interfaces(yaml):
"""Returns a list of all interfaces that are unnumbered"""
ret = []
if not "interfaces" in yaml:
return ret
for ifname, iface in yaml["interfaces"].items():
if "unnumbered" in iface:
ret.append(ifname)
if "sub-interfaces" in iface:
for subid, sub_iface in iface["sub-interfaces"].items():
sub_ifname = f"{ifname}.{int(subid)}"
if "unnumbered" in sub_iface:
ret.append(sub_ifname)
return ret
def is_l2xc_interface(yaml, ifname):
"""Returns True if this interface has an L2 CrossConnect"""
@ -381,6 +398,11 @@ def is_l3(yaml, ifname):
return not is_l2(yaml, ifname)
def is_unnumbered(yaml, ifname):
"""Returns True if the interface exists and is unnumbered"""
return ifname in get_unnumbered_interfaces(yaml)
def get_lcp(yaml, ifname):
"""Returns the LCP of the interface. If the interface is a sub-interface with L3
enabled, synthesize it based on its parent, using smart QinQ syntax.
@ -494,6 +516,37 @@ def validate_interfaces(yaml):
)
result = False
if "unnumbered" in iface:
target = iface["unnumbered"]
_, target_iface = loopback.get_by_name(yaml, target)
if not target_iface:
_, target_iface = get_by_name(yaml, target)
if not target_iface:
msgs.append(
f"interface {ifname} unnumbered target {target} does not exist"
)
result = False
if is_l2(yaml, target):
msgs.append(
f"interface {ifname} unnumbered target {target} cannot be in L2 mode"
)
result = False
if is_unnumbered(yaml, target):
msgs.append(
f"interface {ifname} unnumbered target {target} cannot also be unnumbered"
)
result = False
if ifname == target:
msgs.append(
f"interface {ifname} unnumbered target cannot point to itself"
)
result = False
if has_address(yaml, ifname):
msgs.append(
f"interface {ifname} cannot also have addresses when it is unnumbered"
)
result = False
if "addresses" in iface:
for addr in iface["addresses"]:
if not address.is_allowed(yaml, ifname, iface["addresses"], addr):
@ -625,6 +678,37 @@ def validate_interfaces(yaml):
)
result = False
if "unnumbered" in sub_iface:
target = sub_iface["unnumbered"]
_, target_iface = loopback.get_by_name(yaml, target)
if not target_iface:
_, target_iface = get_by_name(yaml, target)
if not target_iface:
msgs.append(
f"sub-interface {sub_ifname} unnumbered target {target} does not exist"
)
result = False
if is_l2(yaml, target):
msgs.append(
f"sub-interface {sub_ifname} unnumbered target {target} cannot be in L2 mode"
)
result = False
if is_unnumbered(yaml, target):
msgs.append(
f"sub-interface {sub_ifname} unnumbered target {target} cannot also be unnumbered"
)
result = False
if sub_ifname == target:
msgs.append(
f"sub-interface {sub_ifname} unnumbered target cannot point to itself"
)
result = False
if has_address(yaml, sub_ifname):
msgs.append(
f"sub-interface {sub_ifname} cannot also have addresses when it is unnumbered"
)
result = False
if has_address(yaml, sub_ifname):
if not encap or not encap["exact-match"]:
msgs.append(

View File

@ -26,12 +26,12 @@ class TestInterfaceMethods(unittest.TestCase):
def test_enumerators(self):
ifs = interface.get_interfaces(self.cfg)
self.assertEqual(len(ifs), 19)
self.assertEqual(len(ifs), 27)
self.assertIn("GigabitEthernet1/0/1", ifs)
self.assertIn("GigabitEthernet1/0/1.200", ifs)
ifs = interface.get_sub_interfaces(self.cfg)
self.assertEqual(len(ifs), 13)
self.assertEqual(len(ifs), 17)
self.assertNotIn("GigabitEthernet1/0/1", ifs)
self.assertIn("GigabitEthernet1/0/1.200", ifs)
self.assertIn("GigabitEthernet1/0/1.201", ifs)
@ -261,7 +261,7 @@ class TestInterfaceMethods(unittest.TestCase):
def test_get_phys(self):
phys = interface.get_phys(self.cfg)
self.assertEqual(len(phys), 6)
self.assertEqual(len(phys), 10)
self.assertIn("GigabitEthernet1/0/0", phys)
self.assertNotIn("GigabitEthernet1/0/0.100", phys)
@ -284,3 +284,20 @@ class TestInterfaceMethods(unittest.TestCase):
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"))
def test_get_unnumbered_interfaces(self):
unnumbered_ifaces = interface.get_unnumbered_interfaces(self.cfg)
self.assertEqual(len(unnumbered_ifaces), 5)
self.assertNotIn("GigabitEthernet4/0/0", unnumbered_ifaces)
self.assertIn("GigabitEthernet4/0/1", unnumbered_ifaces)
self.assertNotIn("GigabitEthernet4/0/3.100", unnumbered_ifaces)
self.assertIn("GigabitEthernet4/0/3.101", unnumbered_ifaces)
def test_is_unnumbered(self):
self.assertFalse(interface.is_unnumbered(self.cfg, "GigabitEthernet4/0/0"))
self.assertTrue(interface.is_unnumbered(self.cfg, "GigabitEthernet4/0/1"))
self.assertTrue(interface.is_unnumbered(self.cfg, "GigabitEthernet4/0/2"))
self.assertFalse(interface.is_unnumbered(self.cfg, "GigabitEthernet4/0/3.100"))
self.assertTrue(interface.is_unnumbered(self.cfg, "GigabitEthernet4/0/3.101"))
self.assertTrue(interface.is_unnumbered(self.cfg, "GigabitEthernet4/0/3.102"))
self.assertTrue(interface.is_unnumbered(self.cfg, "GigabitEthernet4/0/3.103"))