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 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): def validate_interfaces(yaml):
result = True result = True
msgs = [] msgs = []
@ -410,6 +421,8 @@ def validate_interfaces(yaml):
if ifname.startswith("BondEthernet") and (None,None) == bondethernet.get_by_name(yaml, ifname): if ifname.startswith("BondEthernet") and (None,None) == bondethernet.get_by_name(yaml, ifname):
msgs.append("interface %s does not exist in bondethernets" % ifname) msgs.append("interface %s does not exist in bondethernets" % ifname)
result = False result = False
if not 'state' in iface:
iface['state'] = 'up'
iface_mtu = get_mtu(yaml, ifname) iface_mtu = get_mtu(yaml, ifname)
iface_lcp = get_lcp(yaml, ifname) iface_lcp = get_lcp(yaml, ifname)
@ -473,6 +486,12 @@ def validate_interfaces(yaml):
result = False result = False
continue 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) sub_mtu = get_mtu(yaml, sub_ifname)
if sub_mtu > iface_mtu: 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)) 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) addresses: list(ip_interface(),min=1,max=6,required=False)
sub-interfaces: map(include('sub-interface'),key=int(min=1,max=4294967295),required=False) sub-interfaces: map(include('sub-interface'),key=int(min=1,max=4294967295),required=False)
l2xc: str(required=False) l2xc: str(required=False)
state: enum('up', 'down', required=False)
--- ---
sub-interface: sub-interface:
description: str(exclude='\'"',len=64,required=False) description: str(exclude='\'"',len=64,required=False)
@ -59,6 +60,7 @@ sub-interface:
addresses: list(ip_interface(),required=False) addresses: list(ip_interface(),required=False)
encapsulation: include('encapsulation',required=False) encapsulation: include('encapsulation',required=False)
l2xc: str(required=False) l2xc: str(required=False)
state: enum('up', 'down', required=False)
--- ---
encapsulation: encapsulation:
dot1q: int(min=1,max=4095,required=False) dot1q: int(min=1,max=4095,required=False)

View File

@ -201,3 +201,10 @@ class TestInterfaceMethods(unittest.TestCase):
def test_is_phy(self): def test_is_phy(self):
self.assertTrue(interface.is_phy(self.cfg, "GigabitEthernet1/0/0")) self.assertTrue(interface.is_phy(self.cfg, "GigabitEthernet1/0/0"))
self.assertFalse(interface.is_phy(self.cfg, "GigabitEthernet1/0/0.100")) 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 * ***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 this interface cannot have any L3 configuration (IP addresses or LCP), and neither can the
target interface. 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 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 to have any number of sub-interfaces specified by `subid`, an integer between [0,2G), which further

View File

@ -1,13 +1,17 @@
interfaces: interfaces:
GigabitEthernet3/0/0: GigabitEthernet3/0/0:
mtu: 1500 mtu: 1500
state: down
description: Not Used description: Not Used
GigabitEthernet3/0/1: GigabitEthernet3/0/1:
mtu: 1500 mtu: 1500
state: down
description: Not Used description: Not Used
HundredGigabitEthernet12/0/0: HundredGigabitEthernet12/0/0:
mtu: 1500 mtu: 1500
state: down
description: Not Used description: Not Used
HundredGigabitEthernet12/0/1: HundredGigabitEthernet12/0/1:
mtu: 1500 mtu: 1500
state: down
description: Not Used description: Not Used

View File

@ -37,6 +37,7 @@ interface:
addresses: list(ip_interface(),min=1,max=6,required=False) addresses: list(ip_interface(),min=1,max=6,required=False)
sub-interfaces: map(include('sub-interface'),key=int(min=1,max=4294967295),required=False) sub-interfaces: map(include('sub-interface'),key=int(min=1,max=4294967295),required=False)
l2xc: str(required=False) l2xc: str(required=False)
state: enum('up', 'down', required=False)
--- ---
sub-interface: sub-interface:
description: str(exclude='\'"',len=64,required=False) description: str(exclude='\'"',len=64,required=False)
@ -45,6 +46,7 @@ sub-interface:
addresses: list(ip_interface(),required=False) addresses: list(ip_interface(),required=False)
encapsulation: include('encapsulation',required=False) encapsulation: include('encapsulation',required=False)
l2xc: str(required=False) l2xc: str(required=False)
state: enum('up', 'down', required=False)
--- ---
encapsulation: encapsulation:
dot1q: int(min=1,max=4095,required=False) 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" description: "This sub-int is invalid because it has no outer dot1q and dot1ad"
encapsulation: encapsulation:
inner-dot1q: 1000 inner-dot1q: 1000
102: 102:
description: "This sub-int is has the same encap as 103" description: "This sub-int is has the same encap as 103"
state: down
103: 103:
description: "This sub-int is has the same encap as 102" description: "This sub-int is has the same encap as 102"
encapsulation: encapsulation:
@ -59,6 +59,7 @@ interfaces:
GigabitEthernet2/0/0: GigabitEthernet2/0/0:
description: "This interface has no sub-ints" description: "This interface has no sub-ints"
lcp: "e2" lcp: "e2"
state: down
GigabitEthernet3/0/0: GigabitEthernet3/0/0:
l2xc: GigabitEthernet3/0/1 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 config_admin_state = 1
else: else:
vpp_ifname, config_iface = interface.get_by_name(self.cfg, ifname) 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 vpp_admin_state = 0
if vpp_ifname in self.vpp.cache['interface_names']: 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']: if iface.sw_if_index in self.cache['l2xcs']:
l2xc = self.cache['l2xcs'][iface.sw_if_index] l2xc = self.cache['l2xcs'][iface.sw_if_index]
i['l2xc'] = self.cache['interfaces'][l2xc.tx_sw_if_index].interface_name 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] i['mtu'] = iface.mtu[0]
if iface.sub_number_of_tags == 0: if iface.sub_number_of_tags == 0:
config['interfaces'][iface.interface_name] = i config['interfaces'][iface.interface_name] = i