From b461ef49bb8cbc6cec7d8fd4049b5e28f50ed5d8 Mon Sep 17 00:00:00 2001 From: Pim van Pelt Date: Tue, 5 Apr 2022 11:06:33 +0000 Subject: [PATCH] Add 'state' field to interfaces and sub-interfaces Assert that children cannot be 'up' of their parent is 'down'. Add tests. Update user-guide. --- config/interface.py | 19 +++++++++++++++++++ config/schema.py | 2 ++ config/test_interface.py | 7 +++++++ docs/config-guide.md | 2 ++ intest/hippo-empty.yaml | 4 ++++ schema.yaml | 2 ++ unittest/test_interface.yaml | 3 ++- unittest/yaml/error-subinterface9.yaml | 17 +++++++++++++++++ vpp/reconciler.py | 2 +- vpp/vppapi.py | 3 +++ 10 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 unittest/yaml/error-subinterface9.yaml diff --git a/config/interface.py b/config/interface.py index b18e97e..7e79a62 100644 --- a/config/interface.py +++ b/config/interface.py @@ -396,6 +396,17 @@ def get_mtu(yaml, ifname): return 1500 +def get_admin_state(yaml, ifname): + """ Return True if the interface admin state should be 'up'. Return False + if it does not exist, or if it's set to 'down'. """ + ifname, iface = get_by_name(yaml, ifname) + if not iface: + return False + if not 'state' in iface: + return True + return iface['state'] == 'up' + + def validate_interfaces(yaml): result = True msgs = [] @@ -410,6 +421,8 @@ def validate_interfaces(yaml): if ifname.startswith("BondEthernet") and (None,None) == bondethernet.get_by_name(yaml, ifname): msgs.append("interface %s does not exist in bondethernets" % ifname) result = False + if not 'state' in iface: + iface['state'] = 'up' iface_mtu = get_mtu(yaml, ifname) iface_lcp = get_lcp(yaml, ifname) @@ -473,6 +486,12 @@ def validate_interfaces(yaml): result = False continue + if not 'state' in sub_iface: + sub_iface['state'] = 'up' + if sub_iface['state'] == 'up' and iface['state'] == 'down': + msgs.append("sub-interface %s cannot be up if parent %s is down" % (sub_ifname, ifname)) + result = False + sub_mtu = get_mtu(yaml, sub_ifname) if sub_mtu > iface_mtu: msgs.append("sub-interface %s has MTU %d higher than parent %s MTU %d" % (sub_ifname, sub_iface['mtu'], ifname, iface_mtu)) diff --git a/config/schema.py b/config/schema.py index 64c57d6..4bd8477 100644 --- a/config/schema.py +++ b/config/schema.py @@ -51,6 +51,7 @@ interface: addresses: list(ip_interface(),min=1,max=6,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) --- sub-interface: description: str(exclude='\'"',len=64,required=False) @@ -59,6 +60,7 @@ sub-interface: addresses: list(ip_interface(),required=False) encapsulation: include('encapsulation',required=False) l2xc: str(required=False) + state: enum('up', 'down', required=False) --- encapsulation: dot1q: int(min=1,max=4095,required=False) diff --git a/config/test_interface.py b/config/test_interface.py index acf1f40..83b0700 100644 --- a/config/test_interface.py +++ b/config/test_interface.py @@ -201,3 +201,10 @@ class TestInterfaceMethods(unittest.TestCase): def test_is_phy(self): self.assertTrue(interface.is_phy(self.cfg, "GigabitEthernet1/0/0")) self.assertFalse(interface.is_phy(self.cfg, "GigabitEthernet1/0/0.100")) + + def test_get_admin_state(self): + self.assertFalse(interface.get_admin_state(self.cfg, "notexist")) + self.assertFalse(interface.get_admin_state(self.cfg, "GigabitEthernet2/0/0")) + self.assertTrue(interface.get_admin_state(self.cfg, "GigabitEthernet1/0/0")) + self.assertTrue(interface.get_admin_state(self.cfg, "GigabitEthernet1/0/0.101")) + self.assertFalse(interface.get_admin_state(self.cfg, "GigabitEthernet1/0/0.102")) diff --git a/docs/config-guide.md b/docs/config-guide.md index 29c157e..1fde7f3 100644 --- a/docs/config-guide.md +++ b/docs/config-guide.md @@ -212,6 +212,8 @@ exist as a PHY in VPP (ie. `HundredGigabitEthernet12/0/0`) or as a specified `Bo * ***l2xc***: A Layer2 Cross Connect interface name. An `l2xc` will be configured, after which this interface cannot have any L3 configuration (IP addresses or LCP), and neither can the target interface. +* ***state***: An optional string that configures the link admin state, either `up` or `down`. + If it is not specified, the link is considered admin 'up'. 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 diff --git a/intest/hippo-empty.yaml b/intest/hippo-empty.yaml index 48ea85f..35e1811 100644 --- a/intest/hippo-empty.yaml +++ b/intest/hippo-empty.yaml @@ -1,13 +1,17 @@ interfaces: GigabitEthernet3/0/0: mtu: 1500 + state: down description: Not Used GigabitEthernet3/0/1: mtu: 1500 + state: down description: Not Used HundredGigabitEthernet12/0/0: mtu: 1500 + state: down description: Not Used HundredGigabitEthernet12/0/1: mtu: 1500 + state: down description: Not Used diff --git a/schema.yaml b/schema.yaml index 995652a..de920b5 100644 --- a/schema.yaml +++ b/schema.yaml @@ -37,6 +37,7 @@ interface: addresses: list(ip_interface(),min=1,max=6,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) --- sub-interface: description: str(exclude='\'"',len=64,required=False) @@ -45,6 +46,7 @@ sub-interface: addresses: list(ip_interface(),required=False) encapsulation: include('encapsulation',required=False) l2xc: str(required=False) + state: enum('up', 'down', required=False) --- encapsulation: dot1q: int(min=1,max=4095,required=False) diff --git a/unittest/test_interface.yaml b/unittest/test_interface.yaml index 0af3681..648c2eb 100644 --- a/unittest/test_interface.yaml +++ b/unittest/test_interface.yaml @@ -10,9 +10,9 @@ interfaces: description: "This sub-int is invalid because it has no outer dot1q and dot1ad" encapsulation: inner-dot1q: 1000 - 102: description: "This sub-int is has the same encap as 103" + state: down 103: description: "This sub-int is has the same encap as 102" encapsulation: @@ -59,6 +59,7 @@ interfaces: GigabitEthernet2/0/0: description: "This interface has no sub-ints" lcp: "e2" + state: down GigabitEthernet3/0/0: l2xc: GigabitEthernet3/0/1 diff --git a/unittest/yaml/error-subinterface9.yaml b/unittest/yaml/error-subinterface9.yaml new file mode 100644 index 0000000..3a58550 --- /dev/null +++ b/unittest/yaml/error-subinterface9.yaml @@ -0,0 +1,17 @@ +test: + description: "A sub-interface cannot be up if its parent is down." + errors: + expected: + - "sub-interface .* cannot be up if parent .* is down" + count: 1 +--- +interfaces: + GigabitEthernet1/0/0: + state: down + lcp: "e1" + sub-interfaces: + 100: + state: up + encapsulation: + dot1q: 100 + exact-match: false diff --git a/vpp/reconciler.py b/vpp/reconciler.py index 3ae41d8..630b3cf 100644 --- a/vpp/reconciler.py +++ b/vpp/reconciler.py @@ -920,7 +920,7 @@ class Reconciler(): config_admin_state = 1 else: vpp_ifname, config_iface = interface.get_by_name(self.cfg, ifname) - config_admin_state = 1 + config_admin_state = interface.get_admin_state(self.cfg, ifname) vpp_admin_state = 0 if vpp_ifname in self.vpp.cache['interface_names']: diff --git a/vpp/vppapi.py b/vpp/vppapi.py index 7ff47eb..24f85ab 100644 --- a/vpp/vppapi.py +++ b/vpp/vppapi.py @@ -316,6 +316,9 @@ class VPPApiDumper(VPPApi): if iface.sw_if_index in self.cache['l2xcs']: l2xc = self.cache['l2xcs'][iface.sw_if_index] i['l2xc'] = self.cache['interfaces'][l2xc.tx_sw_if_index].interface_name + if not self.cache['interfaces'][idx].flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP + i['state'] = 'down' + i['mtu'] = iface.mtu[0] if iface.sub_number_of_tags == 0: config['interfaces'][iface.interface_name] = i