diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index b420eeae..a951bfe4 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -38,7 +38,7 @@ import os import re -SCRIPT_VERSION = "v4.1.0" +SCRIPT_VERSION = "v4.2.0-dev" DEFAULT_TIMEOUT = 600 # sec # result constants DONE = 'DONE' @@ -3866,27 +3866,83 @@ def target_version_compatibility_check(cversion, tversion, **kwargs): return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) -@check_wrapper(check_title="Gen 1 switch compatibility") -def gen1_switch_compatibility_check(tversion, fabric_nodes, **kwargs): +@check_wrapper(check_title="Supported hardware compatibility") +def supported_hardware_check(tversion, fabric_nodes, **kwargs): result = FAIL_UF - headers = ["Target Version", "Node ID", "Model", "Warning"] + headers = ["Target Version", "Node ID", "Model", "Type", "Warning"] + data = [] + unformatted_headers = ["Target Version", "DN", "Model", "Type", "Warning"] + unformatted_data = [] gen1_models = ["N9K-C9336PQ", "N9K-X9736PQ", "N9K-C9504-FM", "N9K-C9508-FM", "N9K-C9516-FM", "N9K-C9372PX-E", "N9K-C9372TX-E", "N9K-C9332PQ", "N9K-C9372PX", "N9K-C9372TX", "N9K-C9396PX", "N9K-C9396TX", "N9K-C93128TX"] - data = [] + unsupported_6_0_1_switch_models = ["N9K-C93120TX"] + unsupported_6_1_1_switch_models = ["N9K-C93180LC-EX"] + unsupported_5_0_1_exp_module_models = ["N9K-M12PQ", "N9K-M6PQ", "N9K-M6PQ-E"] + unsupported_6_1_1_fex_models = ["N2K-C2332TQ-10GT", "N2K-C2348TQ-10GE", "N2K-C2232PP-10GE", "N2K-C2232TM-E-10GE", "N2K-C2348TQ-10G-E"] + unsupported_6_1_1_sup_models = ["N9K-SUP-A", "N9K-SUP-B"] recommended_action = 'Select supported target version or upgrade hardware' - doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#compatibility-switch-hardware-gen1' + doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#supported-hardware-compatibility' - if not tversion: - return Result(result=MANUAL, msg=TVER_MISSING) - if tversion.newer_than("5.0(1a)"): + if not tversion.older_than("5.0(1a)"): for node in fabric_nodes: - if node['fabricNode']['attributes']['model'] in gen1_models: - data.append([str(tversion), node['fabricNode']['attributes']['id'], - node['fabricNode']['attributes']['model'], 'Not supported on 5.x+']) - if not data: + model = node['fabricNode']['attributes']['model'] + if model in gen1_models: + data.append([str(tversion), node['fabricNode']['attributes']['id'], model, 'Switch', 'Not supported on 5.x+']) + + eqptLCs = icurl('class', 'eqptLC.json') + for eqptLC in eqptLCs: + model = eqptLC['eqptLC']['attributes']['model'] + if model in unsupported_5_0_1_exp_module_models: + dn = re.search(node_regex, eqptLC['eqptLC']['attributes']['dn']) + if dn: + data.append([str(tversion), dn.group('node'), model, 'Expansion Module', 'Not supported on 5.x+']) + else: + unformatted_data.append([str(tversion), eqptLC['eqptLC']['attributes']['dn'], model, 'Expansion Module', 'Not supported on 5.x+']) + + if not tversion.older_than("6.0(1a)"): + for node in fabric_nodes: + model = node['fabricNode']['attributes']['model'] + if model in unsupported_6_0_1_switch_models: + data.append([str(tversion), node['fabricNode']['attributes']['id'], model, 'Switch', 'Deprecated from 6.0(1)+']) + + if not tversion.older_than("6.1(1f)"): + for node in fabric_nodes: + model = node['fabricNode']['attributes']['model'] + if model in unsupported_6_1_1_switch_models: + data.append([str(tversion), node['fabricNode']['attributes']['id'], model, 'Switch', 'Deprecated from 6.1(1)+']) + + eqptExtChs = icurl('class', 'eqptExtCh.json') + for eqptExtCh in eqptExtChs: + model = eqptExtCh['eqptExtCh']['attributes']['model'] + if model in unsupported_6_1_1_fex_models: + dn = re.search(node_regex, eqptExtCh['eqptExtCh']['attributes']['dn']) + if dn: + data.append([str(tversion), dn.group('node'), model, 'FEX', 'Deprecated from 6.1(1)+']) + else: + unformatted_data.append([str(tversion), eqptExtCh['eqptExtCh']['attributes']['dn'], model, 'FEX', 'Deprecated from 6.1(1)+']) + + eqptSupCs = icurl('class', 'eqptSupC.json') + for eqptSupC in eqptSupCs: + model = eqptSupC['eqptSupC']['attributes']['model'] + if model in unsupported_6_1_1_sup_models: + dn = re.search(node_regex, eqptSupC['eqptSupC']['attributes']['dn']) + if dn: + data.append([str(tversion), dn.group('node'), model, 'Supervisor', 'Deprecated from 6.1(1)+']) + else: + unformatted_data.append([str(tversion), eqptSupC['eqptSupC']['attributes']['dn'], model, 'Supervisor', 'Deprecated from 6.1(1)+']) + + if not data and not unformatted_data: result = PASS - return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) + 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="Contract Port 22 Defect") @@ -6417,7 +6473,7 @@ class CheckManager: api_checks = [ # General Checks target_version_compatibility_check, - gen1_switch_compatibility_check, + supported_hardware_check, r_leaf_compatibility_check, cimc_compatibilty_check, apic_cluster_health_check, diff --git a/docs/docs/validations.md b/docs/docs/validations.md index 64d59554..82f22119 100644 --- a/docs/docs/validations.md +++ b/docs/docs/validations.md @@ -37,7 +37,8 @@ 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: -[Svccore Excessive Data Check][g20] | :white_check_mark: | :no_entry_sign: +[Supported Hardware Compatibility][g20] | :white_check_mark: | :no_entry_sign: +[Svccore Excessive Data Check][g21] | :white_check_mark: | :no_entry_sign: [g1]: #compatibility-target-aci-version [g2]: #compatibility-cimc-version @@ -58,7 +59,8 @@ Items | This Script [g17]: #fabric-link-redundancy [g18]: #apic-database-size [g19]: #apic-downgrade-compatibility-when-crossing-62-release -[g20]: #svccore-excessive-data-check +[g20]: #supported-hardware-compatibility +[g21]: #svccore-excessive-data-check ### Fault Checks Items | Faults | This Script | APIC built-in @@ -267,6 +269,17 @@ The script checks the presence of generation one switches when the upgrade is cr Or you can check the [Release Note 15.0(1) of ACI switches][3] to see the list of generation one switches, typically the one without any suffix such as N9K-C9372PX, that are no longer supported from 15.0(1) release. +### Supported Hardware Compatibility + +The script checks the presence of deprecated hardware in the fabric. + +The list of supported and unsupported hardware is populated from the Release Notes across all ACI releases. This means the check covers hardware compatibility changes introduced in any version, not just the most recent release. As new release notes are published and hardware is deprecated, this list is updated accordingly. + +Refer the [Release Note 15.0(1) of ACI switches][3] to see the list of unsuporrted hardware for your desired target versions. Prior upgrading to target version, replace the unsupported hardware elements in your fabric with other supported hardware. + +Contact cisco TAC for further assistance. + + ### Compatibility (Remote Leaf Switch) The script checks the requirement to use remote leaf switches on the target version. diff --git a/tests/checks/gen1_switch_compatibility_check/fabricNode_no_gen1.json b/tests/checks/gen1_switch_compatibility_check/fabricNode_no_gen1.json deleted file mode 100644 index d266e8af..00000000 --- a/tests/checks/gen1_switch_compatibility_check/fabricNode_no_gen1.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "fabricNode": { - "attributes": { - "adSt": "on", - "dn": "topology/pod-2/node-201", - "fabricSt": "active", - "id": "201", - "model": "N9K-C93180YC-FX", - "name": "RL201", - "nodeType": "remote-leaf-wan", - "role": "leaf" - } - } - }, - { - "fabricNode": { - "attributes": { - "adSt": "on", - "dn": "topology/pod-2/node-202", - "fabricSt": "active", - "id": "202", - "model": "N9K-C93180YC-FX", - "name": "RL202", - "nodeType": "remote-leaf-wan", - "role": "leaf" - } - } - } -] diff --git a/tests/checks/gen1_switch_compatibility_check/fabricNode_with_gen1.json b/tests/checks/gen1_switch_compatibility_check/fabricNode_with_gen1.json deleted file mode 100644 index e01273b6..00000000 --- a/tests/checks/gen1_switch_compatibility_check/fabricNode_with_gen1.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "fabricNode": { - "attributes": { - "adSt": "on", - "dn": "topology/pod-1/node-101", - "fabricSt": "active", - "id": "101", - "model": "N9K-C9372TX-E", - "name": "Leaf-101", - "nodeType": "unspecified", - "role": "leaf" - } - } - }, - { - "fabricNode": { - "attributes": { - "adSt": "on", - "dn": "topology/pod-1/node-102", - "fabricSt": "active", - "id": "102", - "model": "N9K-C9372TX-E", - "name": "Leaf-102", - "nodeType": "unspecified", - "role": "leaf" - } - } - }, - { - "fabricNode": { - "attributes": { - "adSt": "on", - "dn": "topology/pod-1/node-1001", - "fabricSt": "active", - "id": "1001", - "model": "N9K-C9332PQ", - "name": "Spine-1001", - "nodeType": "unspecified", - "role": "spine" - } - } - } -] diff --git a/tests/checks/gen1_switch_compatibility_check/test_gen1_switch_compatibility_check.py b/tests/checks/gen1_switch_compatibility_check/test_gen1_switch_compatibility_check.py deleted file mode 100644 index 4f076c56..00000000 --- a/tests/checks/gen1_switch_compatibility_check/test_gen1_switch_compatibility_check.py +++ /dev/null @@ -1,42 +0,0 @@ -import os -import pytest -import logging -import importlib -from helpers.utils import read_data - -log = logging.getLogger(__name__) -dir = os.path.dirname(os.path.abspath(__file__)) - -script = importlib.import_module("aci-preupgrade-validation-script") -AciVersion = script.AciVersion - -test_function = "gen1_switch_compatibility_check" - - -@pytest.mark.parametrize( - "tversion, fabric_nodes, expected_result, expected_data", - [ - # FAIL - gen1 HW does not support t_ver - ( - "5.2(3b)", - read_data(dir, "fabricNode_with_gen1.json"), - script.FAIL_UF, - [ - ["5.2(3b)", "101", "N9K-C9372TX-E", "Not supported on 5.x+"], - ["5.2(3b)", "102", "N9K-C9372TX-E", "Not supported on 5.x+"], - ["5.2(3b)", "1001", "N9K-C9332PQ", "Not supported on 5.x+"], - ], - ), - # PASS - gen1 HW supports t_ver - ("4.2(7r)", read_data(dir, "fabricNode_with_gen1.json"), script.PASS, []), - # PASS - no gen1 hw found - ("5.2(3b)", read_data(dir, "fabricNode_no_gen1.json"), script.PASS, []), - ], -) -def test_logic(run_check, tversion, fabric_nodes, expected_result, expected_data): - result = run_check( - tversion=AciVersion(tversion) if tversion else None, - fabric_nodes=fabric_nodes, - ) - assert result.result == expected_result - assert result.data == expected_data diff --git a/tests/checks/supported_hardware_check/eqptExtCh_supported_only.json b/tests/checks/supported_hardware_check/eqptExtCh_supported_only.json new file mode 100644 index 00000000..88400f8d --- /dev/null +++ b/tests/checks/supported_hardware_check/eqptExtCh_supported_only.json @@ -0,0 +1,10 @@ +[ + { + "eqptExtCh": { + "attributes": { + "dn": "topology/pod-1/node-101/sys/ch/supslot-1/extch-101", + "model": "N2K-C2348UPQ" + } + } + } +] diff --git a/tests/checks/supported_hardware_check/eqptExtCh_with_unsupported.json b/tests/checks/supported_hardware_check/eqptExtCh_with_unsupported.json new file mode 100644 index 00000000..848f9070 --- /dev/null +++ b/tests/checks/supported_hardware_check/eqptExtCh_with_unsupported.json @@ -0,0 +1,18 @@ +[ + { + "eqptExtCh": { + "attributes": { + "dn": "topology/pod-1/node-101/sys/ch/supslot-1/extch-101", + "model": "N2K-C2232PP-10GE" + } + } + }, + { + "eqptExtCh": { + "attributes": { + "dn": "topology/pod-1/node-101/sys/ch/supslot-1/extch-102", + "model": "N2K-C2348UPQ" + } + } + } +] diff --git a/tests/checks/supported_hardware_check/eqptLC_supported_only.json b/tests/checks/supported_hardware_check/eqptLC_supported_only.json new file mode 100644 index 00000000..9d27af9a --- /dev/null +++ b/tests/checks/supported_hardware_check/eqptLC_supported_only.json @@ -0,0 +1,10 @@ +[ + { + "eqptLC": { + "attributes": { + "dn": "topology/pod-1/node-1001/sys/ch/lcslot-1/lc", + "model": "N9K-X9732C-EX" + } + } + } +] diff --git a/tests/checks/supported_hardware_check/eqptLC_with_unformatted_dn.json b/tests/checks/supported_hardware_check/eqptLC_with_unformatted_dn.json new file mode 100644 index 00000000..7520fe4e --- /dev/null +++ b/tests/checks/supported_hardware_check/eqptLC_with_unformatted_dn.json @@ -0,0 +1,10 @@ +[ + { + "eqptLC": { + "attributes": { + "dn": "sys/ch/lcslot-1/lc", + "model": "N9K-M6PQ" + } + } + } +] diff --git a/tests/checks/supported_hardware_check/eqptLC_with_unsupported.json b/tests/checks/supported_hardware_check/eqptLC_with_unsupported.json new file mode 100644 index 00000000..f3adc68b --- /dev/null +++ b/tests/checks/supported_hardware_check/eqptLC_with_unsupported.json @@ -0,0 +1,18 @@ +[ + { + "eqptLC": { + "attributes": { + "dn": "topology/pod-1/node-1001/sys/ch/lcslot-1/lc", + "model": "N9K-M6PQ" + } + } + }, + { + "eqptLC": { + "attributes": { + "dn": "topology/pod-1/node-1001/sys/ch/lcslot-2/lc", + "model": "N9K-X9732C-EX" + } + } + } +] diff --git a/tests/checks/supported_hardware_check/eqptSupC_supported_only.json b/tests/checks/supported_hardware_check/eqptSupC_supported_only.json new file mode 100644 index 00000000..c8d387a3 --- /dev/null +++ b/tests/checks/supported_hardware_check/eqptSupC_supported_only.json @@ -0,0 +1,10 @@ +[ + { + "eqptSupC": { + "attributes": { + "dn": "topology/pod-1/node-1001/sys/ch/supslot-1/sup", + "model": "N9K-SUP-C" + } + } + } +] diff --git a/tests/checks/supported_hardware_check/eqptSupC_with_unsupported.json b/tests/checks/supported_hardware_check/eqptSupC_with_unsupported.json new file mode 100644 index 00000000..da9f67aa --- /dev/null +++ b/tests/checks/supported_hardware_check/eqptSupC_with_unsupported.json @@ -0,0 +1,18 @@ +[ + { + "eqptSupC": { + "attributes": { + "dn": "topology/pod-1/node-1001/sys/ch/supslot-1/sup", + "model": "N9K-SUP-B" + } + } + }, + { + "eqptSupC": { + "attributes": { + "dn": "topology/pod-1/node-1001/sys/ch/supslot-2/sup", + "model": "N9K-SUP-C" + } + } + } +] diff --git a/tests/checks/supported_hardware_check/fabricNode_supported_only.json b/tests/checks/supported_hardware_check/fabricNode_supported_only.json new file mode 100644 index 00000000..3b6cb1a5 --- /dev/null +++ b/tests/checks/supported_hardware_check/fabricNode_supported_only.json @@ -0,0 +1,20 @@ +[ + { + "fabricNode": { + "attributes": { + "id": "101", + "model": "N9K-C93180YC-FX", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "id": "1001", + "model": "N9K-C9504", + "role": "spine" + } + } + } +] diff --git a/tests/checks/supported_hardware_check/fabricNode_with_unsupported_hardware.json b/tests/checks/supported_hardware_check/fabricNode_with_unsupported_hardware.json new file mode 100644 index 00000000..6345c323 --- /dev/null +++ b/tests/checks/supported_hardware_check/fabricNode_with_unsupported_hardware.json @@ -0,0 +1,38 @@ +[ + { + "fabricNode": { + "attributes": { + "id": "101", + "model": "N9K-C9372TX-E", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "id": "102", + "model": "N9K-C93180LC-EX", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "id": "103", + "model": "N9K-C93180YC-FX", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "id": "104", + "model": "N9K-C9332PQ", + "role": "leaf" + } + } + } +] diff --git a/tests/checks/supported_hardware_check/test_supported_hardware_check.py b/tests/checks/supported_hardware_check/test_supported_hardware_check.py new file mode 100644 index 00000000..ddf1452b --- /dev/null +++ b/tests/checks/supported_hardware_check/test_supported_hardware_check.py @@ -0,0 +1,133 @@ +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 = "supported_hardware_check" +# icurl queries +eqptLC = "eqptLC.json" +eqptExtCh = "eqptExtCh.json" +eqptSupC = "eqptSupC.json" + +@pytest.mark.parametrize( + "icurl_outputs, tversion, fabric_nodes, expected_result, expected_data, expected_unformatted_data", + [ + # FAIL - unsupported Gen1 and 6.1(1)+ deprecated hardware found + ( + { + eqptLC: read_data(dir, "eqptLC_with_unsupported.json"), + eqptExtCh: read_data(dir, "eqptExtCh_with_unsupported.json"), + eqptSupC: read_data(dir, "eqptSupC_with_unsupported.json"), + }, + "6.1(1f)", + read_data(dir, "fabricNode_with_unsupported_hardware.json"), + script.FAIL_UF, + [ + ["6.1(1f)", "101", "N9K-C9372TX-E", "Switch", "Not supported on 5.x+"], + ["6.1(1f)", "104", "N9K-C9332PQ", "Switch", "Not supported on 5.x+"], + ["6.1(1f)", "1001", "N9K-M6PQ", "Expansion Module", "Not supported on 5.x+"], + ["6.1(1f)", "102", "N9K-C93180LC-EX", "Switch", "Deprecated from 6.1(1)+"], + ["6.1(1f)", "101", "N2K-C2232PP-10GE", "FEX", "Deprecated from 6.1(1)+"], + ["6.1(1f)", "1001", "N9K-SUP-B", "Supervisor", "Deprecated from 6.1(1)+"], + ], + [], + ), + # PASS - no unsupported hardware found + ( + { + eqptLC: read_data(dir, "eqptLC_supported_only.json"), + eqptExtCh: read_data(dir, "eqptExtCh_supported_only.json"), + eqptSupC: read_data(dir, "eqptSupC_supported_only.json"), + }, + "6.1(1f)", + read_data(dir, "fabricNode_supported_only.json"), + script.PASS, + [], + [], + ), + # FAIL - pre 6.1(1f): only Gen1 hit should be reported + ( + { + eqptLC: read_data(dir, "eqptLC_supported_only.json"), + }, + "6.1(1a)", + read_data(dir, "fabricNode_with_unsupported_hardware.json"), + script.FAIL_UF, + [["6.1(1a)", "101", "N9K-C9372TX-E", "Switch", "Not supported on 5.x+"], + ["6.1(1a)", "104", "N9K-C9332PQ", "Switch", "Not supported on 5.x+"]], + [], + ), + # PASS - pre 5.x: unsupported hardware checks should not trigger + ( + { + eqptLC: read_data(dir, "eqptLC_with_unsupported.json"), + eqptExtCh: read_data(dir, "eqptExtCh_with_unsupported.json"), + eqptSupC: read_data(dir, "eqptSupC_with_unsupported.json"), + }, + "4.2(7r)", + read_data(dir, "fabricNode_with_unsupported_hardware.json"), + script.PASS, + [], + [], + ), + # FAIL - 6.0(1)+ unsupported switch model + ( + { + eqptLC: read_data(dir, "eqptLC_supported_only.json"), + }, + "6.0(1a)", + [ + { + "fabricNode": { + "attributes": { + "id": "201", + "model": "N9K-C93120TX", + } + } + } + ], + script.FAIL_UF, + [["6.0(1a)", "201", "N9K-C93120TX", "Switch", "Deprecated from 6.0(1)+"]], + [], + ), + # PASS - empty fabric nodes and supported inventory + ( + { + eqptLC: read_data(dir, "eqptLC_supported_only.json"), + eqptExtCh: read_data(dir, "eqptExtCh_supported_only.json"), + eqptSupC: read_data(dir, "eqptSupC_supported_only.json"), + }, + "6.1(1f)", + [], + script.PASS, + [], + [], + ), + # FAIL - expansion module with unformatted DN (no topology/pod-X/node-Y prefix); + # the entry should appear in unformatted_data with the raw DN, not in data + ( + { + eqptLC: read_data(dir, "eqptLC_with_unformatted_dn.json"), + }, + "5.0(1a)", + [], + script.FAIL_UF, + [], + [["5.0(1a)", "sys/ch/lcslot-1/lc", "N9K-M6PQ", "Expansion Module", "Not supported on 5.x+"]], + ), + ], +) +def test_logic(run_check, mock_icurl, tversion, fabric_nodes, expected_result, expected_data, expected_unformatted_data): + result = run_check( + tversion=script.AciVersion(tversion) if tversion else None, + fabric_nodes=fabric_nodes, + ) + + assert result.result == expected_result + assert result.data == expected_data + assert result.unformatted_data == expected_unformatted_data