diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index fd6f076..5c7f215 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -6293,6 +6293,106 @@ def multipod_modular_spine_bootscript_check(tversion, fabric_nodes, username, pa 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(fabric_nodes, **kwargs): + result = PASS + headers = ["NodeId", "Name", "Model", "Memory Detected (GB)"] + data = [] + recommended_action = 'Increase the switch memory to at least 32GB on affected N9K-C93180YC-FX3 switches.' + doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#n9300-switch-memory' + min_memory_kb = 32 * 1000 * 1000 + msg = '' + + affected_nodes = [ + node for node in fabric_nodes + if node.get('fabricNode', {}).get('attributes', {}).get('model', '') == 'N9K-C93180YC-FX3' + ] + + if not affected_nodes: + result = NA + msg = 'No N9K-C93180YC-FX3 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 + + node_id = dn_match.group('node') + if node_id not in node_total_kb: + node_total_kb[node_id] = total_kb + + if parse_errors: + result = ERROR + msg = 'Failed to parse procMemUsage Total for one or more nodes.' + headers = ['DN', 'Total'] + data = parse_errors + recommended_action = '' + 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 total_kb < min_memory_kb: + memory_in_gb = round(total_kb / 1000000, 2) + result = MANUAL + data.append([ + node_id, + node['fabricNode']['attributes'].get('name', ''), + node['fabricNode']['attributes'].get('model', ''), + memory_in_gb, + ]) + + if missing_nodes and data: + result = MANUAL + msg = ( + 'Some N9K-C93180YC-FX3 nodes have insufficient memory and others are missing ' + 'procMemUsage data. Please manually verify the memory on all affected nodes.\n' + 'Nodes with insufficient memory: {}\n' + 'Nodes with missing data: {}'.format( + ', '.join(str(row[0]) for row in data), + ', '.join(str(row[0]) for row in missing_nodes), + ) + ) + headers = ['NodeId', 'Name', 'Model', 'Memory Detected (GB)'] + data = data + [row + ['N/A'] for row in missing_nodes] + elif missing_nodes: + result = ERROR + msg = 'Missing procMemUsage data for one or more affected N9K-C93180YC-FX3 nodes.' + headers = ['NodeId', 'Name', 'Model'] + data = missing_nodes + recommended_action = '' + elif data: + msg = ( + 'One or more N9K-C93180YC-FX3 switches have less than 32GB of memory. ' + 'An outage is not guaranteed but can occur. Please verify and upgrade the memory on affected nodes.' + ) + + return Result(result=result, msg=msg, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) + + # ---- Script Execution ---- @@ -6384,6 +6484,7 @@ class CheckManager: validate_32_64_bit_image_check, fabric_link_redundancy_check, apic_downgrade_compat_warning_check, + n9300_switch_memory_check, # Faults apic_disk_space_faults_check, diff --git a/docs/docs/validations.md b/docs/docs/validations.md index 49fe535..f61ad03 100644 --- a/docs/docs/validations.md +++ b/docs/docs/validations.md @@ -37,6 +37,7 @@ Items | This Script [Fabric Link Redundancy][g17] | :white_check_mark: | :no_entry_sign: [APIC Database Size][g18] | :white_check_mark: | :no_entry_sign: [APIC downgrade compatibility when crossing 6.2 release][g19]| :white_check_mark: | :no_entry_sign: +[N9300 Switch Memory][g20] | :white_check_mark: | :no_entry_sign: [g1]: #compatibility-target-aci-version [g2]: #compatibility-cimc-version @@ -57,6 +58,7 @@ Items | This Script [g17]: #fabric-link-redundancy [g18]: #apic-database-size [g19]: #apic-downgrade-compatibility-when-crossing-62-release +[g20]: #n9300-switch-memory ### Fault Checks Items | Faults | This Script | APIC built-in @@ -2725,6 +2727,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 N9K-C93180YC-FX3 switches only. It reviews `procMemUsage` and flags nodes with less than 32GB memory installed. This check is not version dependent and runs for all upgrade versions. + +Impact: Running an N9K-C93180YC-FX3 switch with less than 32GB memory can lead to memory pressure and increase the risk of service instability. + +If any N9K-C93180YC-FX3 switch is flagged by this check, upgrade the switch memory to at least 32GB. + ### Rogue EP Exception List missing on switches 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..3599e1a --- /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": "33554432" + } + } + }, + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-102/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "33554432" + } + } + } +] \ 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..6f1d221 --- /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": "33554432" + } + } + } +] \ 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..252adfa --- /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": "33554432" + } + } + }, + { + "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/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..c0256c6 --- /dev/null +++ b/tests/checks/n9300_switch_memory_24g_check/test_n9300_switch_memory_24g_check.py @@ -0,0 +1,116 @@ +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", + [ + # No nodes returned + ( + [], + {}, + "6.0(3c)", + script.NA, + 'No N9K-C93180YC-FX3 switches found. Skipping.', + [], + ), + # Non-N9K-C93180YC-FX3 node + ( + read_data(dir, "fabricNode_non_n9300.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_node201_gt32gb.json"), + }, + "6.0(3c)", + script.NA, + 'No N9K-C93180YC-FX3 switches found. Skipping.', + [], + ), + # N9K-C93180YC-FX3 node with >=32GB memory + ( + read_data(dir, "fabricNode_one.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_gt32gb.json"), + }, + "6.0(3c)", + script.PASS, + '', + [], + ), + # Multiple nodes, only N9K-C93180YC-FX3 checked, all >=32GB + ( + read_data(dir, "fabricNode_two.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_all_gt24gb.json"), + }, + "6.0(3c)", + script.PASS, + '', + [], + ), + # 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 N9K-C93180YC-FX3 nodes.', + [["101", "leaf101", "N9K-C93180YC-FX3"]], + ), + # N9K-C93180YC-FX3 node with <32GB memory + ( + read_data(dir, "fabricNode_two.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_mixed.json"), + }, + "6.0(3c)", + script.PASS, + '', + [], + ), + # N9K-C93180YC-FX3 node with <32GB memory (fail case) + ( + read_data(dir, "fabricNode_one.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_lt32gb.json"), + }, + "6.0(3c)", + script.FAIL_O, + '', + [["101", "leaf101", "N9K-C93180YC-FX3", 25.31]], + ), + ], +) +def test_logic(run_check, mock_icurl, fabric_nodes, tversion, expected_result, expected_msg, expected_data): + result = run_check( + tversion=script.AciVersion(tversion), + 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 diff --git a/tests/checks/n9300_switch_memory_32g_check/fabricNode_non_n9300.json b/tests/checks/n9300_switch_memory_32g_check/fabricNode_non_n9300.json new file mode 100644 index 0000000..a82341a --- /dev/null +++ b/tests/checks/n9300_switch_memory_32g_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_32g_check/fabricNode_one.json b/tests/checks/n9300_switch_memory_32g_check/fabricNode_one.json new file mode 100644 index 0000000..74a31ad --- /dev/null +++ b/tests/checks/n9300_switch_memory_32g_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_32g_check/fabricNode_two.json b/tests/checks/n9300_switch_memory_32g_check/fabricNode_two.json new file mode 100644 index 0000000..fad0289 --- /dev/null +++ b/tests/checks/n9300_switch_memory_32g_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_32g_check/fabricNode_two_fx3.json b/tests/checks/n9300_switch_memory_32g_check/fabricNode_two_fx3.json new file mode 100644 index 0000000..dfc8a77 --- /dev/null +++ b/tests/checks/n9300_switch_memory_32g_check/fabricNode_two_fx3.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-C93180YC-FX3", + "role": "leaf" + } + } + } +] diff --git a/tests/checks/n9300_switch_memory_32g_check/procMemUsage_all_gt32gb.json b/tests/checks/n9300_switch_memory_32g_check/procMemUsage_all_gt32gb.json new file mode 100644 index 0000000..c3aef55 --- /dev/null +++ b/tests/checks/n9300_switch_memory_32g_check/procMemUsage_all_gt32gb.json @@ -0,0 +1,20 @@ +[ + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-101/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "32676092" + } + } + }, + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-102/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "32676092" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/n9300_switch_memory_32g_check/procMemUsage_fail_and_missing.json b/tests/checks/n9300_switch_memory_32g_check/procMemUsage_fail_and_missing.json new file mode 100644 index 0000000..5f50d60 --- /dev/null +++ b/tests/checks/n9300_switch_memory_32g_check/procMemUsage_fail_and_missing.json @@ -0,0 +1,11 @@ +[ + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-101/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "16000000" + } + } + } +] diff --git a/tests/checks/n9300_switch_memory_32g_check/procMemUsage_gt32gb.json b/tests/checks/n9300_switch_memory_32g_check/procMemUsage_gt32gb.json new file mode 100644 index 0000000..5597a51 --- /dev/null +++ b/tests/checks/n9300_switch_memory_32g_check/procMemUsage_gt32gb.json @@ -0,0 +1,11 @@ +[ + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-101/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "32676092" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/n9300_switch_memory_32g_check/procMemUsage_invalid_total.json b/tests/checks/n9300_switch_memory_32g_check/procMemUsage_invalid_total.json new file mode 100644 index 0000000..dc1a4ee --- /dev/null +++ b/tests/checks/n9300_switch_memory_32g_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_32g_check/procMemUsage_lt32gb.json b/tests/checks/n9300_switch_memory_32g_check/procMemUsage_lt32gb.json new file mode 100644 index 0000000..5f50d60 --- /dev/null +++ b/tests/checks/n9300_switch_memory_32g_check/procMemUsage_lt32gb.json @@ -0,0 +1,11 @@ +[ + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-101/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "16000000" + } + } + } +] diff --git a/tests/checks/n9300_switch_memory_32g_check/procMemUsage_missing_affected_node.json b/tests/checks/n9300_switch_memory_32g_check/procMemUsage_missing_affected_node.json new file mode 100644 index 0000000..f8b2209 --- /dev/null +++ b/tests/checks/n9300_switch_memory_32g_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": "32676092" + } + } + } +] diff --git a/tests/checks/n9300_switch_memory_32g_check/procMemUsage_mixed.json b/tests/checks/n9300_switch_memory_32g_check/procMemUsage_mixed.json new file mode 100644 index 0000000..b7c74c0 --- /dev/null +++ b/tests/checks/n9300_switch_memory_32g_check/procMemUsage_mixed.json @@ -0,0 +1,20 @@ +[ + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-101/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "32676092" + } + } + }, + { + "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_32g_check/procMemUsage_node201_gt32gb.json b/tests/checks/n9300_switch_memory_32g_check/procMemUsage_node201_gt32gb.json new file mode 100644 index 0000000..db65e8c --- /dev/null +++ b/tests/checks/n9300_switch_memory_32g_check/procMemUsage_node201_gt32gb.json @@ -0,0 +1,11 @@ +[ + { + "procMemUsage": { + "attributes": { + "dn": "topology/pod-1/node-201/sys/procmem/memusage-sup", + "Modname": "sup", + "Total": "32676092" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/n9300_switch_memory_32g_check/test_n9300_switch_memory_32g_check.py b/tests/checks/n9300_switch_memory_32g_check/test_n9300_switch_memory_32g_check.py new file mode 100644 index 0000000..699f649 --- /dev/null +++ b/tests/checks/n9300_switch_memory_32g_check/test_n9300_switch_memory_32g_check.py @@ -0,0 +1,135 @@ +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", + [ + # No nodes returned + ( + [], + {}, + "6.0(3c)", + script.NA, + 'No N9K-C93180YC-FX3 switches found. Skipping.', + [], + ), + # Non-N9K-C93180YC-FX3 node + ( + read_data(dir, "fabricNode_non_n9300.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_node201_gt32gb.json"), + }, + "6.0(3c)", + script.NA, + 'No N9K-C93180YC-FX3 switches found. Skipping.', + [], + ), + # N9K-C93180YC-FX3 node with >=32GB memory + ( + read_data(dir, "fabricNode_one.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_gt32gb.json"), + }, + "6.0(3c)", + script.PASS, + '', + [], + ), + # Multiple nodes, only N9K-C93180YC-FX3 checked, all >=32GB + ( + read_data(dir, "fabricNode_two.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_all_gt32gb.json"), + }, + "6.0(3c)", + script.PASS, + '', + [], + ), + # 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 N9K-C93180YC-FX3 nodes.', + [["101", "leaf101", "N9K-C93180YC-FX3"]], + ), + # N9K-C93180YC-FX3 node with <32GB memory + ( + read_data(dir, "fabricNode_two.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_mixed.json"), + }, + "6.0(3c)", + script.PASS, + '', + [], + ), + # N9K-C93180YC-FX3 node with <32GB memory (fail case) + ( + read_data(dir, "fabricNode_one.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_lt32gb.json"), + }, + "6.0(3c)", + script.MANUAL, + ( + 'One or more N9K-C93180YC-FX3 switches have less than 32GB of memory. ' + 'An outage is not guaranteed but can occur. Please verify and upgrade the memory on affected nodes.' + ), + [["101", "leaf101", "N9K-C93180YC-FX3", 16.0]], + ), + # One FX3 node fails memory check, another FX3 node has missing procMemUsage data + ( + read_data(dir, "fabricNode_two_fx3.json"), + { + proc_mem_query: read_data(dir, "procMemUsage_fail_and_missing.json"), + }, + "6.0(3c)", + script.MANUAL, + ( + 'Some N9K-C93180YC-FX3 nodes have insufficient memory and others are missing ' + 'procMemUsage data. Please manually verify the memory on all affected nodes.\n' + 'Nodes with insufficient memory: 101\n' + 'Nodes with missing data: 102' + ), + [["101", "leaf101", "N9K-C93180YC-FX3", 16.0], ["102", "leaf102", "N9K-C93180YC-FX3", "N/A"]], + ), + ], +) +def test_logic(run_check, mock_icurl, fabric_nodes, tversion, expected_result, expected_msg, expected_data): + result = run_check( + tversion=script.AciVersion(tversion), + 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