From 18431a14fc208af6d04806d2af0e04fc68dd850a Mon Sep 17 00:00:00 2001 From: Priyanka Date: Fri, 20 Mar 2026 16:34:31 +0530 Subject: [PATCH 1/5] Added N9300 switch memory check --- aci-preupgrade-validation-script.py | 96 +++++++++++++++- docs/docs/validations.md | 8 ++ .../fabricNode_non_n9300.json | 13 +++ .../fabricNode_one.json | 13 +++ .../fabricNode_two.json | 24 ++++ .../procMemUsage_all_gt24gb.json | 20 ++++ .../procMemUsage_gt24gb.json | 11 ++ .../procMemUsage_lt24gb.json | 11 ++ .../procMemUsage_mixed.json | 20 ++++ .../procMemUsage_node201_gt24gb.json | 11 ++ .../test_n9300_switch_memory_24g_check.py | 105 ++++++++++++++++++ 11 files changed, 327 insertions(+), 5 deletions(-) create mode 100644 tests/checks/n9300_switch_memory_24g_check/fabricNode_non_n9300.json create mode 100644 tests/checks/n9300_switch_memory_24g_check/fabricNode_one.json create mode 100644 tests/checks/n9300_switch_memory_24g_check/fabricNode_two.json create mode 100644 tests/checks/n9300_switch_memory_24g_check/procMemUsage_all_gt24gb.json create mode 100644 tests/checks/n9300_switch_memory_24g_check/procMemUsage_gt24gb.json create mode 100644 tests/checks/n9300_switch_memory_24g_check/procMemUsage_lt24gb.json create mode 100644 tests/checks/n9300_switch_memory_24g_check/procMemUsage_mixed.json create mode 100644 tests/checks/n9300_switch_memory_24g_check/procMemUsage_node201_gt24gb.json create mode 100644 tests/checks/n9300_switch_memory_24g_check/test_n9300_switch_memory_24g_check.py diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index f29c66b..65ffddd 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -950,9 +950,7 @@ def start(self, timeout=5.0): timeout (float): How long we wait for the thread start event. 5.0 sec by default. """ - _active_limbo_lock = threading._active_limbo_lock - _limbo = threading._limbo - _start_new_thread = threading._start_new_thread + _start_new_thread = getattr(threading, "_start_new_thread", None) # Python2 uses name mangling if hasattr(self, "_Thread__initialized"): @@ -968,6 +966,18 @@ def start(self, timeout=5.0): if self._started.is_set(): raise RuntimeError("threads can only be started once") + # Python 3.14+ no longer exposes threading._start_new_thread. + # In that case, defer to Thread.start() to keep internal join handles valid. + if _start_new_thread is None: + super(CustomThread, self).start() + self._started.wait(timeout) + if not self._started.is_set(): + raise RuntimeError("can't start new thread") + return + + _active_limbo_lock = threading._active_limbo_lock + _limbo = threading._limbo + with _active_limbo_lock: _limbo[self] = self try: @@ -1493,11 +1503,19 @@ def get_row(widths, values, spad=" ", lpad=""): def prints(objects, sep=' ', end='\n'): with open(RESULT_FILE, 'a') as f: - print(objects, sep=sep, end=end, file=sys.stdout) + stdout_ok = True + try: + print(objects, sep=sep, end=end, file=sys.stdout) + except (OSError, ValueError): + stdout_ok = False if end == "\r": end = "\n" # easier to read with \n in a log file print(objects, sep=sep, end=end, file=f) - sys.stdout.flush() + if stdout_ok: + try: + sys.stdout.flush() + except (OSError, ValueError): + pass f.flush() @@ -5877,6 +5895,73 @@ def isis_database_byte_check(tversion, **kwargs): return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) +@check_wrapper(check_title='N9300 Switch Memory') +def n9300_switch_memory_check(tversion, fabric_nodes, **kwargs): + result = PASS + headers = ["NodeId", "Name", "Model", "Memory Detected (GB)"] + data = [] + recommended_action = 'Increase the switch memory to at least 24GB before proceeding with the Cisco ACI software upgrade.' + doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#n9300-switch-memory' + min_memory_kb = 24 * 1024 * 1024 + + if not tversion: + return Result(result=MANUAL, msg=TVER_MISSING) + + if tversion.major_version != '6.1': + return Result(result=NA, msg=VER_NOT_AFFECTED) + + proc_mem_query = 'procMemUsage.json' + affected_nodes = [ + node for node in fabric_nodes + if node.get('fabricNode', {}).get('attributes', {}).get('model', '').startswith('N9K-C93') + ] + + if not affected_nodes: + return Result(result=NA, msg='No N9300 switches found. Skipping.') + + proc_mem_mos = icurl('class', proc_mem_query) + node_total_kb = {} + + for memory_mo in proc_mem_mos: + attrs = memory_mo.get('procMemUsage', {}).get('attributes', {}) + total = attrs.get('Total') + mem_dn = attrs.get('dn', '') + if not total or '/memusage-sup' not in mem_dn: + continue + dn_match = re.search(node_regex, mem_dn) + if not dn_match: + continue + try: + total_kb = int(total) + except ValueError: + continue + + node_id = dn_match.group('node') + if node_id not in node_total_kb: + node_total_kb[node_id] = total_kb + + for node in affected_nodes: + node_id = node['fabricNode']['attributes']['id'] + total_kb = node_total_kb.get(node_id) + if total_kb is None: + continue + + memory_in_gb = round(total_kb / 1048576, 2) + if total_kb < min_memory_kb: + result = FAIL_O + data.append([ + node_id, + node['fabricNode']['attributes'].get('name', ''), + node['fabricNode']['attributes'].get('model', ''), + memory_in_gb, + ]) + + if result == PASS: + return Result(result=result) + + return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) + + # Subprocess check - cat + acidiag @check_wrapper(check_title='APIC Database Size') def apic_database_size_check(cversion, **kwargs): @@ -6214,6 +6299,7 @@ class CheckManager: pbr_high_scale_check, standby_sup_sync_check, isis_database_byte_check, + n9300_switch_memory_check, configpush_shard_check, auto_firmware_update_on_switch_check, diff --git a/docs/docs/validations.md b/docs/docs/validations.md index f46e03d..0926580 100644 --- a/docs/docs/validations.md +++ b/docs/docs/validations.md @@ -194,6 +194,7 @@ Items | Defect | This Script [ISIS DTEPs Byte Size][d27] | CSCwp15375 | :white_check_mark: | :no_entry_sign: [Policydist configpushShardCont Crash][d28] | CSCwp95515 | :white_check_mark: | :no_entry_sign: [Auto Firmware Update on Switch Discovery][d29] | CSCwe83941 | :white_check_mark: | :no_entry_sign: +[N9300 Switch Memory][d30] | - | :white_check_mark: | :no_entry_sign: [d1]: #ep-announce-compatibility [d2]: #eventmgr-db-size-defect-susceptibility @@ -224,6 +225,7 @@ Items | Defect | This Script [d27]: #isis-dteps-byte-size [d28]: #policydist-configpushshardcont-crash [d29]: #auto-firmware-update-on-switch-discovery +[d30]: #n9300-switch-memory ## General Check Details @@ -2648,6 +2650,12 @@ 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. +### N9300 Switch Memory + +This check applies only when the target upgrade version is 6.1. It reviews `procMemUsage` for N9300-series switches and flags nodes with less than 24GB memory installed. + +If any N9300-series switch is flagged by this check, increase the switch memory to at least 24GB before the upgrade. + ### Auto Firmware Update on Switch Discovery [Auto Firmware Update on Switch Discovery][63] automatically upgrades a new switch to the target firmware version before registering it to the ACI fabric. This feature activates in three scenarios: diff --git a/tests/checks/n9300_switch_memory_24g_check/fabricNode_non_n9300.json b/tests/checks/n9300_switch_memory_24g_check/fabricNode_non_n9300.json new file mode 100644 index 0000000..a82341a --- /dev/null +++ b/tests/checks/n9300_switch_memory_24g_check/fabricNode_non_n9300.json @@ -0,0 +1,13 @@ +[ + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-201", + "id": "201", + "name": "leaf201", + "model": "N9K-C9508", + "role": "leaf" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/n9300_switch_memory_24g_check/fabricNode_one.json b/tests/checks/n9300_switch_memory_24g_check/fabricNode_one.json new file mode 100644 index 0000000..74a31ad --- /dev/null +++ b/tests/checks/n9300_switch_memory_24g_check/fabricNode_one.json @@ -0,0 +1,13 @@ +[ + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-101", + "id": "101", + "name": "leaf101", + "model": "N9K-C93180YC-FX3", + "role": "leaf" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/n9300_switch_memory_24g_check/fabricNode_two.json b/tests/checks/n9300_switch_memory_24g_check/fabricNode_two.json new file mode 100644 index 0000000..fad0289 --- /dev/null +++ b/tests/checks/n9300_switch_memory_24g_check/fabricNode_two.json @@ -0,0 +1,24 @@ +[ + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-101", + "id": "101", + "name": "leaf101", + "model": "N9K-C93180YC-FX3", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-102", + "id": "102", + "name": "leaf102", + "model": "N9K-C9364C", + "role": "leaf" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/n9300_switch_memory_24g_check/procMemUsage_all_gt24gb.json b/tests/checks/n9300_switch_memory_24g_check/procMemUsage_all_gt24gb.json new file mode 100644 index 0000000..dab23d2 --- /dev/null +++ b/tests/checks/n9300_switch_memory_24g_check/procMemUsage_all_gt24gb.json @@ -0,0 +1,20 @@ +[ + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-101/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "32535444" + } + } + }, + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-102/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "28535444" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/n9300_switch_memory_24g_check/procMemUsage_gt24gb.json b/tests/checks/n9300_switch_memory_24g_check/procMemUsage_gt24gb.json new file mode 100644 index 0000000..597db59 --- /dev/null +++ b/tests/checks/n9300_switch_memory_24g_check/procMemUsage_gt24gb.json @@ -0,0 +1,11 @@ +[ + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-101/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "26535444" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/n9300_switch_memory_24g_check/procMemUsage_lt24gb.json b/tests/checks/n9300_switch_memory_24g_check/procMemUsage_lt24gb.json new file mode 100644 index 0000000..d84e82c --- /dev/null +++ b/tests/checks/n9300_switch_memory_24g_check/procMemUsage_lt24gb.json @@ -0,0 +1,11 @@ +[ + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-101/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "22535444" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/n9300_switch_memory_24g_check/procMemUsage_mixed.json b/tests/checks/n9300_switch_memory_24g_check/procMemUsage_mixed.json new file mode 100644 index 0000000..58ece8e --- /dev/null +++ b/tests/checks/n9300_switch_memory_24g_check/procMemUsage_mixed.json @@ -0,0 +1,20 @@ +[ + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-101/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "32535444" + } + } + }, + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-102/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "22535444" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/n9300_switch_memory_24g_check/procMemUsage_node201_gt24gb.json b/tests/checks/n9300_switch_memory_24g_check/procMemUsage_node201_gt24gb.json new file mode 100644 index 0000000..bca2b9f --- /dev/null +++ b/tests/checks/n9300_switch_memory_24g_check/procMemUsage_node201_gt24gb.json @@ -0,0 +1,11 @@ +[ + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-201/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "28535444" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/n9300_switch_memory_24g_check/test_n9300_switch_memory_24g_check.py b/tests/checks/n9300_switch_memory_24g_check/test_n9300_switch_memory_24g_check.py new file mode 100644 index 0000000..8ae92ad --- /dev/null +++ b/tests/checks/n9300_switch_memory_24g_check/test_n9300_switch_memory_24g_check.py @@ -0,0 +1,105 @@ +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 = "n9300_switch_memory_check" + +# icurl queries +proc_mem_query = 'procMemUsage.json' + + +@pytest.mark.parametrize( + "fabric_nodes, icurl_outputs, tversion, expected_result, expected_msg, expected_data", + [ + # Target version missing + ( + read_data(dir, "fabricNode_one.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_gt24gb.json"), + }, + None, + script.MANUAL, + script.TVER_MISSING, + [], + ), + # Target version not affected + ( + read_data(dir, "fabricNode_one.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_gt24gb.json"), + }, + "6.0(3c)", + script.NA, + script.VER_NOT_AFFECTED, + [], + ), + # No nodes returned + ( + [], + {}, + "6.1(2f)", + script.NA, + 'No N9300 switches found. Skipping.', + [], + ), + # Non-N9300 node with >=24GB memory + ( + read_data(dir, "fabricNode_non_n9300.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_node201_gt24gb.json"), + }, + "6.1(2f)", + script.NA, + 'No N9300 switches found. Skipping.', + [], + ), + # N9300 node with >=24GB memory + ( + read_data(dir, "fabricNode_one.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_gt24gb.json"), + }, + "6.1(2f)", + script.PASS, + '', + [], + ), + # Multiple N9300 nodes, all >=24GB memory + ( + read_data(dir, "fabricNode_two.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_all_gt24gb.json"), + }, + "6.1(2f)", + script.PASS, + '', + [], + ), + # N9300 node with <24GB memory + ( + read_data(dir, "fabricNode_two.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_mixed.json"), + }, + "6.1(2f)", + script.FAIL_O, + '', + [["102", "leaf102", "N9K-C9364C", 21.49]], + ), + ], +) +def test_logic(run_check, mock_icurl, fabric_nodes, tversion, expected_result, expected_msg, expected_data): + result = run_check( + tversion=script.AciVersion(tversion) if tversion else None, + fabric_nodes=fabric_nodes, + ) + assert result.result == expected_result + assert result.msg == expected_msg + assert result.data == expected_data \ No newline at end of file From a0e132acf5e88ca5142790afefdcfe32a488145f Mon Sep 17 00:00:00 2001 From: Priyanka Date: Fri, 20 Mar 2026 17:29:45 +0530 Subject: [PATCH 2/5] Remove unrelated thread/prints changes from N9300 check PR --- aci-preupgrade-validation-script.py | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 65ffddd..b19128c 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -950,7 +950,9 @@ def start(self, timeout=5.0): timeout (float): How long we wait for the thread start event. 5.0 sec by default. """ - _start_new_thread = getattr(threading, "_start_new_thread", None) + _active_limbo_lock = threading._active_limbo_lock + _limbo = threading._limbo + _start_new_thread = threading._start_new_thread # Python2 uses name mangling if hasattr(self, "_Thread__initialized"): @@ -966,18 +968,6 @@ def start(self, timeout=5.0): if self._started.is_set(): raise RuntimeError("threads can only be started once") - # Python 3.14+ no longer exposes threading._start_new_thread. - # In that case, defer to Thread.start() to keep internal join handles valid. - if _start_new_thread is None: - super(CustomThread, self).start() - self._started.wait(timeout) - if not self._started.is_set(): - raise RuntimeError("can't start new thread") - return - - _active_limbo_lock = threading._active_limbo_lock - _limbo = threading._limbo - with _active_limbo_lock: _limbo[self] = self try: @@ -1503,19 +1493,11 @@ def get_row(widths, values, spad=" ", lpad=""): def prints(objects, sep=' ', end='\n'): with open(RESULT_FILE, 'a') as f: - stdout_ok = True - try: - print(objects, sep=sep, end=end, file=sys.stdout) - except (OSError, ValueError): - stdout_ok = False + print(objects, sep=sep, end=end, file=sys.stdout) if end == "\r": end = "\n" # easier to read with \n in a log file print(objects, sep=sep, end=end, file=f) - if stdout_ok: - try: - sys.stdout.flush() - except (OSError, ValueError): - pass + sys.stdout.flush() f.flush() From 827d36e1c8ec09de089daf428dbff9178c6fcf6c Mon Sep 17 00:00:00 2001 From: Priyanka Date: Mon, 23 Mar 2026 18:02:32 +0530 Subject: [PATCH 3/5] Addressed PR comments --- aci-preupgrade-validation-script.py | 8 +---- docs/docs/validations.md | 14 ++++---- .../test_n9300_switch_memory_24g_check.py | 34 ++++--------------- 3 files changed, 15 insertions(+), 41 deletions(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index b19128c..95940cc 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -5882,16 +5882,10 @@ def n9300_switch_memory_check(tversion, fabric_nodes, **kwargs): result = PASS headers = ["NodeId", "Name", "Model", "Memory Detected (GB)"] data = [] - recommended_action = 'Increase the switch memory to at least 24GB before proceeding with the Cisco ACI software upgrade.' + recommended_action = 'Increase the switch memory to at least 24GB on affected N9300-series switches.' doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#n9300-switch-memory' min_memory_kb = 24 * 1024 * 1024 - if not tversion: - return Result(result=MANUAL, msg=TVER_MISSING) - - if tversion.major_version != '6.1': - return Result(result=NA, msg=VER_NOT_AFFECTED) - proc_mem_query = 'procMemUsage.json' affected_nodes = [ node for node in fabric_nodes diff --git a/docs/docs/validations.md b/docs/docs/validations.md index 0926580..537a289 100644 --- a/docs/docs/validations.md +++ b/docs/docs/validations.md @@ -2650,12 +2650,6 @@ 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. -### N9300 Switch Memory - -This check applies only when the target upgrade version is 6.1. It reviews `procMemUsage` for N9300-series switches and flags nodes with less than 24GB memory installed. - -If any N9300-series switch is flagged by this check, increase the switch memory to at least 24GB before the upgrade. - ### Auto Firmware Update on Switch Discovery [Auto Firmware Update on Switch Discovery][63] automatically upgrades a new switch to the target firmware version before registering it to the ACI fabric. This feature activates in three scenarios: @@ -2675,6 +2669,14 @@ To avoid this risk, consider disabling Auto Firmware Update before upgrading to !!! note This issue occurs because older switch firmware versions are not compatible with switch images 6.0(3) or newer. The APIC version is not a factor. +### N9300 Switch Memory + +This check applies to N9300-series switches only. It reviews `procMemUsage` and flags nodes with less than 24GB memory installed. + +Impact: Running an N9300-series switch with less than 24GB memory can lead to memory pressure and increase the risk of service instability. + +If any N9300-series switch is flagged by this check, increase the switch memory to at least 24GB. + [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 diff --git a/tests/checks/n9300_switch_memory_24g_check/test_n9300_switch_memory_24g_check.py b/tests/checks/n9300_switch_memory_24g_check/test_n9300_switch_memory_24g_check.py index 8ae92ad..72b76de 100644 --- a/tests/checks/n9300_switch_memory_24g_check/test_n9300_switch_memory_24g_check.py +++ b/tests/checks/n9300_switch_memory_24g_check/test_n9300_switch_memory_24g_check.py @@ -18,33 +18,11 @@ @pytest.mark.parametrize( "fabric_nodes, icurl_outputs, tversion, expected_result, expected_msg, expected_data", [ - # Target version missing - ( - read_data(dir, "fabricNode_one.json"), - { - proc_mem_query: read_data(dir, "procMemUsage_gt24gb.json"), - }, - None, - script.MANUAL, - script.TVER_MISSING, - [], - ), - # Target version not affected - ( - read_data(dir, "fabricNode_one.json"), - { - proc_mem_query: read_data(dir, "procMemUsage_gt24gb.json"), - }, - "6.0(3c)", - script.NA, - script.VER_NOT_AFFECTED, - [], - ), # No nodes returned ( [], {}, - "6.1(2f)", + "6.0(3c)", script.NA, 'No N9300 switches found. Skipping.', [], @@ -55,7 +33,7 @@ { proc_mem_query: read_data(dir, "procMemUsage_node201_gt24gb.json"), }, - "6.1(2f)", + "6.0(3c)", script.NA, 'No N9300 switches found. Skipping.', [], @@ -66,7 +44,7 @@ { proc_mem_query: read_data(dir, "procMemUsage_gt24gb.json"), }, - "6.1(2f)", + "6.0(3c)", script.PASS, '', [], @@ -77,7 +55,7 @@ { proc_mem_query: read_data(dir, "procMemUsage_all_gt24gb.json"), }, - "6.1(2f)", + "6.0(3c)", script.PASS, '', [], @@ -88,7 +66,7 @@ { proc_mem_query: read_data(dir, "procMemUsage_mixed.json"), }, - "6.1(2f)", + "6.0(3c)", script.FAIL_O, '', [["102", "leaf102", "N9K-C9364C", 21.49]], @@ -97,7 +75,7 @@ ) def test_logic(run_check, mock_icurl, fabric_nodes, tversion, expected_result, expected_msg, expected_data): result = run_check( - tversion=script.AciVersion(tversion) if tversion else None, + tversion=script.AciVersion(tversion), fabric_nodes=fabric_nodes, ) assert result.result == expected_result From 648e8c3fd7a5e733a7aca9ae809af7ca329c8929 Mon Sep 17 00:00:00 2001 From: Priyanka Date: Tue, 24 Mar 2026 15:30:49 +0530 Subject: [PATCH 4/5] Addressed and updated n9300_switch_memory_check files --- aci-preupgrade-validation-script.py | 148 ++++++++++-------- .../procMemUsage_invalid_total.json | 11 ++ .../procMemUsage_missing_affected_node.json | 11 ++ .../test_n9300_switch_memory_24g_check.py | 22 +++ 4 files changed, 130 insertions(+), 62 deletions(-) create mode 100644 tests/checks/n9300_switch_memory_24g_check/procMemUsage_invalid_total.json create mode 100644 tests/checks/n9300_switch_memory_24g_check/procMemUsage_missing_affected_node.json diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 95940cc..256a10e 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -5877,67 +5877,6 @@ def isis_database_byte_check(tversion, **kwargs): return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) -@check_wrapper(check_title='N9300 Switch Memory') -def n9300_switch_memory_check(tversion, fabric_nodes, **kwargs): - result = PASS - headers = ["NodeId", "Name", "Model", "Memory Detected (GB)"] - data = [] - recommended_action = 'Increase the switch memory to at least 24GB on affected N9300-series switches.' - doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#n9300-switch-memory' - min_memory_kb = 24 * 1024 * 1024 - - proc_mem_query = 'procMemUsage.json' - affected_nodes = [ - node for node in fabric_nodes - if node.get('fabricNode', {}).get('attributes', {}).get('model', '').startswith('N9K-C93') - ] - - if not affected_nodes: - return Result(result=NA, msg='No N9300 switches found. Skipping.') - - proc_mem_mos = icurl('class', proc_mem_query) - node_total_kb = {} - - for memory_mo in proc_mem_mos: - attrs = memory_mo.get('procMemUsage', {}).get('attributes', {}) - total = attrs.get('Total') - mem_dn = attrs.get('dn', '') - if not total or '/memusage-sup' not in mem_dn: - continue - dn_match = re.search(node_regex, mem_dn) - if not dn_match: - continue - try: - total_kb = int(total) - except ValueError: - continue - - node_id = dn_match.group('node') - if node_id not in node_total_kb: - node_total_kb[node_id] = total_kb - - for node in affected_nodes: - node_id = node['fabricNode']['attributes']['id'] - total_kb = node_total_kb.get(node_id) - if total_kb is None: - continue - - memory_in_gb = round(total_kb / 1048576, 2) - if total_kb < min_memory_kb: - result = FAIL_O - data.append([ - node_id, - node['fabricNode']['attributes'].get('name', ''), - node['fabricNode']['attributes'].get('model', ''), - memory_in_gb, - ]) - - if result == PASS: - return Result(result=result) - - return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) - - # Subprocess check - cat + acidiag @check_wrapper(check_title='APIC Database Size') def apic_database_size_check(cversion, **kwargs): @@ -6114,6 +6053,91 @@ def auto_firmware_update_on_switch_check(cversion, tversion, **kwargs): return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) + +@check_wrapper(check_title='N9300 Switch Memory') +def n9300_switch_memory_check(tversion, fabric_nodes, **kwargs): + result = PASS + headers = ["NodeId", "Name", "Model", "Memory Detected (GB)"] + data = [] + recommended_action = 'Increase the switch memory to at least 24GB on affected N9300-series switches.' + doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#n9300-switch-memory' + min_memory_kb = 24 * 1024 * 1024 + + affected_nodes = [ + node for node in fabric_nodes + if node.get('fabricNode', {}).get('attributes', {}).get('model', '').startswith('N9K-C93') + ] + + if not affected_nodes: + return Result(result=NA, msg='No N9300 switches found. Skipping.') + + proc_mem_mos = icurl('class', 'procMemUsage.json') + node_total_kb = {} + parse_errors = [] + + for memory_mo in proc_mem_mos: + attrs = memory_mo.get('procMemUsage', {}).get('attributes', {}) + total = attrs.get('Total') + mem_dn = attrs.get('dn', '') + if not total or '/memusage-sup' not in mem_dn: + continue + dn_match = re.search(node_regex, mem_dn) + if not dn_match: + continue + try: + total_kb = int(total) + except (TypeError, ValueError): + parse_errors.append([mem_dn, total]) + continue + + node_id = dn_match.group('node') + if node_id not in node_total_kb: + node_total_kb[node_id] = total_kb + + if parse_errors: + return Result( + result=ERROR, + msg='Failed to parse procMemUsage Total for one or more nodes.', + headers=['DN', 'Total'], + data=parse_errors, + ) + + missing_nodes = [] + + for node in affected_nodes: + node_id = node['fabricNode']['attributes']['id'] + total_kb = node_total_kb.get(node_id) + if total_kb is None: + missing_nodes.append([ + node_id, + node['fabricNode']['attributes'].get('name', ''), + node['fabricNode']['attributes'].get('model', ''), + ]) + continue + + memory_in_gb = round(total_kb / 1048576, 2) + if total_kb < min_memory_kb: + result = FAIL_O + data.append([ + node_id, + node['fabricNode']['attributes'].get('name', ''), + node['fabricNode']['attributes'].get('model', ''), + memory_in_gb, + ]) + + if missing_nodes: + return Result( + result=ERROR, + msg='Missing procMemUsage data for one or more affected N9300 nodes.', + headers=['NodeId', 'Name', 'Model'], + data=missing_nodes, + ) + + if result == FAIL_O: + return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) + + return Result(result=result) + # ---- Script Execution ---- @@ -6275,9 +6299,9 @@ class CheckManager: pbr_high_scale_check, standby_sup_sync_check, isis_database_byte_check, - n9300_switch_memory_check, configpush_shard_check, auto_firmware_update_on_switch_check, + n9300_switch_memory_check, ] ssh_checks = [ diff --git a/tests/checks/n9300_switch_memory_24g_check/procMemUsage_invalid_total.json b/tests/checks/n9300_switch_memory_24g_check/procMemUsage_invalid_total.json new file mode 100644 index 0000000..dc1a4ee --- /dev/null +++ b/tests/checks/n9300_switch_memory_24g_check/procMemUsage_invalid_total.json @@ -0,0 +1,11 @@ +[ + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-101/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "unknown" + } + } + } +] diff --git a/tests/checks/n9300_switch_memory_24g_check/procMemUsage_missing_affected_node.json b/tests/checks/n9300_switch_memory_24g_check/procMemUsage_missing_affected_node.json new file mode 100644 index 0000000..e11293a --- /dev/null +++ b/tests/checks/n9300_switch_memory_24g_check/procMemUsage_missing_affected_node.json @@ -0,0 +1,11 @@ +[ + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-201/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "26535444" + } + } + } +] diff --git a/tests/checks/n9300_switch_memory_24g_check/test_n9300_switch_memory_24g_check.py b/tests/checks/n9300_switch_memory_24g_check/test_n9300_switch_memory_24g_check.py index 72b76de..0f6aa00 100644 --- a/tests/checks/n9300_switch_memory_24g_check/test_n9300_switch_memory_24g_check.py +++ b/tests/checks/n9300_switch_memory_24g_check/test_n9300_switch_memory_24g_check.py @@ -60,6 +60,28 @@ '', [], ), + # Invalid procMemUsage Total value + ( + read_data(dir, "fabricNode_one.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_invalid_total.json"), + }, + "6.0(3c)", + script.ERROR, + 'Failed to parse procMemUsage Total for one or more nodes.', + [["topology/pod-1/node-101/sys/procmem/memusage-sup", "unknown"]], + ), + # Missing procMemUsage data for affected node + ( + read_data(dir, "fabricNode_one.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_missing_affected_node.json"), + }, + "6.0(3c)", + script.ERROR, + 'Missing procMemUsage data for one or more affected N9300 nodes.', + [["101", "leaf101", "N9K-C93180YC-FX3"]], + ), # N9300 node with <24GB memory ( read_data(dir, "fabricNode_two.json"), From a38bdf8d71fb4ae1f5b7028f1a833b8c0d744243 Mon Sep 17 00:00:00 2001 From: Priyanka Date: Wed, 25 Mar 2026 09:20:05 +0530 Subject: [PATCH 5/5] Updated aci-preupgrade-validation-script.py script --- aci-preupgrade-validation-script.py | 121 +++++++++++++--------------- 1 file changed, 58 insertions(+), 63 deletions(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 256a10e..9ee408a 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -6062,6 +6062,7 @@ def n9300_switch_memory_check(tversion, fabric_nodes, **kwargs): recommended_action = 'Increase the switch memory to at least 24GB on affected N9300-series switches.' doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#n9300-switch-memory' min_memory_kb = 24 * 1024 * 1024 + msg = '' affected_nodes = [ node for node in fabric_nodes @@ -6069,74 +6070,68 @@ def n9300_switch_memory_check(tversion, fabric_nodes, **kwargs): ] if not affected_nodes: - return Result(result=NA, msg='No N9300 switches found. Skipping.') - - proc_mem_mos = icurl('class', 'procMemUsage.json') - node_total_kb = {} - parse_errors = [] - - for memory_mo in proc_mem_mos: - attrs = memory_mo.get('procMemUsage', {}).get('attributes', {}) - total = attrs.get('Total') - mem_dn = attrs.get('dn', '') - if not total or '/memusage-sup' not in mem_dn: - continue - dn_match = re.search(node_regex, mem_dn) - if not dn_match: - continue - try: - total_kb = int(total) - except (TypeError, ValueError): - parse_errors.append([mem_dn, total]) - continue - - node_id = dn_match.group('node') - if node_id not in node_total_kb: - node_total_kb[node_id] = total_kb - - if parse_errors: - return Result( - result=ERROR, - msg='Failed to parse procMemUsage Total for one or more nodes.', - headers=['DN', 'Total'], - data=parse_errors, - ) - - missing_nodes = [] + result = NA + msg = 'No N9300 switches found. Skipping.' + else: + proc_mem_mos = icurl('class', 'procMemUsage.json') + node_total_kb = {} + parse_errors = [] + + for memory_mo in proc_mem_mos: + attrs = memory_mo.get('procMemUsage', {}).get('attributes', {}) + total = attrs.get('Total') + mem_dn = attrs.get('dn', '') + if not total or '/memusage-sup' not in mem_dn: + continue + dn_match = re.search(node_regex, mem_dn) + if not dn_match: + continue + try: + total_kb = int(total) + except (TypeError, ValueError): + parse_errors.append([mem_dn, total]) + continue - for node in affected_nodes: - node_id = node['fabricNode']['attributes']['id'] - total_kb = node_total_kb.get(node_id) - if total_kb is None: - missing_nodes.append([ - node_id, - node['fabricNode']['attributes'].get('name', ''), - node['fabricNode']['attributes'].get('model', ''), - ]) - continue + node_id = dn_match.group('node') + if node_id not in node_total_kb: + node_total_kb[node_id] = total_kb - memory_in_gb = round(total_kb / 1048576, 2) - if total_kb < min_memory_kb: - result = FAIL_O - data.append([ - node_id, - node['fabricNode']['attributes'].get('name', ''), - node['fabricNode']['attributes'].get('model', ''), - memory_in_gb, - ]) + if parse_errors: + result = ERROR + msg = 'Failed to parse procMemUsage Total for one or more nodes.' + headers = ['DN', 'Total'] + data = parse_errors + else: + missing_nodes = [] + + for node in affected_nodes: + node_id = node['fabricNode']['attributes']['id'] + total_kb = node_total_kb.get(node_id) + if total_kb is None: + missing_nodes.append([ + node_id, + node['fabricNode']['attributes'].get('name', ''), + node['fabricNode']['attributes'].get('model', ''), + ]) + continue - if missing_nodes: - return Result( - result=ERROR, - msg='Missing procMemUsage data for one or more affected N9300 nodes.', - headers=['NodeId', 'Name', 'Model'], - data=missing_nodes, - ) + if total_kb < min_memory_kb: + memory_in_gb = round(total_kb / 1048576, 2) + result = FAIL_O + data.append([ + node_id, + node['fabricNode']['attributes'].get('name', ''), + node['fabricNode']['attributes'].get('model', ''), + memory_in_gb, + ]) - if result == FAIL_O: - return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) + if missing_nodes: + result = ERROR + msg = 'Missing procMemUsage data for one or more affected N9300 nodes.' + headers = ['NodeId', 'Name', 'Model'] + data = missing_nodes - return Result(result=result) + return Result(result=result, msg=msg, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) # ---- Script Execution ----