Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions aci-preupgrade-validation-script.py
Original file line number Diff line number Diff line change
Expand Up @@ -6025,6 +6025,61 @@ 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)

# Connection Base Check
@check_wrapper(check_title='GX HW_Changes_Bit')
def gx_hw_changes_bit_check(username, password, fabric_nodes, cversion, tversion, **kwargs):
result = PASS
headers = ["Switch ID", "Switch Name", "HW Changes Bits value"]
data = []
recommended_action = 'Review The Bug RNE and apply the workaround before Upgrade'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium: Review The Bug RNE is internal terminology and does not give users an actionable, customer-safe remediation. Please point users to the authoritative CSCvv04251 guidance or instruct them to contact Cisco TAC before proceeding, rather than referring to an RNE they may not be able to access.

doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations#gx_hw_changes_bit'

if not tversion:
return Result(result=MANUAL, msg=TVER_MISSING)
if (
cversion.older_than("4.2(3a)") or cversion.newer_than("4.2(5a)")
) and tversion.newer_than("4.2(5l)"):
result = NA

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: Setting result = NA here does not stop the check. The function continues into SSH collection, so an out-of-scope upgrade can be changed to FAIL_UF when 0x3 is found or ERROR when SSH fails. Please return the NA Result immediately when the version combination is not applicable, and cover the boundaries with affected GX fixtures (the current NA tests use the non-GX fixture and therefore cannot catch this).

has_error = False
# -GX Switches can be Leaf or Spine
switches = [node for node in fabric_nodes if (
node["fabricNode"]["attributes"]["model"] == "N9K-C9316D-GX" or
node["fabricNode"]["attributes"]["model"] == "N9K-C93600CD-GX"
)]
if not switches:
return Result(result=NA, msg="No affected Switches found", doc_url=doc_url)

for switch in switches:
switch_id = switch["fabricNode"]["attributes"]["id"]
switch_name = switch["fabricNode"]["attributes"]["name"]
switch_addr = switch["fabricNode"]["attributes"]["address"]

try:
c = Connection(switch_addr)
c.username = username
c.password = password
c.log = LOG_FILE
c.connect()
except Exception as e:
data.append([switch_id, switch_name, str(e)])
has_error = True
continue
try:
cmd = r"vsh -c 'show sprom cpu-info' | grep 'HW Changes Bits'"
c.cmd(cmd)
if "HW Changes Bits : 0x3" in c.output:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: This exact substring check treats every other output as safe, including empty/malformed output and a command timeout (Connection.cmd() can return timeout without raising). That can report PASS without ever reading a valid HW Changes Bits value. Please parse and validate the returned value, report ERROR when it is missing/unparseable or the command did not return prompt, and add empty, malformed, and timeout tests.

data.append([switch_id, switch_name, '0x3 Found!'])
#has_error = True
continue
except Exception as e:
data.append([switch_id, switch_name, str(e)])
has_error = True
continue
if has_error:
result = ERROR
elif data:
result = FAIL_UF
return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url)

# ---- Script Execution ----

Expand Down Expand Up @@ -6188,6 +6243,7 @@ class CheckManager:
standby_sup_sync_check,
isis_database_byte_check,
configpush_shard_check,
gx_hw_changes_bit_check,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High: This check opens SSH connections but is registered in api_checks. With --api-only, query_common_data() intentionally leaves username/password as None, yet this check will still attempt SSH to every GX switch. Please move it to ssh_checks and add a CheckManager API-only registration test.


]
ssh_checks = [
Expand Down
8 changes: 8 additions & 0 deletions docs/docs/validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
[GX Hw Changes Bit][d29] | CSCvv04251 | :white_check_mark: | :no_entry_sign:

[d1]: #ep-announce-compatibility
[d2]: #eventmgr-db-size-defect-susceptibility
Expand Down Expand Up @@ -222,6 +223,7 @@ Items | Defect | This Script
[d26]: #stale-pconsra-object

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium: This fragment does not match the generated Markdown anchor for ### GX HW Changes Bit, which is #gx-hw-changes-bit; the link will not navigate to the new section. Please correct this reference and the matching runtime doc_url in gx_hw_changes_bit_check (which currently uses the same underscore fragment).

[d27]: #isis-dteps-byte-size
[d28]: #policydist-configpushshardcont-crash
[d29]: #gx_hw_changes_bit


## General Check Details
Expand Down Expand Up @@ -2647,6 +2649,11 @@ 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.

### GX HW Changes Bit

Due to [CSCvv04251][62] a policy Upgrade on N9K-C9316D-GX and N9K-C93600CD-GX ACI Switches can fail if the Sprom CPU-Info has a '0x3' Value.
A Workaround needs to be performed on any of the affected switches before proceeding with the a policy 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
Expand Down Expand Up @@ -2710,3 +2717,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/CSCvv04251
64 changes: 64 additions & 0 deletions tests/checks/gx_hw_changes_bit_check/fabricNode-Neg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
[
{
"fabricNode": {
"attributes": {
"adSt": "on",
"address": "10.1.72.70",
"annotation": "",
"apicType": "apic",
"childAction": "",
"delayedHeartbeat": "no",
"dn": "topology/pod-1/node-101",
"extMngdBy": "",
"fabricSt": "active",
"id": "101",
"lastStateModTs": "2026-01-21T14:24:21.952-05:00",
"lcOwn": "local",
"modTs": "2026-01-21T14:24:22.964-05:00",
"model": "N9K-C93180YC-EX",
"monPolDn": "uni/fabric/monfab-default",
"name": "leaf1",
"nameAlias": "",
"nodeType": "unspecified",
"role": "leaf",
"serial": "FDO221802B0",
"status": "",
"uid": "0",
"userdom": "all",
"vendor": "Cisco Systems, Inc",
"version": "n9000-16.0(9d)"
}
}
},
{
"fabricNode": {
"attributes": {
"adSt": "on",
"address": "10.1.72.71",
"annotation": "",
"apicType": "apic",
"childAction": "",
"delayedHeartbeat": "no",
"dn": "topology/pod-1/node-103",
"extMngdBy": "",
"fabricSt": "active",
"id": "103",
"lastStateModTs": "2026-01-20T19:37:36.258-05:00",
"lcOwn": "local",
"modTs": "2026-01-20T19:38:54.044-05:00",
"model": "N9K-C93180YC-EX",
"monPolDn": "uni/fabric/monfab-default",
"name": "leaf3",
"nameAlias": "",
"nodeType": "unspecified",
"role": "leaf",
"serial": "FDO22180284",
"status": "",
"uid": "0",
"userdom": "all",
"vendor": "Cisco Systems, Inc",
"version": "n9000-16.0(9d)"
}
}
}
]
64 changes: 64 additions & 0 deletions tests/checks/gx_hw_changes_bit_check/fabricNode-Pos.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
[
{
"fabricNode": {
"attributes": {
"adSt": "on",
"address": "10.1.72.70",
"annotation": "",
"apicType": "apic",
"childAction": "",
"delayedHeartbeat": "no",
"dn": "topology/pod-1/node-101",
"extMngdBy": "",
"fabricSt": "active",
"id": "101",
"lastStateModTs": "2026-01-21T14:24:21.952-05:00",
"lcOwn": "local",
"modTs": "2026-01-21T14:24:22.964-05:00",
"model": "N9K-C9316D-GX",
"monPolDn": "uni/fabric/monfab-default",
"name": "leaf1",
"nameAlias": "",
"nodeType": "unspecified",
"role": "leaf",
"serial": "FDO221802B0",
"status": "",
"uid": "0",
"userdom": "all",
"vendor": "Cisco Systems, Inc",
"version": "n9000-16.0(9d)"
}
}
},
{
"fabricNode": {
"attributes": {
"adSt": "on",
"address": "10.1.72.71",
"annotation": "",
"apicType": "apic",
"childAction": "",
"delayedHeartbeat": "no",
"dn": "topology/pod-1/node-103",
"extMngdBy": "",
"fabricSt": "active",
"id": "103",
"lastStateModTs": "2026-01-20T19:37:36.258-05:00",
"lcOwn": "local",
"modTs": "2026-01-20T19:38:54.044-05:00",
"model": "N9K-C93600CD-GX",
"monPolDn": "uni/fabric/monfab-default",
"name": "leaf3",
"nameAlias": "",
"nodeType": "unspecified",
"role": "leaf",
"serial": "FDO22180284",
"status": "",
"uid": "0",
"userdom": "all",
"vendor": "Cisco Systems, Inc",
"version": "n9000-16.0(9d)"
}
}
}
]
155 changes: 155 additions & 0 deletions tests/checks/gx_hw_changes_bit_check/test_gx_hw_changes_bit_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
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 = "gx_hw_changes_bit_check"

fabricNodes = read_data(dir, "fabricNode-Pos.json")
switch_ips = [
mo["fabricNode"]["attributes"]["address"]
for mo in fabricNodes
if "-GX" in mo["fabricNode"]["attributes"]["model"]
]

sprom_cmd = "vsh -c 'show sprom cpu-info' | grep 'HW Changes Bits'"
sprom_output_neg = " HW Changes Bits : 0x0"
sprom_output_pos = " HW Changes Bits : 0x3"

sprom_output_no_such_file = ""


@pytest.mark.parametrize(
"icurl_outputs, fabric_nodes, cversion, tversion, conn_failure, conn_cmds, expected_result, expected_data",
[
# NA, no fabricNode with affected models
(
{},
read_data(dir, "fabricNode-Neg.json"),
"4.2(3b)",
"4.2(4p)",
False,
[],
script.NA,
[],
),
# NA, Versions not affected
(
{},
read_data(dir, "fabricNode-Neg.json"),
"4.2(5l)",
"6.0(1h)",
False,
[],
script.NA,
[],
),
# NA, Versions not affected
(
{},
read_data(dir, "fabricNode-Neg.json"),
"4.2(1f)",
"5.2(7f)",
False,
[],
script.NA,
[],
),
# Connection failure
(
{},
fabricNodes,
"4.2(3b)",
"4.2(4b)",
True,
[],
script.ERROR,
[
["101", "leaf1", "Simulated exception at connect()"],
["103", "leaf3", "Simulated exception at connect()"],
],
),
# Simulated exception at `show sprom cpu-info` command
(
{},
fabricNodes,
"4.2(3b)",
"4.2(4p)",
False,
{
switch_ip: [
{
"cmd": sprom_cmd,
"output": "",
"exception": Exception("Simulated exception at `vsh` command"),
}
]
for switch_ip in switch_ips
},
script.ERROR,
[
["101", "leaf1", "Simulated exception at `vsh` command"],
["103", "leaf3", "Simulated exception at `vsh` command"],
],
),
# PASS Affected Models, cpu-info is correct (0x0)
(
{},
fabricNodes,
"4.2(3b)",
"4.2(4c)",
False,
{
switch_ip: [
{
"cmd": sprom_cmd,
"output": "\n".join([sprom_cmd, sprom_output_neg]),
"exception": None,
}
]
for switch_ip in switch_ips
},
script.PASS,
[],
),
# FAIL_UF Affected Models, cpu-info is incorrect (0x3)
(
{},
fabricNodes,
"4.2(3b)",
"4.2(4p)",
False,
{
switch_ip: [
{
"cmd": sprom_cmd,
"output": "\n".join([sprom_cmd, sprom_output_pos]),
"exception": None,
}
]
for switch_ip in switch_ips
},
script.FAIL_UF,
[
["101", "leaf1", "0x3 Found!"],
["103", "leaf3", "0x3 Found!"],
],
),
],
)
def test_logic(run_check, mock_icurl, fabric_nodes, cversion, tversion, mock_conn, expected_result, expected_data):
result = run_check(
username="fake_username",
password="fake_password",
fabric_nodes=fabric_nodes,
cversion=script.AciVersion(cversion),
tversion=script.AciVersion(tversion) if tversion else None,
)
assert result.result == expected_result
assert result.data == expected_data