From 63f58591e056498c8d46e35a9b1c94ddda8558ac Mon Sep 17 00:00:00 2001 From: muthuku Date: Mon, 1 Dec 2025 11:08:19 +0000 Subject: [PATCH 1/7] Added new CFD validation for CSCwn81692 --- aci-preupgrade-validation-script.py | 56 ++++++++++ docs/docs/validations.md | 17 +++ .../fabricNode.json | 24 ++++ .../fabricNode_empty.json | 1 + .../presListener.json | 21 ++++ .../presListener_empty.json | 1 + .../presListener_missing_node.json | 10 ++ ...ctive_node_presListener_mo_object_check.py | 103 ++++++++++++++++++ 8 files changed, 233 insertions(+) create mode 100644 tests/checks/active_node_presListener_mo_object_check/fabricNode.json create mode 100644 tests/checks/active_node_presListener_mo_object_check/fabricNode_empty.json create mode 100644 tests/checks/active_node_presListener_mo_object_check/presListener.json create mode 100644 tests/checks/active_node_presListener_mo_object_check/presListener_empty.json create mode 100644 tests/checks/active_node_presListener_mo_object_check/presListener_missing_node.json create mode 100644 tests/checks/active_node_presListener_mo_object_check/test_active_node_presListener_mo_object_check.py diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index bfca5bb6..e1a06ac8 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -5962,6 +5962,61 @@ def configpush_shard_check(tversion, **kwargs): return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) +@check_wrapper(check_title='active_node pres.Listener mo object check') +def active_node_presListener_mo_object_check(tversion, **kwargs): + result = PASS + headers = ["Missing Node ID", "Node Status"] + data = [] + fabric_leaf_ids = [] + preslistener_leaf_ids = [] + recommended_action = 'PresListener objects should be configured for all fabric leaf active nodes before upgrade and target version with the fix for CSCwn81692,version > 6.1(3)' + doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#active_node_presListener_mo_object_check' + + url1= 'presListener.json?query-target-filter=wcard(presListener.dn,"4307")' + url2='fabricNode.json?query-target-filter=and(wcard(fabricNode.role,"leaf"),wcard(fabricNode.fabricSt,"active"))' + + presListeners = icurl('class', url1 ) + fabric_leaf_data= icurl('class', url2) + + node_regex = r"uni/infra/nodecfgcont/node-(?P\d+)" + class_regex = r"resregistry/resregistry-(?P\d+)/class-(?P\d+)" + + if not tversion: + return Result(result=MANUAL, msg=TVER_MISSING) + + # Only run check if target version < 6.1(3f) + if tversion and tversion.older_than("6.1(3f)"): + + if fabric_leaf_data : + for leaf in fabric_leaf_data: + leaf_id = leaf['fabricNode']['attributes']['id'] + fabric_leaf_ids.append(leaf_id) + + if presListeners : + for presListener_mo in presListeners: + dn = presListener_mo['presListener']['attributes']['dn'] + node_match = re.search(node_regex, dn) + class_match = re.search(class_regex, dn) + + if class_match and class_match.group("class") == "4307" and node_match: + node = node_match.group("node") + class_id = class_match.group("class") + preslistener_leaf_ids.append(node) + + missing_nodes = set(fabric_leaf_ids) - set(preslistener_leaf_ids) + + if missing_nodes: + for node_id in sorted(missing_nodes): + data.append([node_id, "active"]) + log.warning("Alert ! Some Leaf Nodes missing in PresListener Object \n\n Missing Nodes: {}".format(missing_nodes )) + result = FAIL_O + return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) + if not fabric_leaf_ids: + return Result(result=FAIL_UF, msg="Could not retrieve any leaf node data") + return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) + else: + return Result(result=NA, msg=VER_NOT_AFFECTED) + # ---- Script Execution ---- @@ -6122,6 +6177,7 @@ class CheckManager: standby_sup_sync_check, isis_database_byte_check, configpush_shard_check, + active_node_presListener_mo_object_check ] ssh_checks = [ diff --git a/docs/docs/validations.md b/docs/docs/validations.md index e395564f..1d327743 100644 --- a/docs/docs/validations.md +++ b/docs/docs/validations.md @@ -191,6 +191,8 @@ Items | Defect | This Script [Stale pconsRA Object][d26] | CSCwp22212 | :warning:{title="Deprecated"} | :no_entry_sign: [ISIS DTEPs Byte Size][d27] | CSCwp15375 | :white_check_mark: | :no_entry_sign: [Policydist configpushShardCont Crash][d28] | CSCwp95515 | :white_check_mark: | +[active_node_presListener_mo_object_check][d29] | CSCwn81692 | :white_check_mark: | :no_entry_sign: + [d1]: #ep-announce-compatibility [d2]: #eventmgr-db-size-defect-susceptibility @@ -220,6 +222,7 @@ Items | Defect | This Script [d26]: #stale-pconsra-object [d27]: #isis-dteps-byte-size [d28]: #policydist-configpushshardcont-crash +[d29]: #active_node_presListener_mo_object_check ## General Check Details @@ -2604,6 +2607,19 @@ Due to [CSCwp95515][59], upgrading to an affected version while having any `conf If any instances of `configpushShardCont` are flagged by this script, Cisco TAC must be contacted to identify and resolve the underlying issue before performing the upgrade. +### active_node_presListener_mo_object_check + +RCA: +After the leaf upgrade to 6.0.6c, infraAccPortP and infraAccBndlGrp failed to program the interfaces. +This happened due to the "pres.Listener" missing object in the APIC after the apic upgrade 6.0.6c. + +IMPACT : +After the leaf upgrade or when the leaf clean-reloaded, if the "pres.Listener" is missing in the APIC, the leaf ports will not program, and the port status will be kept out of service and infraAccPortP and infraAccBndlGrp will be failed to program the interfaces. + +Suggestion: +This check will verify the all active node object presence in pres.Listener list with class 4307 and alert if targeting an affected version. If alerted, the recommended action is to target a version that has the fix for [CSCwn81692][62]. + + [0]: https://github.com/datacenter/ACI-Pre-Upgrade-Validation-Script [1]: https://www.cisco.com/c/dam/en/us/td/docs/Website/datacenter/apicmatrix/index.html [2]: https://www.cisco.com/c/en/us/support/switches/nexus-9000-series-switches/products-release-notes-list.html @@ -2666,3 +2682,4 @@ If any instances of `configpushShardCont` are flagged by this script, Cisco TAC [59]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwp95515 [60]: https://www.cisco.com/c/en/us/solutions/collateral/data-center-virtualization/application-centric-infrastructure/white-paper-c11-743951.html#Inter [61]: https://www.cisco.com/c/en/us/solutions/collateral/data-center-virtualization/application-centric-infrastructure/white-paper-c11-743951.html#EnablePolicyCompression +[62]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwn81692 diff --git a/tests/checks/active_node_presListener_mo_object_check/fabricNode.json b/tests/checks/active_node_presListener_mo_object_check/fabricNode.json new file mode 100644 index 00000000..5dca0137 --- /dev/null +++ b/tests/checks/active_node_presListener_mo_object_check/fabricNode.json @@ -0,0 +1,24 @@ +[ + { + + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-104", + "fabricSt": "active", + "id": "104", + "role": "leaf" + } + } +}, +{ + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-105", + "fabricSt": "active", + "id": "105", + "role": "leaf" + } + } +} + +] diff --git a/tests/checks/active_node_presListener_mo_object_check/fabricNode_empty.json b/tests/checks/active_node_presListener_mo_object_check/fabricNode_empty.json new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/tests/checks/active_node_presListener_mo_object_check/fabricNode_empty.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/checks/active_node_presListener_mo_object_check/presListener.json b/tests/checks/active_node_presListener_mo_object_check/presListener.json new file mode 100644 index 00000000..741e7b58 --- /dev/null +++ b/tests/checks/active_node_presListener_mo_object_check/presListener.json @@ -0,0 +1,21 @@ +[ + { + "presListener": { + "attributes": { + "childAction": "", + "dn": "resregistry/resregistry-20/relnholder/rspresClass-[resregistry/resregistry-20/class-4307]/list-[uni/infra/nodecfgcont/node-104]", + "lstDn": "uni/infra/nodecfgcont/node-104" + } + } + }, + { + "presListener": { + "attributes": { + "dn": "resregistry/resregistry-20/relnholder/rspresClass-[resregistry/resregistry-20/class-4307]/list-[uni/infra/nodecfgcont/node-105]", + "lstDn": "uni/infra/nodecfgcont/node-105" + + } + } + } + +] diff --git a/tests/checks/active_node_presListener_mo_object_check/presListener_empty.json b/tests/checks/active_node_presListener_mo_object_check/presListener_empty.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/tests/checks/active_node_presListener_mo_object_check/presListener_empty.json @@ -0,0 +1 @@ +[] diff --git a/tests/checks/active_node_presListener_mo_object_check/presListener_missing_node.json b/tests/checks/active_node_presListener_mo_object_check/presListener_missing_node.json new file mode 100644 index 00000000..66d396a3 --- /dev/null +++ b/tests/checks/active_node_presListener_mo_object_check/presListener_missing_node.json @@ -0,0 +1,10 @@ +[ + { + "presListener": { + "attributes": { + "dn": "resregistry/resregistry-20/relnholder/rspresClass-[resregistry/resregistry-20/class-4307]/list-[uni/infra/nodecfgcont/node-104]", + "lstDn": "uni/infra/nodecfgcont/node-104" + } + } +} +] diff --git a/tests/checks/active_node_presListener_mo_object_check/test_active_node_presListener_mo_object_check.py b/tests/checks/active_node_presListener_mo_object_check/test_active_node_presListener_mo_object_check.py new file mode 100644 index 00000000..e402f953 --- /dev/null +++ b/tests/checks/active_node_presListener_mo_object_check/test_active_node_presListener_mo_object_check.py @@ -0,0 +1,103 @@ +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__)) + +test_function = "active_node_presListener_mo_object_check" + +# icurl queries +presListener = 'presListener.json?query-target-filter=wcard(presListener.dn,"4307")' +fabricNode = 'fabricNode.json?query-target-filter=and(wcard(fabricNode.role,"leaf"),wcard(fabricNode.fabricSt,"active"))' + +@pytest.mark.parametrize( + "icurl_outputs, tversion,expected_result", + [ + #Check pass case + ( + { + presListener: read_data( + dir, "presListener.json" + ), + fabricNode: read_data( + dir, "fabricNode.json" + ), + }, + "6.1(2f)", + script.PASS, + ), + #Check with missing nodes + ( + { + presListener: read_data( + dir, "presListener_missing_node.json" + ), + fabricNode: read_data( + dir, "fabricNode.json" + ), + }, + "6.1(2f)", + script.FAIL_O + ), + #Check with missing nodes on affected version + ( + { + presListener: read_data( + dir, "presListener_missing_node.json" + ), + fabricNode: read_data( + dir, "fabricNode.json" + ), + }, + "5.2(8h)", + script.FAIL_O, + ), + #Check with empty responses for latest version + ( + { + presListener: read_data( + dir, "presListener_empty.json" + ), + fabricNode: read_data( + dir, "fabricNode_empty.json" + ), + }, + "6.1(3h)", + script.NA, + ), + #Check with empty responses for affected version + ( + { + presListener: read_data( + dir, "presListener_empty.json" + ), + fabricNode: read_data( + dir, "fabricNode_empty.json" + ), + }, + "5.2(8h)", + script.FAIL_UF, + ), + # tversion not given + ( + { + presListener: read_data( + dir, "presListener_missing_node.json" + ), + fabricNode: read_data( + dir, "fabricNode.json" + ), + }, + None, + script.MANUAL, + ), + ], +) +def test_logic(run_check,mock_icurl, tversion, expected_result): + result = run_check(tversion=script.AciVersion(tversion) if tversion else None) + assert result.result == expected_result From 5e69a533dc9598b17063e0f94edd50bc53bed09c Mon Sep 17 00:00:00 2001 From: muthuku Date: Wed, 3 Dec 2025 13:31:05 +0000 Subject: [PATCH 2/7] updated validation.md documentation for the cfd --- docs/docs/validations.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/docs/validations.md b/docs/docs/validations.md index 1d327743..988830b6 100644 --- a/docs/docs/validations.md +++ b/docs/docs/validations.md @@ -2609,15 +2609,15 @@ If any instances of `configpushShardCont` are flagged by this script, Cisco TAC ### active_node_presListener_mo_object_check -RCA: -After the leaf upgrade to 6.0.6c, infraAccPortP and infraAccBndlGrp failed to program the interfaces. -This happened due to the "pres.Listener" missing object in the APIC after the apic upgrade 6.0.6c. +RCA: +After upgrading the leaf to an affected version, interface configurations are not pushed because the "pres.Listener" object is missing in the APIC. +This occurs due to an incomplete/faulty APIC upgrade. -IMPACT : -After the leaf upgrade or when the leaf clean-reloaded, if the "pres.Listener" is missing in the APIC, the leaf ports will not program, and the port status will be kept out of service and infraAccPortP and infraAccBndlGrp will be failed to program the interfaces. +IMPACT: +If "pres.Listener" is missing after a leaf upgrade or clean reload, leaf ports remain out of service, and infraAccPortP and infraAccBndlGrp cannot program interfaces. -Suggestion: -This check will verify the all active node object presence in pres.Listener list with class 4307 and alert if targeting an affected version. If alerted, the recommended action is to target a version that has the fix for [CSCwn81692][62]. +Suggestion: +Verify that all active leaf node objects are present in the "pres.Listener" list (class 4307) and alert if the affected version is detected. If an alert is raised, upgrade to a version that includes the fix for [CSCwn81692][62]. [0]: https://github.com/datacenter/ACI-Pre-Upgrade-Validation-Script From def0ceb550d5f11ab54cf89a8592c04dd1f0880c Mon Sep 17 00:00:00 2001 From: muthuku Date: Thu, 4 Dec 2025 06:47:20 +0000 Subject: [PATCH 3/7] addressed syntax/spacing comments --- aci-preupgrade-validation-script.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index e1a06ac8..9a97eebe 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -5969,14 +5969,14 @@ def active_node_presListener_mo_object_check(tversion, **kwargs): data = [] fabric_leaf_ids = [] preslistener_leaf_ids = [] - recommended_action = 'PresListener objects should be configured for all fabric leaf active nodes before upgrade and target version with the fix for CSCwn81692,version > 6.1(3)' + recommended_action = 'Contact Cisco TAC to investigate missing leaf nodes from class-4307 preslisteners objects' doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#active_node_presListener_mo_object_check' - url1= 'presListener.json?query-target-filter=wcard(presListener.dn,"4307")' - url2='fabricNode.json?query-target-filter=and(wcard(fabricNode.role,"leaf"),wcard(fabricNode.fabricSt,"active"))' + presListener_node_objects = 'presListener.json?query-target-filter=wcard(presListener.dn,"4307")' + fabric_leaf_node_objects = 'fabricNode.json?query-target-filter=and(wcard(fabricNode.role,"leaf"),wcard(fabricNode.fabricSt,"active"))' - presListeners = icurl('class', url1 ) - fabric_leaf_data= icurl('class', url2) + presListeners_object_data = icurl('class', presListener_node_objects) + fabric_leaf_object_data = icurl('class', fabric_leaf_node_objects) node_regex = r"uni/infra/nodecfgcont/node-(?P\d+)" class_regex = r"resregistry/resregistry-(?P\d+)/class-(?P\d+)" @@ -5985,15 +5985,15 @@ def active_node_presListener_mo_object_check(tversion, **kwargs): return Result(result=MANUAL, msg=TVER_MISSING) # Only run check if target version < 6.1(3f) - if tversion and tversion.older_than("6.1(3f)"): + if tversion and tversion.older_than("6.1(3f)"): - if fabric_leaf_data : - for leaf in fabric_leaf_data: + if fabric_leaf_object_data: + for leaf in fabric_leaf_object_data: leaf_id = leaf['fabricNode']['attributes']['id'] fabric_leaf_ids.append(leaf_id) - if presListeners : - for presListener_mo in presListeners: + if presListeners_object_data: + for presListener_mo in presListeners_object_data: dn = presListener_mo['presListener']['attributes']['dn'] node_match = re.search(node_regex, dn) class_match = re.search(class_regex, dn) @@ -6008,9 +6008,8 @@ def active_node_presListener_mo_object_check(tversion, **kwargs): if missing_nodes: for node_id in sorted(missing_nodes): data.append([node_id, "active"]) - log.warning("Alert ! Some Leaf Nodes missing in PresListener Object \n\n Missing Nodes: {}".format(missing_nodes )) result = FAIL_O - return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) + return Result(result=result,msg="PresListener Object Missing Nodes: {}".format(missing_nodes ), headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) if not fabric_leaf_ids: return Result(result=FAIL_UF, msg="Could not retrieve any leaf node data") return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) From 32b04af809e716c02b2649473af6d5c5c0618576 Mon Sep 17 00:00:00 2001 From: muthuku Date: Wed, 10 Dec 2025 12:20:16 +0000 Subject: [PATCH 4/7] updated code with latest approach to get fabric_nodes --- aci-preupgrade-validation-script.py | 17 +++-- ...ctive_node_presListener_mo_object_check.py | 64 +++++++++---------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 9a97eebe..153ca1b2 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -1805,6 +1805,10 @@ def query_common_data(api_only=False, arg_cversion=None, arg_tversion=None): } + + + + @check_wrapper(check_title="APIC Cluster Status") def apic_cluster_health_check(cversion, **kwargs): result = FAIL_UF @@ -5963,7 +5967,7 @@ def configpush_shard_check(tversion, **kwargs): return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) @check_wrapper(check_title='active_node pres.Listener mo object check') -def active_node_presListener_mo_object_check(tversion, **kwargs): +def active_node_presListener_mo_object_check(tversion,fabric_nodes, **kwargs): result = PASS headers = ["Missing Node ID", "Node Status"] data = [] @@ -5973,11 +5977,9 @@ def active_node_presListener_mo_object_check(tversion, **kwargs): doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#active_node_presListener_mo_object_check' presListener_node_objects = 'presListener.json?query-target-filter=wcard(presListener.dn,"4307")' - fabric_leaf_node_objects = 'fabricNode.json?query-target-filter=and(wcard(fabricNode.role,"leaf"),wcard(fabricNode.fabricSt,"active"))' - + presListeners_object_data = icurl('class', presListener_node_objects) - fabric_leaf_object_data = icurl('class', fabric_leaf_node_objects) - + node_regex = r"uni/infra/nodecfgcont/node-(?P\d+)" class_regex = r"resregistry/resregistry-(?P\d+)/class-(?P\d+)" @@ -5987,8 +5989,9 @@ def active_node_presListener_mo_object_check(tversion, **kwargs): # Only run check if target version < 6.1(3f) if tversion and tversion.older_than("6.1(3f)"): - if fabric_leaf_object_data: - for leaf in fabric_leaf_object_data: + if fabric_nodes: + fabric_active_leaf_nodes = [node for node in fabric_nodes if node["fabricNode"]["attributes"]["role"] == "leaf" and node["fabricNode"]["attributes"]["fabricSt"] == "active"] + for leaf in fabric_active_leaf_nodes: leaf_id = leaf['fabricNode']['attributes']['id'] fabric_leaf_ids.append(leaf_id) diff --git a/tests/checks/active_node_presListener_mo_object_check/test_active_node_presListener_mo_object_check.py b/tests/checks/active_node_presListener_mo_object_check/test_active_node_presListener_mo_object_check.py index e402f953..a04422dd 100644 --- a/tests/checks/active_node_presListener_mo_object_check/test_active_node_presListener_mo_object_check.py +++ b/tests/checks/active_node_presListener_mo_object_check/test_active_node_presListener_mo_object_check.py @@ -16,88 +16,88 @@ fabricNode = 'fabricNode.json?query-target-filter=and(wcard(fabricNode.role,"leaf"),wcard(fabricNode.fabricSt,"active"))' @pytest.mark.parametrize( - "icurl_outputs, tversion,expected_result", + "icurl_outputs, tversion,fabric_nodes,expected_result", [ - #Check pass case + #CASE 1: Check pass case with all nodes present ( { presListener: read_data( dir, "presListener.json" - ), - fabricNode: read_data( - dir, "fabricNode.json" - ), + ) }, "6.1(2f)", + read_data( + dir, "fabricNode.json" + ), script.PASS, ), - #Check with missing nodes + #CASE 2: Check with missing nodes in presListener mo ( { presListener: read_data( dir, "presListener_missing_node.json" - ), - fabricNode: read_data( - dir, "fabricNode.json" - ), + ) }, "6.1(2f)", + read_data( + dir, "fabricNode.json" + ), script.FAIL_O ), - #Check with missing nodes on affected version + #CASE 3: Check with missing nodes in presListener mo on affected version ( { presListener: read_data( dir, "presListener_missing_node.json" - ), - fabricNode: read_data( - dir, "fabricNode.json" - ), + ) }, "5.2(8h)", + read_data( + dir, "fabricNode.json" + ), script.FAIL_O, ), - #Check with empty responses for latest version + #CASE 4: Check with empty responses of fabric node and presListener mo for latest version ( { presListener: read_data( dir, "presListener_empty.json" - ), - fabricNode: read_data( - dir, "fabricNode_empty.json" - ), + ) }, "6.1(3h)", + read_data( + dir, "fabricNode_empty.json" + ), script.NA, ), - #Check with empty responses for affected version + #CASE 5: Check with empty responses of fabric node and presListener mo for affected version ( { presListener: read_data( dir, "presListener_empty.json" ), - fabricNode: read_data( - dir, "fabricNode_empty.json" - ), }, "5.2(8h)", + read_data( + dir, "fabricNode_empty.json" + ), script.FAIL_UF, ), - # tversion not given + #CASE 6: tversion not given ( { presListener: read_data( dir, "presListener_missing_node.json" - ), - fabricNode: read_data( - dir, "fabricNode.json" - ), + ) }, None, + read_data( + dir, "fabricNode.json" + ), script.MANUAL, ), ], ) -def test_logic(run_check,mock_icurl, tversion, expected_result): - result = run_check(tversion=script.AciVersion(tversion) if tversion else None) +def test_logic(run_check,mock_icurl, tversion,fabric_nodes, expected_result): + result = run_check(tversion=script.AciVersion(tversion) if tversion else None, fabric_nodes=fabric_nodes) assert result.result == expected_result From 7915a424b4a334df204d186789ce6a7d326d91b8 Mon Sep 17 00:00:00 2001 From: muthuku Date: Mon, 22 Dec 2025 05:48:42 +0000 Subject: [PATCH 5/7] merged latest changes --- aci-preupgrade-validation-script.py | 10 +++++- tests/test_get_vpc_node.py | 49 +++++++++-------------------- 2 files changed, 23 insertions(+), 36 deletions(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 153ca1b2..527f757c 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -1764,7 +1764,15 @@ def get_vpc_nodes(): """ Returns list of VPC Node IDs; ['101', '102', etc...] """ prints("Collecting VPC Node IDs...", end='') vpc_nodes = [] - prot_pols = icurl('class', 'fabricNodePEp.json') + try: + prot_pols = icurl('class', 'fabricNodePEp.json') + except Exception as e: + # CSCws30568: expected for fabricNodePEp to return non-zero totalCount + # incorrectly for an empty response. + if str(e).startswith("API response empty with totalCount:"): + prot_pols = [] + else: + raise e for vpc_node in prot_pols: vpc_nodes.append(vpc_node['fabricNodePEp']['attributes']['id']) vpc_nodes.sort() diff --git a/tests/test_get_vpc_node.py b/tests/test_get_vpc_node.py index 956377cc..613e4587 100644 --- a/tests/test_get_vpc_node.py +++ b/tests/test_get_vpc_node.py @@ -7,38 +7,10 @@ fabricNodePEps = "fabricNodePEp.json" data = [ - { - "fabricNodePEp": { - "attributes": { - "dn": "uni/fabric/protpol/expgep-101-103/nodepep-101", - "id": "101" - } - } - }, - { - "fabricNodePEp": { - "attributes": { - "dn": "uni/fabric/protpol/expgep-204-206/nodepep-206", - "id": "206" - } - } - }, - { - "fabricNodePEp": { - "attributes": { - "dn": "uni/fabric/protpol/expgep-101-103/nodepep-103", - "id": "103" - } - } - }, - { - "fabricNodePEp": { - "attributes": { - "dn": "uni/fabric/protpol/expgep-204-206/nodepep-204", - "id": "204" - } - } - } + {"fabricNodePEp": {"attributes": {"dn": "uni/fabric/protpol/expgep-101-103/nodepep-101", "id": "101"}}}, + {"fabricNodePEp": {"attributes": {"dn": "uni/fabric/protpol/expgep-204-206/nodepep-206", "id": "206"}}}, + {"fabricNodePEp": {"attributes": {"dn": "uni/fabric/protpol/expgep-101-103/nodepep-103", "id": "103"}}}, + {"fabricNodePEp": {"attributes": {"dn": "uni/fabric/protpol/expgep-204-206/nodepep-204", "id": "204"}}}, ] data2 = [ @@ -68,8 +40,15 @@ {fabricNodePEps: data2}, ["101", "102", "103", "104", "105", "106"], "Collecting VPC Node IDs...101, 102, 103, 104, ... (and 2 more)\n\n", - ) - ] + ), + # CSCws30568: expected for fabricNodePEp to return non-zero totalCount + # incorrectly for an empty response. + ( + {fabricNodePEps: {"totalCount": "8", "imdata": []}}, + [], + "Collecting VPC Node IDs...\n\n", + ), + ], ) def test_get_vpc_nodes(capsys, mock_icurl, expected_result, expected_stdout): vpc_nodes = script.get_vpc_nodes() @@ -77,4 +56,4 @@ def test_get_vpc_nodes(capsys, mock_icurl, expected_result, expected_stdout): captured = capsys.readouterr() print(captured.out) - assert captured.out == expected_stdout + assert captured.out == expected_stdout \ No newline at end of file From 09869b7e7be1bda200ab6d9f3cad0fb00cdcf136 Mon Sep 17 00:00:00 2001 From: muthuku Date: Wed, 7 Jan 2026 09:45:23 +0000 Subject: [PATCH 6/7] removed unwanted new lines --- aci-preupgrade-validation-script.py | 79 ++++++++++++++--------------- docs/docs/validations.md | 1 - 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 535b8858..d398694e 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -1813,10 +1813,6 @@ def query_common_data(api_only=False, arg_cversion=None, arg_tversion=None): } - - - - @check_wrapper(check_title="APIC Cluster Status") def apic_cluster_health_check(cversion, **kwargs): result = FAIL_UF @@ -5974,6 +5970,44 @@ def configpush_shard_check(tversion, **kwargs): return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) + +@check_wrapper(check_title='APIC VMM inventory sync fault (F0132)') +def apic_vmm_inventory_sync_faults_check(**kwargs): + result = PASS + headers = ['Fault', 'VMM Domain', 'Controller'] + data = [] + unformatted_headers = ["Fault", "Fault DN"] + unformatted_data = [] + recommended_action = "Please look for Faults under VM and Host and fix them via VCenter, then manually re-trigger inventory sync on APIC" + doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#vmm-inventory-partially-synced' + vmm_regex = r'comp/prov-VMware/ctrlr-\[(?P.+?)\]-(?P.+?)/fault-F0132' + faultInsts = icurl('class', 'faultInst.json?query-target-filter=eq(faultInst.code,"F0132")') + + for faultInst in faultInsts: + fc = faultInst['faultInst']['attributes']['code'] + dn = faultInst['faultInst']['attributes']['dn'] + desc = faultInst['faultInst']['attributes']['descr'] + change_set = faultInst['faultInst']['attributes']['changeSet'] + + dn_array = re.search(vmm_regex, dn) + if dn_array and "partial-inv" in change_set: + data.append([fc, dn_array.group("domain"), dn_array.group("controller")]) + elif "partial-inv" in change_set: + unformatted_data.append([fc, dn]) + + if data or unformatted_data: + result = MANUAL + + return Result( + result=result, + headers=headers, + data=data, + unformatted_headers=unformatted_headers, + unformatted_data=unformatted_data, + recommended_action=recommended_action, + doc_url=doc_url) + + @check_wrapper(check_title='active_node pres.Listener mo object check') def active_node_presListener_mo_object_check(tversion,fabric_nodes, **kwargs): result = PASS @@ -6026,42 +6060,7 @@ def active_node_presListener_mo_object_check(tversion,fabric_nodes, **kwargs): return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) else: return Result(result=NA, msg=VER_NOT_AFFECTED) - -@check_wrapper(check_title='APIC VMM inventory sync fault (F0132)') -def apic_vmm_inventory_sync_faults_check(**kwargs): - result = PASS - headers = ['Fault', 'VMM Domain', 'Controller'] - data = [] - unformatted_headers = ["Fault", "Fault DN"] - unformatted_data = [] - recommended_action = "Please look for Faults under VM and Host and fix them via VCenter, then manually re-trigger inventory sync on APIC" - doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#vmm-inventory-partially-synced' - vmm_regex = r'comp/prov-VMware/ctrlr-\[(?P.+?)\]-(?P.+?)/fault-F0132' - faultInsts = icurl('class', 'faultInst.json?query-target-filter=eq(faultInst.code,"F0132")') - - for faultInst in faultInsts: - fc = faultInst['faultInst']['attributes']['code'] - dn = faultInst['faultInst']['attributes']['dn'] - desc = faultInst['faultInst']['attributes']['descr'] - change_set = faultInst['faultInst']['attributes']['changeSet'] - - dn_array = re.search(vmm_regex, dn) - if dn_array and "partial-inv" in change_set: - data.append([fc, dn_array.group("domain"), dn_array.group("controller")]) - elif "partial-inv" in change_set: - unformatted_data.append([fc, dn]) - - if data or unformatted_data: - result = MANUAL - - return Result( - result=result, - headers=headers, - data=data, - unformatted_headers=unformatted_headers, - unformatted_data=unformatted_data, - recommended_action=recommended_action, - doc_url=doc_url) + # ---- Script Execution ---- diff --git a/docs/docs/validations.md b/docs/docs/validations.md index 2c75e7b8..51bc6ae0 100644 --- a/docs/docs/validations.md +++ b/docs/docs/validations.md @@ -193,7 +193,6 @@ Items | Defect | This Script [Policydist configpushShardCont Crash][d28] | CSCwp95515 | :white_check_mark: | [active_node_presListener_mo_object_check][d29] | CSCwn81692 | :white_check_mark: | :no_entry_sign: - [d1]: #ep-announce-compatibility [d2]: #eventmgr-db-size-defect-susceptibility [d3]: #contract-port-22-defect From 6459c37e2cffb671c6348e38527bf2f49a0ee8f1 Mon Sep 17 00:00:00 2001 From: muthu-ku Date: Wed, 7 Jan 2026 15:38:50 +0530 Subject: [PATCH 7/7] remove white spaces --- tests/test_get_vpc_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_get_vpc_node.py b/tests/test_get_vpc_node.py index 613e4587..0f664a5d 100644 --- a/tests/test_get_vpc_node.py +++ b/tests/test_get_vpc_node.py @@ -56,4 +56,4 @@ def test_get_vpc_nodes(capsys, mock_icurl, expected_result, expected_stdout): captured = capsys.readouterr() print(captured.out) - assert captured.out == expected_stdout \ No newline at end of file + assert captured.out == expected_stdout