diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 4b83f4c..58f488e 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -6025,6 +6025,48 @@ def apic_downgrade_compat_warning_check(cversion, tversion, **kwargs): return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) +@check_wrapper(check_title="presListener MO Status") +def pres_listener_mo_check(fabric_nodes, tversion, **kwargs): + result = PASS + msg = '' + headers = ['Pod-ID', 'Node-ID', 'State'] + data = [] + recommended_action = 'Contact TAC to apply the workaround for the listed Nodes BEFORE Upgrade.' + doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations#missing-preslistener-mo' + # fabricNode.fabricSt shows `disabled` for both Decommissioned and Maintenance (GIR). + # fabricRsDecommissionNode.debug==yes is required to show `disabled (Maintenance)`. + presListener = icurl('class','presListener.json') + if not tversion: + return Result(result=MANUAL, msg=TVER_MISSING) + + if tversion.newer_than("6.1(3f)"): + result = NA + return Result(result=result, msg=msg, headers=headers, data=data, recommended_action=recommended_action) + + for fabric_node in fabric_nodes: + found = False + if fabric_node['fabricNode']['attributes']['role'] != "leaf": + continue + state = fabric_node['fabricNode']['attributes']['fabricSt'] + if state != 'active': + continue + dn = re.search(node_regex, fabric_node['fabricNode']['attributes']['dn']) + pod_id = dn.group("pod") + node_id = dn.group("node") + for mo in presListener: + if ( + node_id in mo['presListener']['attributes']['lstDn'] and + 'class-4307' in mo['presListener']['attributes']['dn'] + ): + found = True + continue + if not found: + result = FAIL_O + data.append([pod_id, node_id, state]) + if not fabric_nodes: + result = MANUAL + msg = 'Active Switch Leaf fabricNode not found!' + return Result(result=result, msg=msg, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) # ---- Script Execution ---- @@ -6188,6 +6230,7 @@ class CheckManager: standby_sup_sync_check, isis_database_byte_check, configpush_shard_check, + pres_listener_mo_check, ] ssh_checks = [ diff --git a/docs/docs/validations.md b/docs/docs/validations.md index 68ca1c0..a5afb56 100644 --- a/docs/docs/validations.md +++ b/docs/docs/validations.md @@ -193,6 +193,7 @@ 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: | +[Missing presListener MO][d29] | CSCwn81692 | :white_check_mark: | [d1]: #ep-announce-compatibility [d2]: #eventmgr-db-size-defect-susceptibility @@ -222,6 +223,7 @@ Items | Defect | This Script [d26]: #stale-pconsra-object [d27]: #isis-dteps-byte-size [d28]: #policydist-configpushshardcont-crash +[d29]: #missing-preslistener-mo ## General Check Details @@ -2648,6 +2650,15 @@ 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. +### Missing presListener MO + +In ACI there is a special class called presListener that handles policy download/deployment upon a Switch clean reload or upgrade. + +Due to [CSCwn81692][62], after upgrade (or clean-reload) if the presListener MO is missing for a leaf , the infraAccPortP and infraAccBndlGrp MOs will fail to program in the interfaces, this puts the ports in "out-of-service" status. + +If there any missing presListener, contact Cisco TAC to provide a workaround before an upgrade. + + [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 @@ -2710,3 +2721,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 \ No newline at end of file diff --git a/tests/checks/pres_listener_mo_check/fabricNode.json b/tests/checks/pres_listener_mo_check/fabricNode.json new file mode 100644 index 0000000..4e13469 --- /dev/null +++ b/tests/checks/pres_listener_mo_check/fabricNode.json @@ -0,0 +1,95 @@ +[ + { + "fabricNode": { + "attributes": { + "adSt": "off", + "address": "10.2.176.64", + "annotation": "", + "apicType": "apic", + "childAction": "", + "delayedHeartbeat": "no", + "dn": "topology/pod-1/node-101", + "extMngdBy": "", + "fabricSt": "active", + "id": "101", + "lastStateModTs": "2026-01-28T22:24:54.843+00:00", + "lcOwn": "local", + "modTs": "2026-01-28T22:25:42.207+00:00", + "model": "N9K-C93180YC-EX", + "monPolDn": "uni/fabric/monfab-default", + "name": "SITE2-L101", + "nameAlias": "", + "nodeType": "unspecified", + "role": "leaf", + "serial": "FDO204520TY", + "status": "", + "uid": "0", + "userdom": "all", + "vendor": "Cisco Systems, Inc", + "version": "n9000-15.2(6e)" + } + } + }, + { + "fabricNode": { + "attributes": { + "adSt": "off", + "address": "10.2.176.66", + "annotation": "", + "apicType": "apic", + "childAction": "", + "delayedHeartbeat": "no", + "dn": "topology/pod-1/node-102", + "extMngdBy": "", + "fabricSt": "active", + "id": "102", + "lastStateModTs": "2026-01-28T22:25:45.863+00:00", + "lcOwn": "local", + "modTs": "2026-01-28T22:26:42.234+00:00", + "model": "N9K-C93180YC-EX", + "monPolDn": "uni/fabric/monfab-default", + "name": "SITE2-L102", + "nameAlias": "", + "nodeType": "unspecified", + "role": "leaf", + "serial": "FDO204520SZ", + "status": "", + "uid": "0", + "userdom": "all", + "vendor": "Cisco Systems, Inc", + "version": "n9000-15.2(6e)" + } + } + }, + { + "fabricNode": { + "attributes": { + "adSt": "off", + "address": "10.2.176.67", + "annotation": "", + "apicType": "apic", + "childAction": "", + "delayedHeartbeat": "no", + "dn": "topology/pod-1/node-103", + "extMngdBy": "", + "fabricSt": "active", + "id": "103", + "lastStateModTs": "2026-01-28T22:25:45.863+00:00", + "lcOwn": "local", + "modTs": "2026-01-28T22:26:42.231+00:00", + "model": "N9K-C93240YC-FX2", + "monPolDn": "uni/fabric/monfab-default", + "name": "SITE2-L103", + "nameAlias": "", + "nodeType": "unspecified", + "role": "leaf", + "serial": "FDO25210JA1", + "status": "", + "uid": "0", + "userdom": "all", + "vendor": "Cisco Systems, Inc", + "version": "n9000-15.3(2f)" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/pres_listener_mo_check/presListener-Neg.json b/tests/checks/pres_listener_mo_check/presListener-Neg.json new file mode 100644 index 0000000..8107ab1 --- /dev/null +++ b/tests/checks/pres_listener_mo_check/presListener-Neg.json @@ -0,0 +1,38 @@ +[ + { + "presListener": { + "attributes": { + "childAction": "", + "dn": "resregistry/resregistry-20/relnholder/rspresClass-[resregistry/resregistry-20/class-4307]/list-[uni/infra/nodecfgcont]", + "lcOwn": "local", + "lstDn": "uni/infra/nodecfgcont", + "modTs": "2026-01-21T01:44:22.201+00:00", + "status": "" + } + } + }, + { + "presListener": { + "attributes": { + "childAction": "", + "dn": "resregistry/resregistry-20/relnholder/rspresClass-[resregistry/resregistry-20/class-4307]/list-[uni/infra/nodecfgcont/node-102]", + "lcOwn": "local", + "lstDn": "uni/infra/nodecfgcont/node-102", + "modTs": "2026-01-21T16:02:15.685+00:00", + "status": "" + } + } + }, + { + "presListener": { + "attributes": { + "childAction": "", + "dn": "resregistry/resregistry-20/relnholder/rspresClass-[resregistry/resregistry-20/class-4307]/list-[uni/infra/nodecfgcont/node-101]", + "lcOwn": "local", + "lstDn": "uni/infra/nodecfgcont/node-101", + "modTs": "2026-01-21T16:02:15.685+00:00", + "status": "" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/pres_listener_mo_check/presListener-Pos.json b/tests/checks/pres_listener_mo_check/presListener-Pos.json new file mode 100644 index 0000000..1669b2d --- /dev/null +++ b/tests/checks/pres_listener_mo_check/presListener-Pos.json @@ -0,0 +1,50 @@ +[ + { + "presListener": { + "attributes": { + "childAction": "", + "dn": "resregistry/resregistry-20/relnholder/rspresClass-[resregistry/resregistry-20/class-4307]/list-[uni/infra/nodecfgcont]", + "lcOwn": "local", + "lstDn": "uni/infra/nodecfgcont", + "modTs": "2026-01-21T01:44:22.201+00:00", + "status": "" + } + } + }, + { + "presListener": { + "attributes": { + "childAction": "", + "dn": "resregistry/resregistry-20/relnholder/rspresClass-[resregistry/resregistry-20/class-4307]/list-[uni/infra/nodecfgcont/node-102]", + "lcOwn": "local", + "lstDn": "uni/infra/nodecfgcont/node-102", + "modTs": "2026-01-21T16:02:15.685+00:00", + "status": "" + } + } + }, + { + "presListener": { + "attributes": { + "childAction": "", + "dn": "resregistry/resregistry-20/relnholder/rspresClass-[resregistry/resregistry-20/class-4307]/list-[uni/infra/nodecfgcont/node-101]", + "lcOwn": "local", + "lstDn": "uni/infra/nodecfgcont/node-101", + "modTs": "2026-01-21T16:02:15.685+00:00", + "status": "" + } + } + }, + { + "presListener": { + "attributes": { + "childAction": "", + "dn": "resregistry/resregistry-20/relnholder/rspresClass-[resregistry/resregistry-20/class-4307]/list-[uni/infra/nodecfgcont/node-103]", + "lcOwn": "local", + "lstDn": "uni/infra/nodecfgcont/node-103", + "modTs": "2026-01-21T18:39:38.321+00:00", + "status": "" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/pres_listener_mo_check/test_pres_listener_mo_check.py b/tests/checks/pres_listener_mo_check/test_pres_listener_mo_check.py new file mode 100644 index 0000000..896e845 --- /dev/null +++ b/tests/checks/pres_listener_mo_check/test_pres_listener_mo_check.py @@ -0,0 +1,67 @@ +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 = "pres_listener_mo_check" + +# icurl queries +# fabricNode = "fabricNode.json" +# fabricNode += '?query-target-filter=and(eq(fabricNode.role,"leaf"),eq(fabricNode.fabricSt,"active"))' + +presListener = "presListener.json" + +@pytest.mark.parametrize( + "icurl_outputs, fabric_nodes, tversion, expected_result", + [ + # No tVersion, MANUAL + ( + {presListener: read_data(dir, "presListener-Pos.json")}, + [], + None, + script.MANUAL + ), + # FabricNodes missing, MANUAL + ( + {presListener: read_data(dir, "presListener-Pos.json")}, + [], + "6.1(3a)", + script.MANUAL + ), + # tVersion newer than 6.1-3f , NA + ( + {presListener: read_data(dir, "presListener-Pos.json")}, + [], + "6.1(4a)", + script.NA + ), + # PASS TESTS + # Version can be recovered with testapi (>=6.1-3f) + ( + {presListener: read_data(dir, "presListener-Pos.json")}, + read_data(dir, "fabricNode.json"), + "6.1(3a)", + script.PASS, + ), + # FAIL_O TESTS + # Version can be recovered with testapi (>=6.1-3f) + ( + {presListener: read_data(dir, "presListener-Neg.json")}, + read_data(dir, "fabricNode.json"), + "6.1(3a)", + script.FAIL_O, + ), + ], +) +def test_logic(run_check, mock_icurl, fabric_nodes, tversion, expected_result): + result = run_check( + fabric_nodes=fabric_nodes, + tversion=script.AciVersion(tversion) if tversion else None, + ) + assert result.result == expected_result