Add l2xc support, including extensive tests to validate correct usage of the feature
This commit is contained in:
@ -27,6 +27,7 @@ interface:
|
|||||||
mtu: int(min=128,max=9216,required=False)
|
mtu: int(min=128,max=9216,required=False)
|
||||||
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)
|
||||||
---
|
---
|
||||||
sub-interface:
|
sub-interface:
|
||||||
description: str(exclude='\'"',required=False)
|
description: str(exclude='\'"',required=False)
|
||||||
@ -34,6 +35,7 @@ sub-interface:
|
|||||||
mtu: int(min=128,max=9216,required=False)
|
mtu: int(min=128,max=9216,required=False)
|
||||||
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)
|
||||||
---
|
---
|
||||||
encapsulation:
|
encapsulation:
|
||||||
dot1q: int(min=1,max=4095,required=False)
|
dot1q: int(min=1,max=4095,required=False)
|
||||||
|
41
unittest/correct-l2xc.yaml
Normal file
41
unittest/correct-l2xc.yaml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
test:
|
||||||
|
description: "A few correct ways of setting L2 cross connects"
|
||||||
|
errors:
|
||||||
|
count: 0
|
||||||
|
---
|
||||||
|
bondethernets:
|
||||||
|
BondEthernet0:
|
||||||
|
interfaces: [ GigabitEthernet3/0/0, GigabitEthernet3/0/1 ]
|
||||||
|
|
||||||
|
interfaces:
|
||||||
|
GigabitEthernet1/0/0:
|
||||||
|
description: "Cross connected to Gi1/0/1"
|
||||||
|
l2xc: GigabitEthernet1/0/1
|
||||||
|
GigabitEthernet1/0/1:
|
||||||
|
description: "Cross connected to Gi1/0/0"
|
||||||
|
l2xc: GigabitEthernet1/0/0
|
||||||
|
|
||||||
|
GigabitEthernet2/0/0:
|
||||||
|
description: "Cross connected to Gi2/0/1.100"
|
||||||
|
l2xc: GigabitEthernet2/0/1.100
|
||||||
|
GigabitEthernet2/0/1:
|
||||||
|
description: "Main phy with a subint"
|
||||||
|
sub-interfaces:
|
||||||
|
100:
|
||||||
|
description: "Cross connected to Gi2/0/0"
|
||||||
|
l2xc: GigabitEthernet2/0/0
|
||||||
|
|
||||||
|
GigabitEthernet3/0/0:
|
||||||
|
mtu: 3000
|
||||||
|
GigabitEthernet3/0/1:
|
||||||
|
mtu: 3000
|
||||||
|
|
||||||
|
BondEthernet0:
|
||||||
|
description: "BE0 with two xconnected sub-ints"
|
||||||
|
sub-interfaces:
|
||||||
|
100:
|
||||||
|
description: "Cross connected to BE0.101"
|
||||||
|
l2xc: BondEthernet0.101
|
||||||
|
101:
|
||||||
|
description: "Cross connected to BE0.100"
|
||||||
|
l2xc: BondEthernet0.100
|
22
unittest/error-bridgedomain7.yaml
Normal file
22
unittest/error-bridgedomain7.yaml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
test:
|
||||||
|
description: "An interface that is in a bridgedomain, cannot also be an l2 cross connect"
|
||||||
|
errors:
|
||||||
|
expected:
|
||||||
|
- "interface .* l2xc target .* is in a bridgedomain"
|
||||||
|
count: 1
|
||||||
|
---
|
||||||
|
interfaces:
|
||||||
|
GigabitEthernet1/0/0:
|
||||||
|
mtu: 3000
|
||||||
|
GigabitEthernet1/0/1:
|
||||||
|
mtu: 3000
|
||||||
|
|
||||||
|
GigabitEthernet1/0/1:
|
||||||
|
mtu: 3000
|
||||||
|
l2xc: GigabitEthernet1/0/0
|
||||||
|
|
||||||
|
bridgedomains:
|
||||||
|
bd10:
|
||||||
|
description: "Bridge Domain 10 has Gi1/0/0 which is also a target of an L2XC"
|
||||||
|
mtu: 3000
|
||||||
|
interfaces: [ GigabitEthernet1/0/0, GigabitEthernet1/0/1 ]
|
18
unittest/error-l2xc1.yaml
Normal file
18
unittest/error-l2xc1.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
test:
|
||||||
|
description: "L2 cross connect targets cannot occur more than once"
|
||||||
|
errors:
|
||||||
|
expected:
|
||||||
|
- "interface .* l2xc target .* is not unique"
|
||||||
|
count: 2
|
||||||
|
---
|
||||||
|
interfaces:
|
||||||
|
GigabitEthernet1/0/0:
|
||||||
|
description: "Cross connected to Gi1/0/1"
|
||||||
|
l2xc: GigabitEthernet1/0/1
|
||||||
|
GigabitEthernet1/0/1:
|
||||||
|
description: "Cross connected to Gi1/0/0"
|
||||||
|
l2xc: GigabitEthernet1/0/0
|
||||||
|
|
||||||
|
GigabitEthernet2/0/0:
|
||||||
|
description: "Cross connected to Gi1/0/0 as well"
|
||||||
|
l2xc: GigabitEthernet1/0/0
|
14
unittest/error-l2xc2.yaml
Normal file
14
unittest/error-l2xc2.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
test:
|
||||||
|
description: "L2 cross connect targets must exist"
|
||||||
|
errors:
|
||||||
|
expected:
|
||||||
|
- "interface .* l2xc target .* does not exist"
|
||||||
|
count: 1
|
||||||
|
---
|
||||||
|
interfaces:
|
||||||
|
GigabitEthernet1/0/0:
|
||||||
|
description: "Cross connected to Gi1/0/1"
|
||||||
|
l2xc: GigabitEthernet1/0/1
|
||||||
|
GigabitEthernet1/0/1:
|
||||||
|
description: "Cross connected to Gi1/0/2, which does not exist"
|
||||||
|
l2xc: GigabitEthernet1/0/2
|
18
unittest/error-l2xc3.yaml
Normal file
18
unittest/error-l2xc3.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
test:
|
||||||
|
description: "L2 cross connect targets cannot also occur in a bridgedomain"
|
||||||
|
errors:
|
||||||
|
expected:
|
||||||
|
- "interface .* l2xc target .* in a bridgedomain"
|
||||||
|
count: 1
|
||||||
|
---
|
||||||
|
interfaces:
|
||||||
|
GigabitEthernet1/0/0:
|
||||||
|
description: "Cross connected to Gi1/0/1"
|
||||||
|
l2xc: GigabitEthernet1/0/1
|
||||||
|
GigabitEthernet1/0/1:
|
||||||
|
description: "In a Bridge Domain, so cannot be a target of L2XC"
|
||||||
|
|
||||||
|
bridgedomains:
|
||||||
|
bd10:
|
||||||
|
description: "A Bridge with gi1/0/0 which also occurs as an L2XC target"
|
||||||
|
interfaces: [ GigabitEthernet1/0/1 ]
|
18
unittest/error-l2xc4.yaml
Normal file
18
unittest/error-l2xc4.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
test:
|
||||||
|
description: "L2 cross connect targets cannot have an IP address or LCP"
|
||||||
|
errors:
|
||||||
|
expected:
|
||||||
|
- "interface .* l2xc so it cannot be an LCP"
|
||||||
|
- "interface .* l2xc so it cannot have an address"
|
||||||
|
count: 3
|
||||||
|
---
|
||||||
|
interfaces:
|
||||||
|
GigabitEthernet1/0/0:
|
||||||
|
description: "Cross connected to Gi1/0/1, but should not have an LCP"
|
||||||
|
l2xc: GigabitEthernet1/0/1
|
||||||
|
lcp: "e1-0-0"
|
||||||
|
GigabitEthernet1/0/1:
|
||||||
|
description: "Cross connected to Gi1/0/0, but should not have address and LCP"
|
||||||
|
l2xc: GigabitEthernet1/0/0
|
||||||
|
lcp: "e1-0-1"
|
||||||
|
addresses: [ 192.0.2.1/30 ]
|
18
unittest/error-l2xc5.yaml
Normal file
18
unittest/error-l2xc5.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
test:
|
||||||
|
description: "L2 cross connect from a phy cannot also have sub-interfaces"
|
||||||
|
errors:
|
||||||
|
expected:
|
||||||
|
- "interface .* l2xc so it cannot have sub-interfaces"
|
||||||
|
count: 1
|
||||||
|
---
|
||||||
|
interfaces:
|
||||||
|
GigabitEthernet1/0/0:
|
||||||
|
l2xc: GigabitEthernet1/0/1.100
|
||||||
|
sub-interfaces:
|
||||||
|
100:
|
||||||
|
description: "If the parent is cross connected, it should not have sub-interfaces"
|
||||||
|
|
||||||
|
GigabitEthernet1/0/1:
|
||||||
|
sub-interfaces:
|
||||||
|
100:
|
||||||
|
l2xc: GigabitEthernet1/0/0
|
@ -133,6 +133,65 @@ def is_bridge_interface(yaml, ifname):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_l2xc_interfaces(yaml):
|
||||||
|
""" Returns a list of all interfaces that have an L2 CrossConnect """
|
||||||
|
ret = []
|
||||||
|
if not 'interfaces' in yaml:
|
||||||
|
return ret
|
||||||
|
for ifname, iface in yaml['interfaces'].items():
|
||||||
|
if 'l2xc' in iface:
|
||||||
|
ret.extend(ifname)
|
||||||
|
if 'sub-interfaces' in iface:
|
||||||
|
for sub_ifname, sub_iface in iface['sub-interfaces'].items():
|
||||||
|
if 'l2xc' in sub_iface:
|
||||||
|
ret.extend(sub_ifname)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def is_l2xc_interface(yaml, ifname):
|
||||||
|
""" Returns True if this interface has an L2 CrossConnect """
|
||||||
|
|
||||||
|
if ifname in get_l2xc_interfaces(yaml):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_l2xc_target_interfaces(yaml):
|
||||||
|
""" Returns a list of all interfaces that are the target of an L2 CrossConnect """
|
||||||
|
ret = []
|
||||||
|
if not 'interfaces' in yaml:
|
||||||
|
return ret
|
||||||
|
for ifname, iface in yaml['interfaces'].items():
|
||||||
|
if 'l2xc' in iface:
|
||||||
|
ret.append(iface['l2xc'])
|
||||||
|
if 'sub-interfaces' in iface:
|
||||||
|
for sub_ifname, sub_iface in iface['sub-interfaces'].items():
|
||||||
|
if 'l2xc' in sub_iface:
|
||||||
|
ret.append(sub_iface['l2xc'])
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def is_l2xc_target_interface(yaml, ifname):
|
||||||
|
""" Returns True if this interface is the target of an L2 CrossConnect """
|
||||||
|
|
||||||
|
if ifname in get_l2xc_target_interfaces(yaml):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_l2xc_target_interface_unique(yaml, ifname):
|
||||||
|
""" Returns True if this interface is referenced as an l2xc target zero or one times """
|
||||||
|
|
||||||
|
ifs = get_l2xc_target_interfaces(yaml)
|
||||||
|
n = ifs.count(ifname)
|
||||||
|
|
||||||
|
if n == 0 or n == 1:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def has_lcp(yaml, ifname):
|
def has_lcp(yaml, ifname):
|
||||||
""" Returns True if this interface or sub-interface has an LCP """
|
""" Returns True if this interface or sub-interface has an LCP """
|
||||||
if not 'interfaces' in yaml:
|
if not 'interfaces' in yaml:
|
||||||
@ -322,6 +381,25 @@ def validate_interfaces(yaml):
|
|||||||
if not address.is_allowed(yaml, ifname, iface['addresses'], a):
|
if not address.is_allowed(yaml, ifname, iface['addresses'], a):
|
||||||
msgs.append("interface %s IP address %s conflicts with another" % (ifname, a))
|
msgs.append("interface %s IP address %s conflicts with another" % (ifname, a))
|
||||||
result = False
|
result = False
|
||||||
|
if 'l2xc' in iface:
|
||||||
|
if has_sub(yaml, ifname):
|
||||||
|
msgs.append("interface %s is l2xc so it cannot have sub-interfaces" % (ifname))
|
||||||
|
result = False
|
||||||
|
if iface_lcp:
|
||||||
|
msgs.append("interface %s is l2xc so it cannot be an LCP" % (ifname))
|
||||||
|
result = False
|
||||||
|
if iface_address:
|
||||||
|
msgs.append("interface %s is l2xc so it cannot have an address" % (ifname))
|
||||||
|
result = False
|
||||||
|
if not get_by_name(yaml, iface['l2xc']):
|
||||||
|
msgs.append("interface %s l2xc target %s does not exist" % (ifname, iface['l2xc']))
|
||||||
|
result = False
|
||||||
|
if not is_l2xc_target_interface_unique(yaml, iface['l2xc']):
|
||||||
|
msgs.append("interface %s l2xc target %s is not unique" % (ifname, iface['l2xc']))
|
||||||
|
result = False
|
||||||
|
if is_bridge_interface(yaml, iface['l2xc']):
|
||||||
|
msgs.append("interface %s l2xc target %s is in a bridgedomain" % (ifname, iface['l2xc']))
|
||||||
|
result = False
|
||||||
|
|
||||||
if has_sub(yaml, ifname):
|
if has_sub(yaml, ifname):
|
||||||
for sub_id, sub_iface in yaml['interfaces'][ifname]['sub-interfaces'].items():
|
for sub_id, sub_iface in yaml['interfaces'][ifname]['sub-interfaces'].items():
|
||||||
@ -362,5 +440,22 @@ def validate_interfaces(yaml):
|
|||||||
elif not unique_encapsulation(yaml, sub_ifname):
|
elif not unique_encapsulation(yaml, sub_ifname):
|
||||||
msgs.append("sub-interface %s doesn't have unique encapsulation" % (sub_ifname))
|
msgs.append("sub-interface %s doesn't have unique encapsulation" % (sub_ifname))
|
||||||
result = False
|
result = False
|
||||||
|
if 'l2xc' in sub_iface:
|
||||||
|
if has_lcp(yaml, sub_ifname):
|
||||||
|
msgs.append("sub-interface %s is l2xc so it cannot be an LCP" % (sub_ifname))
|
||||||
|
result = False
|
||||||
|
if has_address(yaml, sub_ifname):
|
||||||
|
msgs.append("sub-interface %s is l2xc so it cannot have an address" % (sub_ifname))
|
||||||
|
result = False
|
||||||
|
if not get_by_name(yaml, sub_iface['l2xc']):
|
||||||
|
msgs.append("sub-interface %s l2xc target %s does not exist" % (ifname, sub_iface['l2xc']))
|
||||||
|
result = False
|
||||||
|
if not is_l2xc_target_interface_unique(yaml, sub_iface['l2xc']):
|
||||||
|
msgs.append("sub-interface %s l2xc target %s is not unique" % (ifname, sub_iface['l2xc']))
|
||||||
|
result = False
|
||||||
|
if is_bridge_interface(yaml, sub_iface['l2xc']):
|
||||||
|
msgs.append("sub-interface %s l2xc target %s is in a bridgedomain" % (ifname, sub_iface['l2xc']))
|
||||||
|
result = False
|
||||||
|
|
||||||
|
|
||||||
return result, msgs
|
return result, msgs
|
||||||
|
Reference in New Issue
Block a user