Add 'state' field to interfaces and sub-interfaces

Assert that children cannot be 'up' of their parent is 'down'. Add tests. Update user-guide.
This commit is contained in:
Pim van Pelt
2022-04-05 11:06:33 +00:00
parent 65de792e35
commit b461ef49bb
10 changed files with 59 additions and 2 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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