diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 4b83f4c..ef7cd30 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -6026,6 +6026,29 @@ 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='Auto Firmware Update on Switch Discovery') +def auto_firmware_update_on_switch_check(cversion, tversion, **kwargs): + result = PASS + headers = ["Auto Firmware Update Status", "Default Firmware Version", "Upgrade Target Version"] + data = [] + recommended_action = 'Disable Auto Firmware Update before the upgrade as a precaution. See the reference doc for details.' + doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#auto-firmware-update-on-switch-discovery' + + if not tversion or not cversion: + return Result(result=MANUAL, msg=TVER_MISSING) + + if tversion.older_than("6.0(3a)") or ( + cversion.newer_than("6.0(3a)") or (cversion.major1 == "5" and cversion.newer_than("5.2(8a)")) + ): + return Result(result=NA, msg=VER_NOT_AFFECTED) + + fwrepop = icurl("mo", "uni/fabric/fwrepop.json") + if fwrepop and fwrepop[0]["firmwareRepoP"]["attributes"]["enforceBootscriptVersionValidation"] == "yes": + data.append(["Enabled", fwrepop[0]["firmwareRepoP"]["attributes"]["defaultSwitchVersion"], str(tversion)]) + result = MANUAL + + return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) + # ---- Script Execution ---- @@ -6188,6 +6211,7 @@ class CheckManager: standby_sup_sync_check, isis_database_byte_check, configpush_shard_check, + auto_firmware_update_on_switch_check, ] ssh_checks = [ diff --git a/docs/docs/validations.md b/docs/docs/validations.md index 68ca1c0..f46e03d 100644 --- a/docs/docs/validations.md +++ b/docs/docs/validations.md @@ -192,7 +192,8 @@ Items | Defect | This Script [Observer Database Size][d25] | CSCvw45531 | :white_check_mark: | :no_entry_sign: [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: | +[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: [d1]: #ep-announce-compatibility [d2]: #eventmgr-db-size-defect-susceptibility @@ -222,7 +223,7 @@ Items | Defect | This Script [d26]: #stale-pconsra-object [d27]: #isis-dteps-byte-size [d28]: #policydist-configpushshardcont-crash - +[d29]: #auto-firmware-update-on-switch-discovery ## General Check Details @@ -2647,6 +2648,25 @@ 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. +### 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: + +* when adding a new switch to expand the fabric +* when replacing an existing switch +* when initializing and rediscovering an existing switch + +It does not activate during regular upgrades initiated through the APIC. + +Due to [CSCwe83941][62], if a new switch is running 6.0(1), 6.0(2) or any version older than 5.2(8), attempting to upgrade it to 6.0(3)+ using Auto Firmware Update will fail. The switch will become unusable until a manual recovery procedure is performed directly on the device. + +While this issue does not occur during standard upgrades, it is important to be aware of the risk when your target version is 6.0(3) or newer and the switch is running 6.0(1), 6.0(2), or a version older than 5.2(8). Auto Firmware Update may get triggered and hit this issue during switch replacement in an upgrade window or if you need to re-initialize a switch after a failed upgrade. + +To avoid this risk, consider disabling Auto Firmware Update before upgrading to 6.0(3)+ if any switches are running the affected older versions. In the future, ensure that any new switch is running a compatible version before re-enabling Auto Firmware Update and registering it to the fabric. + +!!! 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. + [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 @@ -2710,3 +2730,5 @@ 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/CSCwe83941 +[63]: https://www.cisco.com/c/en/us/td/docs/dcn/aci/apic/all/apic-installation-aci-upgrade-downgrade/Cisco-APIC-Installation-ACI-Upgrade-Downgrade-Guide/m-auto-firmware-update.html \ No newline at end of file diff --git a/tests/checks/auto_firmware_update_on_switch_check/firmwareRepoP-neg.json b/tests/checks/auto_firmware_update_on_switch_check/firmwareRepoP-neg.json new file mode 100644 index 0000000..5830873 --- /dev/null +++ b/tests/checks/auto_firmware_update_on_switch_check/firmwareRepoP-neg.json @@ -0,0 +1,25 @@ +[ + { + "firmwareRepoP": { + "attributes": { + "annotation": "", + "childAction": "", + "defaultSwitchVersion": "n9000-16.0(9d)", + "descr": "", + "dn": "uni/fabric/fwrepop", + "enforceBootscriptVersionValidation": "no", + "extMngdBy": "", + "lcOwn": "local", + "modTs": "2025-08-13T17:50:54.830+00:00", + "monPolDn": "uni/fabric/monfab-default", + "name": "default", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "", + "status": "", + "uid": "0", + "userdom": "all" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/auto_firmware_update_on_switch_check/firmwareRepoP-pos.json b/tests/checks/auto_firmware_update_on_switch_check/firmwareRepoP-pos.json new file mode 100644 index 0000000..06e58bb --- /dev/null +++ b/tests/checks/auto_firmware_update_on_switch_check/firmwareRepoP-pos.json @@ -0,0 +1,25 @@ +[ + { + "firmwareRepoP": { + "attributes": { + "annotation": "", + "childAction": "", + "defaultSwitchVersion": "n9000-16.0(9d)", + "descr": "", + "dn": "uni/fabric/fwrepop", + "enforceBootscriptVersionValidation": "yes", + "extMngdBy": "", + "lcOwn": "local", + "modTs": "2025-08-13T17:50:54.830+00:00", + "monPolDn": "uni/fabric/monfab-default", + "name": "default", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "", + "status": "", + "uid": "0", + "userdom": "all" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/auto_firmware_update_on_switch_check/test_auto_firmware_update_on_switch_check.py b/tests/checks/auto_firmware_update_on_switch_check/test_auto_firmware_update_on_switch_check.py new file mode 100644 index 0000000..6b084b6 --- /dev/null +++ b/tests/checks/auto_firmware_update_on_switch_check/test_auto_firmware_update_on_switch_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 = "auto_firmware_update_on_switch_check" + +# icurl queries +auto_firmware_update_api = "uni/fabric/fwrepop.json" + + +@pytest.mark.parametrize( + "icurl_outputs, cversion, tversion, expected_result, expected_data", + [ + # MANUAL cases + ( + {auto_firmware_update_api: read_data(dir, "firmwareRepoP-pos.json")}, + None, + None, + script.MANUAL, + [], + ), + ( + {auto_firmware_update_api: read_data(dir, "firmwareRepoP-pos.json")}, + "5.2(7a)", + None, + script.MANUAL, + [], + ), + ( + {auto_firmware_update_api: read_data(dir, "firmwareRepoP-pos.json")}, + None, + "6.0(3d)", + script.MANUAL, + [], + ), + # NA cases + # firmwareRepoP cversion < 5.2(7) , tversion < 6.0(3) Result NA + ( + {auto_firmware_update_api: read_data(dir, "firmwareRepoP-pos.json")}, + "5.2(7a)", + "6.0(2d)", + script.NA, + [], + ), + # firmwareRepoP 5.2(7) < cversion < 6.0(1) , tversion < 6.0(3) Result NA + ( + {auto_firmware_update_api: read_data(dir, "firmwareRepoP-pos.json")}, + "5.3(2a)", + "6.0(2d)", + script.NA, + [], + ), + # firmwareRepoP 5.2(7) < cversion < 6.0(1) , tversion > 6.0(3) Result NA + ( + {auto_firmware_update_api: read_data(dir, "firmwareRepoP-pos.json")}, + "5.3(2a)", + "6.0(9d)", + script.NA, + [], + ), + # firmwareRepoP cversion > 6.0(3) , tversion > 6.0(3) Result NA + ( + {auto_firmware_update_api: read_data(dir, "firmwareRepoP-pos.json")}, + "6.0(3d)", + "6.0(9d)", + script.NA, + [], + ), + # Failure cases + # firmwareRepoP cversion < 5.2(7) , tversion > 6.0(3) Result MANUAL + ( + {auto_firmware_update_api: read_data(dir, "firmwareRepoP-pos.json")}, + "5.2(7a)", + "6.0(3d)", + script.MANUAL, + [["Enabled", "n9000-16.0(9d)", "6.0(3d)"]], + ), + # firmwareRepoP cversion is 6.0(1) or 6.0(2) , tversion > 6.0(3) Result MANUAL + ( + {auto_firmware_update_api: read_data(dir, "firmwareRepoP-pos.json")}, + "6.0(2a)", + "6.0(3d)", + script.MANUAL, + [["Enabled", "n9000-16.0(9d)", "6.0(3d)"]], + ), + # Pass cases + # no firmwareRepoP cversion is < 5.2(7) , tversion > 6.0(3) Result PASS + ( + {auto_firmware_update_api: []}, + "5.2(7a)", + "6.0(3d)", + script.PASS, + [], + ), + # no firmwareRepoP cversion is 6.0(1) or 6.0(2) , tversion > 6.0(3) Result PASS + ( + {auto_firmware_update_api: []}, + "6.0(2a)", + "6.0(3d)", + script.PASS, + [], + ), + # no firmwareRepoP cversion is < 5.2(7) , tversion > 6.0(3) Result PASS + ( + {auto_firmware_update_api: read_data(dir, "firmwareRepoP-neg.json")}, + "5.2(7a)", + "6.0(3d)", + script.PASS, + [], + ), + # no firmwareRepoP cversion is 6.0(1) or 6.0(2) , tversion > 6.0(3) Result PASS + ( + {auto_firmware_update_api: read_data(dir, "firmwareRepoP-neg.json")}, + "6.0(2a)", + "6.0(3d)", + script.PASS, + [], + ), + ], +) +def test_logic(run_check, mock_icurl, cversion, tversion, expected_result, expected_data): + + result = run_check( + cversion=script.AciVersion(cversion) if cversion else None, + tversion=script.AciVersion(tversion) if tversion else None, + ) + assert result.result == expected_result + assert result.data == expected_data