diff --git a/schema.yaml b/schema.yaml index 75586d3..665c530 100644 --- a/schema.yaml +++ b/schema.yaml @@ -27,6 +27,7 @@ interface: mtu: int(min=128,max=9216,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) + l2xc: str(required=False) --- sub-interface: description: str(exclude='\'"',required=False) @@ -34,6 +35,7 @@ sub-interface: mtu: int(min=128,max=9216,required=False) addresses: list(ip_interface(),required=False) encapsulation: include('encapsulation',required=False) + l2xc: str(required=False) --- encapsulation: dot1q: int(min=1,max=4095,required=False) diff --git a/unittest/correct-l2xc.yaml b/unittest/correct-l2xc.yaml new file mode 100644 index 0000000..6bc9a55 --- /dev/null +++ b/unittest/correct-l2xc.yaml @@ -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 diff --git a/unittest/error-bridgedomain7.yaml b/unittest/error-bridgedomain7.yaml new file mode 100644 index 0000000..69436cf --- /dev/null +++ b/unittest/error-bridgedomain7.yaml @@ -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 ] diff --git a/unittest/error-l2xc1.yaml b/unittest/error-l2xc1.yaml new file mode 100644 index 0000000..1c66793 --- /dev/null +++ b/unittest/error-l2xc1.yaml @@ -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 diff --git a/unittest/error-l2xc2.yaml b/unittest/error-l2xc2.yaml new file mode 100644 index 0000000..3d5cc6f --- /dev/null +++ b/unittest/error-l2xc2.yaml @@ -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 diff --git a/unittest/error-l2xc3.yaml b/unittest/error-l2xc3.yaml new file mode 100644 index 0000000..79aea38 --- /dev/null +++ b/unittest/error-l2xc3.yaml @@ -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 ] diff --git a/unittest/error-l2xc4.yaml b/unittest/error-l2xc4.yaml new file mode 100644 index 0000000..cfbd0cb --- /dev/null +++ b/unittest/error-l2xc4.yaml @@ -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 ] diff --git a/unittest/error-l2xc5.yaml b/unittest/error-l2xc5.yaml new file mode 100644 index 0000000..6e0bb2a --- /dev/null +++ b/unittest/error-l2xc5.yaml @@ -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 diff --git a/validator/interface.py b/validator/interface.py index f1085a1..edbc6a5 100644 --- a/validator/interface.py +++ b/validator/interface.py @@ -133,6 +133,65 @@ def is_bridge_interface(yaml, ifname): 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): """ Returns True if this interface or sub-interface has an LCP """ if not 'interfaces' in yaml: @@ -322,6 +381,25 @@ def validate_interfaces(yaml): if not address.is_allowed(yaml, ifname, iface['addresses'], a): msgs.append("interface %s IP address %s conflicts with another" % (ifname, a)) 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): 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): msgs.append("sub-interface %s doesn't have unique encapsulation" % (sub_ifname)) 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