Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue#125 fixed #161

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion aci-preupgrade-validation-script.py
Original file line number Diff line number Diff line change
Expand Up @@ -3696,6 +3696,55 @@ def validate_32_64_bit_image_check(index, total_checks, tversion, **kwargs):
print_result(title, result, msg, headers, data, recommended_action=recommended_action, doc_url=doc_url)
return result

def leaf_to_spine_redundancy_check(index, total_checks, **kwargs):
title = 'Leaf to Spine Redundancy check'
result = PASS
msg = ''
headers = [" Leaf ", " Spine ", "Message" ]
data = []
problem = 'The Leaf Switch is connected to a Single Spine'
recommended_action = 'Connect the Leaf Switch(es) to multiple Spines for Redundancy'
doc_url = ''
print_title(title, index, total_checks)

# icurl queries
leaf_nodes_api = 'fabricNode.json'
leaf_nodes_api += '?query-target-filter=eq(fabricNode.role,"leaf")'
spine_nodes_api = 'fabricNode.json'
spine_nodes_api += '?query-target-filter=eq(fabricNode.role,"spine")'
lldp_adj_api = 'lldpAdjEp.json'
lldp_adj_api += '?query-target-filter=wcard(lldpAdjEp.sysDesc,"topology/pod")'

leaf_nodes = icurl('class', leaf_nodes_api)
fabricNodes = icurl('class', spine_nodes_api)
spine_nodes = [dn['fabricNode']['attributes']['name'] for dn in fabricNodes]
lldp_adj = icurl('class', lldp_adj_api)

#Check for LLDP Adj Matching with Node DN, count Number of neighbors, break if there are more than 2
for leaf in leaf_nodes:
if leaf['fabricNode']['attributes']['nodeType'] == 'tier-2-leaf':
continue #Skip for any tier-2 Leaf
neighbors = {}
for lldp_neighbor in lldp_adj:
if leaf['fabricNode']['attributes']['dn'] in lldp_neighbor['lldpAdjEp']['attributes']['dn']:
# Add Neighborship count based on Spine name
spine = lldp_neighbor['lldpAdjEp']['attributes']['sysName']
if spine in spine_nodes:
neighbors[spine] = neighbors.get(spine, 0 ) + 1
else:
continue
if len(neighbors) > 1:
# Leaf has more than 1 Spine as neighbor check passed
continue
else:
# Leaf has only 1 neighbor Check fails
data.append([leaf['fabricNode']['attributes']['name'], list(neighbors.keys())[0], problem])
if data:
result = FAIL_O

print_result(title, result, msg, headers, data, recommended_action=recommended_action, doc_url=doc_url)
return result


if __name__ == "__main__":
prints(' ==== %s%s, Script Version %s ====\n' % (ts, tz, SCRIPT_VERSION))
Expand All @@ -3722,7 +3771,7 @@ def validate_32_64_bit_image_check(index, total_checks, tversion, **kwargs):
"script_version": str(SCRIPT_VERSION), "check_details": [],
'cversion': str(cversion), 'tversion': str(tversion)}
checks = [
# General Checks
#General Checks
apic_version_md5_check,
target_version_compatibility_check,
gen1_switch_compatibility_check,
Expand All @@ -3737,6 +3786,7 @@ def validate_32_64_bit_image_check(index, total_checks, tversion, **kwargs):
mini_aci_6_0_2_check,
post_upgrade_cb_check,
validate_32_64_bit_image_check,
leaf_to_spine_redundancy_check,

# Faults
apic_disk_space_faults_check,
Expand Down
10 changes: 8 additions & 2 deletions docs/docs/validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Items | This Script
[Mini ACI Upgrade to 6.0(2)+][g14] | :white_check_mark: | :no_entry_sign: | :no_entry_sign:
[Post Upgrade CallBack Integrity][g15] | :white_check_mark: | :no_entry_sign: | :no_entry_sign:
[6.0(2)+ requires 32 and 64 bit switch images][g16] | :white_check_mark: | :no_entry_sign: | :no_entry_sign:

[Leaf to Spine Redundancy Validation][g17] | :white_check_mark: | :no_entry_sign: | :no_entry_sign:

[g1]: #compatibility-target-aci-version
[g2]: #compatibility-cimc-version
Expand All @@ -52,6 +52,7 @@ Items | This Script
[g14]: #mini-aci-upgrade-to-602-or-later
[g15]: #post-upgrade-callback-integrity
[g16]: #602-requires-32-and-64-bit-switch-images
[g17]: #leaf-to-spine-redundancy-validation

### Fault Checks
Items | Faults | This Script | APIC built-in | Pre-Upgrade Validator (App)
Expand Down Expand Up @@ -427,6 +428,10 @@ When targeting any version that is 6.0(2) or greater, download both the 32-bit a

For additional information, see the [Guidelines and Limitations for Upgrading or Downgrading][28] section of the Cisco APIC Installation and ACI Upgrade and Downgrade Guide.

### Leaf to Spine Redundancy Validation
When Upgrading the Spine Switches Data plane traffic will be affected, any Leaf Switch connecting to that Spine will failover to another active Spine.
If a Leaf Switch is single homed to a Spine, traffic for and from the Leaf will be black holed during upgrade process.
For additional information, check [Cisco ACI Best Practices Quick Summary][31]

## Fault Check Details

Expand Down Expand Up @@ -1971,4 +1976,5 @@ If found, the target version of your upgrade should be a version with a fix for
[27]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwb91766
[28]: https://www.cisco.com/c/en/us/td/docs/dcn/aci/apic/all/apic-installation-aci-upgrade-downgrade/Cisco-APIC-Installation-ACI-Upgrade-Downgrade-Guide/m-aci-upgrade-downgrade-architecture.html#Cisco_Reference.dita_22480abb-4138-416b-8dd5-ecde23f707b4
[29]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwb86706
[30]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwf44222
[30]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwf44222
[31]: https://www.cisco.com/c/en/us/td/docs/dcn/whitepapers/cisco-aci-best-practices-quick-summary.html#SwitchConnectivity
34 changes: 34 additions & 0 deletions tests/leaf_to_spine_redundancy_check/fabricNode-leaf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[
{
"fabricNode": {
"attributes": {
"dn": "topology/pod-1/node-101",
"id": "101",
"name": "LF101",
"role": "leaf",
"nodeType": "unspecified"
}
}
},
{
"fabricNode": {
"attributes": {
"dn": "topology/pod-1/node-102",
"id": "102",
"name": "LF102",
"role": "leaf",
"nodeType": "unspecified"
}
}
},
{
"fabricNode": {
"attributes": {
"dn": "topology/pod-1/node-103",
"id": "103",
"role": "leaf",
"nodeType": "tier-2-leaf"
}
}
}
]
24 changes: 24 additions & 0 deletions tests/leaf_to_spine_redundancy_check/fabricNode-spine.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[
{
"fabricNode": {
"attributes": {
"dn": "topology/pod-1/node-1001",
"id": "1001",
"name": "SP1001",
"role": "spine",
"nodeType": "unspecified"
}
}
},
{
"fabricNode": {
"attributes": {
"dn": "topology/pod-1/node-1002",
"id": "1002",
"name": "SP1002",
"role": "spine",
"nodeType": "unspecified"
}
}
}
]
42 changes: 42 additions & 0 deletions tests/leaf_to_spine_redundancy_check/lldpAdjEp-neg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[
{
"lldpAdjEp": {
"attributes": {
"dn": "topology/pod-1/node-101/sys/lldp/inst/if-[eth1/49]/adj-1",
"portDesc": "topology/pod-1/paths-1002/pathep-[eth1/4]",
"sysDesc": "topology/pod-1/node-1002",
"sysName": "SP1002"
}
}
},
{
"lldpAdjEp": {
"attributes": {
"dn": "topology/pod-1/node-101/sys/lldp/inst/if-[eth1/50]/adj-1",
"portDesc": "topology/pod-1/paths-1002/pathep-[eth1/7]",
"sysDesc": "topology/pod-1/node-1002",
"sysName": "SP1002"
}
}
},
{
"lldpAdjEp": {
"attributes": {
"dn": "topology/pod-1/node-102/sys/lldp/inst/if-[eth1/50]/adj-1",
"portDesc": "topology/pod-1/paths-1001/pathep-[eth1/9]",
"sysDesc": "topology/pod-1/node-1001",
"sysName": "SP1001"
}
}
},
{
"lldpAdjEp": {
"attributes": {
"dn": "topology/pod-1/node-102/sys/lldp/inst/if-[eth1/49]/adj-1",
"portDesc": "topology/pod-1/paths-1002/pathep-[eth1/10]",
"sysDesc": "topology/pod-1/node-1002",
"sysName": "SP1002"
}
}
}
]
52 changes: 52 additions & 0 deletions tests/leaf_to_spine_redundancy_check/lldpAdjEp-pos.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[
{
"lldpAdjEp": {
"attributes": {
"dn": "topology/pod-1/node-101/sys/lldp/inst/if-[eth1/49]/adj-1",
"portDesc": "topology/pod-1/paths-1001/pathep-[eth1/4]",
"sysDesc": "topology/pod-1/node-1001",
"sysName": "SP1001"
}
}
},
{
"lldpAdjEp": {
"attributes": {
"dn": "topology/pod-1/node-101/sys/lldp/inst/if-[eth1/50]/adj-1",
"portDesc": "topology/pod-1/paths-1002/pathep-[eth1/7]",
"sysDesc": "topology/pod-1/node-1002",
"sysName": "SP1002"
}
}
},
{
"lldpAdjEp": {
"attributes": {
"dn": "topology/pod-1/node-102/sys/lldp/inst/if-[eth1/50]/adj-1",
"portDesc": "topology/pod-1/paths-1001/pathep-[eth1/9]",
"sysDesc": "topology/pod-1/node-1001",
"sysName": "SP1001"
}
}
},
{
"lldpAdjEp": {
"attributes": {
"dn": "topology/pod-1/node-102/sys/lldp/inst/if-[eth1/49]/adj-1",
"portDesc": "topology/pod-1/paths-1002/pathep-[eth1/10]",
"sysDesc": "topology/pod-1/node-1002",
"sysName": "SP1002"
}
}
},
{
"lldpAdjEp": {
"attributes": {
"dn": "topology/pod-1/node-103/sys/lldp/inst/if-[eth1/49]/adj-1",
"portDesc": "topology/pod-1/paths-102/pathep-[eth1/10]",
"sysDesc": "topology/pod-1/node-102",
"sysName": "LF1002"
}
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import os
import pytest
import logging
import importlib
from helpers.utils import read_data

script = importlib.import_module("aci-preupgrade-validation-script")

log = logging.getLogger(__name__)
dir = os.path.dirname(os.path.abspath(__file__))

# icurl queries
leaf_nodes_api = 'fabricNode.json'
leaf_nodes_api += '?query-target-filter=eq(fabricNode.role,"leaf")'

#icurl queries
spine_nodes_api = 'fabricNode.json'
spine_nodes_api += '?query-target-filter=eq(fabricNode.role,"spine")'

# icurl queries
lldp_adj_api = 'lldpAdjEp.json'
lldp_adj_api += '?query-target-filter=wcard(lldpAdjEp.sysDesc,"topology/pod")'


@pytest.mark.parametrize(
"icurl_outputs, expected_result",
[
##FAILING = ONE LEAF SWITCH IS SINGLE-HOMED, OTHER IS MULTI-HOMED, TIER2 LEAF IN THE NODE LIST
(
{
leaf_nodes_api: read_data(dir, "fabricNode-leaf.json"),
lldp_adj_api: read_data(dir, "lldpAdjEp-neg.json"),
spine_nodes_api: read_data(dir, "fabricNode-spine.json")
},
script.FAIL_O,
),
##PASSING = ALL LEAF SWITCHES ARE MULTI-HOMED , TIER2 LEAF IN THE NODE LIST
(
{
leaf_nodes_api: read_data(dir, "fabricNode-leaf.json"),
lldp_adj_api: read_data(dir, "lldpAdjEp-pos.json"),
spine_nodes_api: read_data(dir, "fabricNode-spine.json")
},
script.PASS,
),

],
)
def test_logic(mock_icurl , expected_result):
result = script.leaf_to_spine_redundancy_check(1, 1 )
assert result == expected_result