From 476d909904a7e019d252ab1e6295f33cb348ea70 Mon Sep 17 00:00:00 2001 From: Pim van Pelt Date: Sun, 7 Apr 2024 14:17:32 +0200 Subject: [PATCH] unnumbered: Add syntax and semantic checking and unit tests --- vppcfg/config/interface.py | 84 ++++++++++++++++++ vppcfg/config/test_interface.py | 23 ++++- vppcfg/schema.yaml | 2 + vppcfg/unittest/test_interface.yaml | 21 +++++ vppcfg/unittest/yaml/correct-unnumbered.yaml | 43 +++++++++ vppcfg/unittest/yaml/error-unnumbered1.yaml | 37 ++++++++ vppcfg/unittest/yaml/error-unnumbered2.yaml | 32 +++++++ vppcfg/unittest/yaml/error-unnumbered3.yaml | 32 +++++++ vppcfg/unittest/yaml/error-unnumbered4.yaml | 91 ++++++++++++++++++++ 9 files changed, 362 insertions(+), 3 deletions(-) create mode 100644 vppcfg/unittest/yaml/correct-unnumbered.yaml create mode 100644 vppcfg/unittest/yaml/error-unnumbered1.yaml create mode 100644 vppcfg/unittest/yaml/error-unnumbered2.yaml create mode 100644 vppcfg/unittest/yaml/error-unnumbered3.yaml create mode 100644 vppcfg/unittest/yaml/error-unnumbered4.yaml diff --git a/vppcfg/config/interface.py b/vppcfg/config/interface.py index ac77adf..32295d1 100644 --- a/vppcfg/config/interface.py +++ b/vppcfg/config/interface.py @@ -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( diff --git a/vppcfg/config/test_interface.py b/vppcfg/config/test_interface.py index 3d765c4..02a2cee 100644 --- a/vppcfg/config/test_interface.py +++ b/vppcfg/config/test_interface.py @@ -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")) diff --git a/vppcfg/schema.yaml b/vppcfg/schema.yaml index f79b1a5..8787d6d 100644 --- a/vppcfg/schema.yaml +++ b/vppcfg/schema.yaml @@ -50,6 +50,7 @@ interface: 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) + unnumbered: str(required=False) sub-interfaces: map(include('sub-interface'),key=int(min=1,max=4294967295),required=False) l2xc: str(required=False) state: enum('up', 'down', required=False) @@ -61,6 +62,7 @@ sub-interface: lcp: str(max=15,matches='[a-z]+[a-z0-9-]*',required=False) mtu: int(min=128,max=9216,required=False) addresses: list(ip_interface(),required=False) + unnumbered: str(required=False) encapsulation: include('encapsulation',required=False) l2xc: str(required=False) state: enum('up', 'down', required=False) diff --git a/vppcfg/unittest/test_interface.yaml b/vppcfg/unittest/test_interface.yaml index b48811c..2361be3 100644 --- a/vppcfg/unittest/test_interface.yaml +++ b/vppcfg/unittest/test_interface.yaml @@ -1,3 +1,7 @@ +loopbacks: + loop0: + addresses: [ 192.0.2.1/32, 2001:db8::1/128 ] + interfaces: GigabitEthernet1/0/0: sub-interfaces: @@ -75,3 +79,20 @@ interfaces: l2xc: GigabitEthernet3/0/2.200 200: description: "This interface does not connect back to Gi3/0/2.100. Strange, but valid." + + GigabitEthernet4/0/0: + addresses: [ 192.0.2.129/30, 2001:db8:10::1/64 ] + GigabitEthernet4/0/1: + unnumbered: GigabitEthernet4/0/0 + GigabitEthernet4/0/2: + unnumbered: loop0 + GigabitEthernet4/0/3: + sub-interfaces: + 100: + addresses: [ 192.168.2.133/30, 2001:db8:11::1/64 ] + 101: + unnumbered: loop0 + 102: + unnumbered: GigabitEthernet4/0/1 + 103: + unnumbered: GigabitEthernet4/0/3.100 diff --git a/vppcfg/unittest/yaml/correct-unnumbered.yaml b/vppcfg/unittest/yaml/correct-unnumbered.yaml new file mode 100644 index 0000000..1433232 --- /dev/null +++ b/vppcfg/unittest/yaml/correct-unnumbered.yaml @@ -0,0 +1,43 @@ +test: + description: "Test that is meant to pass" + errors: + count: 0 +--- +loopbacks: + loop0: + mtu: 9216 + addresses: [ 192.0.2.1/32, 2001:db8:1::1/128 ] + +interfaces: + GigabitEthernet1/0/0: + addresses: [ 94.142.244.85/24, 2a02:898::146:1/64 ] + sub-interfaces: + 100: + addresses: [ 94.142.241.185/29, 2a02:898:146::1/64 ] + 101: + unnumbered: loop0 + + GigabitEthernet2/0/0: + addresses: [ 192.0.2.5/30, 2001:db8:2::1/64 ] + GigabitEthernet2/0/1: + unnumbered: GigabitEthernet2/0/0 + + GigabitEthernet3/0/0: + sub-interfaces: + 100: + addresses: [ 192.0.2.9/30, 2001:db8:3::1/64 ] + 101: + unnumbered: loop0 + + GigabitEthernet3/0/1: + unnumbered: GigabitEthernet3/0/0.100 + + GigabitEthernet4/0/0: + addresses: [ 192.0.2.13/30, 2001:db8:4::1/64 ] + + GigabitEthernet4/0/1: + sub-interfaces: + 100: + unnumbered: GigabitEthernet4/0/0 + 101: + unnumbered: loop0 diff --git a/vppcfg/unittest/yaml/error-unnumbered1.yaml b/vppcfg/unittest/yaml/error-unnumbered1.yaml new file mode 100644 index 0000000..fc9966e --- /dev/null +++ b/vppcfg/unittest/yaml/error-unnumbered1.yaml @@ -0,0 +1,37 @@ +test: + description: "Nonexistent unnumbered target" + errors: + expected: + - "^sub-interface .* unnumbered target .* does not exist" + - "^interface .* unnumbered target .* does not exist" + count: 4 +--- +loopbacks: + loop0: + mtu: 9216 + addresses: [ 192.0.2.1/32, 2001:db8:1::1/128 ] + +interfaces: + GigabitEthernet1/0/0: + sub-interfaces: + 101: + description: "Error: non existent loopback device" + unnumbered: loop1 + + GigabitEthernet2/0/0: + addresses: [ 192.0.2.5/30, 2001:db8:2::1/64 ] + GigabitEthernet2/0/1: + description: "Error: non existent phy" + unnumbered: GigabitEthernet2/0/2 + + GigabitEthernet3/0/0: + sub-interfaces: + 100: + addresses: [ 192.0.2.9/30, 2001:db8:3::1/64 ] + 101: + description: "Error: non existent loopback device" + unnumbered: loop2 + + GigabitEthernet3/0/1: + description: "Error: non existent sub-int" + unnumbered: GigabitEthernet3/0/1.100 diff --git a/vppcfg/unittest/yaml/error-unnumbered2.yaml b/vppcfg/unittest/yaml/error-unnumbered2.yaml new file mode 100644 index 0000000..3009921 --- /dev/null +++ b/vppcfg/unittest/yaml/error-unnumbered2.yaml @@ -0,0 +1,32 @@ +test: + description: "Unnumbered cannot point to self; Unnumbered cannot be a target of another unnumbered" + errors: + expected: + - "(sub-)?interface .* unnumbered target cannot point to itself" + - "(sub-)?interface .* unnumbered target .* cannot also be unnumbered" + count: 8 +--- +interfaces: + GigabitEthernet1/0/0: + description: "Cannot point to the same interface" + unnumbered: GigabitEthernet1/0/0 + sub-interfaces: + 100: + description: "Cannot point to the same sub-interface" + unnumbered: GigabitEthernet1/0/0.100 + + GigabitEthernet2/0/0: + description: "Cannot point to Gi2/0/1, as that interface is itself unnumbered" + unnumbered: GigabitEthernet2/0/1 + GigabitEthernet2/0/1: + description: "Cannot point to Gi2/0/0, as that interface is itself unnumbered" + unnumbered: GigabitEthernet2/0/0 + + GigabitEthernet3/0/0: + sub-interfaces: + 100: + description: "Cannot point to Gi3/0/0.101, as that interface is itself unnumbered" + unnumbered: GigabitEthernet3/0/0.101 + 101: + description: "Cannot point to Gi3/0/0.100, as that interface is itself unnumbered" + unnumbered: GigabitEthernet3/0/0.100 diff --git a/vppcfg/unittest/yaml/error-unnumbered3.yaml b/vppcfg/unittest/yaml/error-unnumbered3.yaml new file mode 100644 index 0000000..cbbe8b1 --- /dev/null +++ b/vppcfg/unittest/yaml/error-unnumbered3.yaml @@ -0,0 +1,32 @@ +test: + description: "Unnumbered targets cannot have addresses" + errors: + expected: + - "(sub-)?interface .* cannot also have addresses when it is unnumbered" + count: 3 +--- +loopbacks: + loop0: + mtu: 9216 + addresses: [ 192.0.2.1/32, 2001:db8:1::1/128 ] + +interfaces: + GigabitEthernet1/0/0: + sub-interfaces: + 101: + addresses: [ 192.0.2.129/30, 2001:db8:10::1/128 ] + unnumbered: loop0 + + GigabitEthernet2/0/0: + addresses: [ 192.0.2.5/30, 2001:db8:2::1/64 ] + GigabitEthernet2/0/1: + addresses: [ 192.0.2.133/30, 2001:db8:11::1/128 ] + unnumbered: GigabitEthernet2/0/0 + + GigabitEthernet3/0/0: + sub-interfaces: + 100: + addresses: [ 192.0.2.9/30, 2001:db8:3::1/64 ] + 101: + addresses: [ 192.0.2.137/30, 2001:db8:12::1/128 ] + unnumbered: GigabitEthernet3/0/0.100 diff --git a/vppcfg/unittest/yaml/error-unnumbered4.yaml b/vppcfg/unittest/yaml/error-unnumbered4.yaml new file mode 100644 index 0000000..19de758 --- /dev/null +++ b/vppcfg/unittest/yaml/error-unnumbered4.yaml @@ -0,0 +1,91 @@ +test: + description: "Unnumbered targets cannot be in l2 mode" + errors: + expected: + - "(sub-)?interface .* unnumbered target .* cannot be in L2 mode" + count: 2 +--- +loopbacks: + loop0: + lcp: "loop0" + mtu: 3000 + addresses: [ 192.0.2.1/30, 2001:db8::1/64 ] + +bondethernets: + BondEthernet0: + interfaces: [ GigabitEthernet3/0/0, GigabitEthernet3/0/1 ] + +interfaces: + GigabitEthernet1/0/0: + mtu: 3000 + GigabitEthernet1/0/1: + mtu: 3000 + + GigabitEthernet2/0/0: + mtu: 9000 + sub-interfaces: + 100: + mtu: 2000 + GigabitEthernet2/0/1: + mtu: 9000 + sub-interfaces: + 100: + mtu: 2000 + + GigabitEthernet3/0/0: + mtu: 3000 + GigabitEthernet3/0/1: + mtu: 3000 + GigabitEthernet3/0/2: + mtu: 5000 + l2xc: GigabitEthernet3/0/3 + GigabitEthernet3/0/3: + mtu: 5000 + l2xc: GigabitEthernet3/0/2 + + GigabitEthernet4/0/0: + mtu: 3000 + description: "Cannot be unnumbered off of a bond-member" + unnumbered: GigabitEthernet3/0/0 + GigabitEthernet4/0/0: + mtu: 2000 + description: "Cannot be unnumbered off of an l2xc" + unnumbered: GigabitEthernet3/0/2 + GigabitEthernet4/0/2: + mtu: 3000 + description: "Cannot be unnumbered off of a bridge-domain member" + unnumbered: BondEthernet0.100 + sub-interfaces: + 10: + description: "Cannot be unnumbered off of a bond member" + unnumbered: GigabitEthernet3/0/1 + 100: + description: "Cannot be unnumbered off of a bridge-domain member" + unnumbered: BondEthernet0.100 + 200: + description: "Cannot be unnumbered off of an l2xc" + unnumbered: BondEthernet0.200 + + BondEthernet0: + mtu: 3000 + sub-interfaces: + 100: + mtu: 2000 + 200: + mtu: 2000 + l2xc: BondEthernet0.201 + 201: + mtu: 2000 + l2xc: BondEthernet0.200 + +bridgedomains: + bd10: + description: "Bridge Domain 10" + mtu: 3000 + bvi: loop0 + interfaces: [ GigabitEthernet1/0/0, GigabitEthernet1/0/1, BondEthernet0 ] + bd11: + description: "Bridge Domain 11" + mtu: 2000 + interfaces: [ GigabitEthernet2/0/0.100, GigabitEthernet2/0/1.100 ] +