From ce1f77713b350d42d50298163e9e8a18d2b52a6c Mon Sep 17 00:00:00 2001 From: ASRAF KHAN NAZAR Date: Wed, 3 Dec 2025 10:28:09 +0000 Subject: [PATCH 1/5] Added Preupgrade-validation script with HW_Changes_bit_check - CSCvv04251 --- aci-preupgrade-validation-script.py | 108 ++++++++ docs/docs/validations.md | 12 + .../FabricNodes_matching.json | 130 +++++++++ .../test_HW_changes_bit_check.py | 249 ++++++++++++++++++ 4 files changed, 499 insertions(+) create mode 100644 tests/checks/HW_changes_bit_check/FabricNodes_matching.json create mode 100644 tests/checks/HW_changes_bit_check/test_HW_changes_bit_check.py diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index bfca5bb6..59322bbb 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -151,6 +151,7 @@ def __init__(self, hostname): self._term_len = 0 # terminal length for cisco devices self._login = False # set to true at first successful login self._log = None # private variable for tracking logfile state + self.bind_ip = None # optional source IP to bind for SSH (e.g., APIC OoB) def __connected(self): # determine if a connection is already open @@ -207,6 +208,8 @@ def connect(self): "spawning new pexpect connection: ssh %s@%s -p %d" % (self.username, self.hostname, self.port)) no_verify = " -o StrictHostKeyChecking=no -o LogLevel=ERROR -o UserKnownHostsFile=/dev/null" if self.verify: no_verify = "" + if self.bind_ip: + no_verify += " -b %s" % self.bind_ip self.child = pexpect.spawn("ssh %s %s@%s -p %d" % (no_verify, self.username, self.hostname, self.port), searchwindowsize=self.searchwindowsize) elif self.protocol.lower() == "telnet": @@ -5962,6 +5965,110 @@ def configpush_shard_check(tversion, **kwargs): return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) + +@check_wrapper(check_title="HW Changes bit check for specific node model") +def HW_changes_bit_check(tversion, username, password, fabric_nodes, **kwargs): + result = PASS + headers = ["Node", "Model", "HW Changes Bits", "Recommended Action"] + data = [] + recommended_action = "Contact Cisco TAC for Support before upgrade" + doc_url = "https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#HW_changes_bit_check" + + if not tversion: + return Result(result=MANUAL, msg=TVER_MISSING) + if not tversion.newer_than("14.2(4a)"): + return Result(result=NA, msg=VER_NOT_AFFECTED) + + affected_models = {"N9K-C9316D-GX", "N9K-C93600CD-GX"} + + node_found = any( + node["fabricNode"]["attributes"]["model"] in affected_models + for node in fabric_nodes + ) + + if not node_found: + return Result(result=PASS, msg="No switch models found") + + # Discover APIC IP (bind source) + apic_ip = None + + try: + ifconfig_lines = run_cmd("ifconfig | grep 255.255.255.255", splitlines=True) + for line in ifconfig_lines: + m = re.search(r'inet\s+([0-9\.]+)\s+netmask\s+255\.255\.255\.255', line) + if m: + apic_ip = m.group(1) + break + except Exception as e: + return Result(result=ERROR, msg="Failed to get APIC IP: {}".format(e)) + + if not apic_ip: + return Result(result=ERROR, msg="Could not determine APIC IP from ifconfig output") + + hw_bits_re = re.compile(r"HW Changes Bits\s*:\s*(0x[0-9a-fA-F]+)") + has_error = False + + # SSH directly to each switch hostname from APIC, binding source to APIC IP (-b ) + for node in fabric_nodes: + if node["fabricNode"]["attributes"]["model"] not in ["N9K-C9316D-GX", "N9K-C93600CD-GX"]: + continue + attr = node['fabricNode']['attributes'] + node_name = attr['name'] + node_model = attr['model'] + + node_title = "Checking {} ({})...".format(node_name, node_model) + try: + c = Connection(node_name) + c.username = username + c.password = password + c.log = LOG_FILE + c.bind_ip = apic_ip # enables: ssh -b + c.connect() + except Exception as e: + data.append([node_name, node_model, "-", "Connection Error: {}".format(str(e))]) + has_error = True + continue + + try: + # Execute command to check HW Changes Bits + c.cmd("vsh -c 'show sprom cpu-info' | grep \"HW Changes Bits\"") + raw = c.output.strip() + match = hw_bits_re.search(raw) + if not match: + data.append([node_name, node_model, "Parse Error", "Unable to parse HW Changes Bits"]) + has_error = True + else: + hw_bits = match.group(1) + val = int(hw_bits, 16) + if val < 2: + result = FAIL_O + data.append([node_name, node_model, hw_bits, "This switch need Manual Upgrade (CSCvv04251). Contact TAC for Support"]) + except Exception as e: + data.append([ + node_name, + node_model, + '-', + 'Failed to check HW Changes Bits: {}. Please check MANUALLY.'.format(str(e)) + ]) + has_error = True + continue + finally: + try: + c.close() + except Exception: + pass + + if has_error and result == PASS: + result = ERROR + elif not data: + result = PASS + + return Result(result=result, + headers=headers, + data=data, + recommended_action=recommended_action, + doc_url=doc_url) + # ---- Script Execution ---- @@ -6122,6 +6229,7 @@ class CheckManager: standby_sup_sync_check, isis_database_byte_check, configpush_shard_check, + HW_changes_bit_check, ] ssh_checks = [ diff --git a/docs/docs/validations.md b/docs/docs/validations.md index e395564f..f5f133ad 100644 --- a/docs/docs/validations.md +++ b/docs/docs/validations.md @@ -191,6 +191,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: | +[HW_changes_bit_check] | CSCvv04251 | :white_check_mark: | :no_entry_sign: [d1]: #ep-announce-compatibility [d2]: #eventmgr-db-size-defect-susceptibility @@ -220,6 +221,7 @@ Items | Defect | This Script [d26]: #stale-pconsra-object [d27]: #isis-dteps-byte-size [d28]: #policydist-configpushshardcont-crash +[d29]: #HW_changes_bit_check ## General Check Details @@ -2604,6 +2606,15 @@ 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. +### HW changes bit check + +The HW Changes Bit Check is a validation step in the ACI Pre-Upgrade Validation script that verifies the status of `HW Changes Bits` indicators within the ACI fabric before an upgrade. + +If discrepancies are detected, the function raises alerts to prompt administrators to investigate and resolve these issues prior to proceeding with the upgrade. This proactive measure helps prevent potential failures or complications during the upgrade process, ensuring a smoother transition to the new software version. + +For more detailed information, refer to the defect report [CSCvv04251][62], which outlines specific scenarios and resolutions related to hardware change indicators. + + [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 [2]: https://www.cisco.com/c/en/us/support/switches/nexus-9000-series-switches/products-release-notes-list.html @@ -2666,3 +2677,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 diff --git a/tests/checks/HW_changes_bit_check/FabricNodes_matching.json b/tests/checks/HW_changes_bit_check/FabricNodes_matching.json new file mode 100644 index 00000000..ce5902c4 --- /dev/null +++ b/tests/checks/HW_changes_bit_check/FabricNodes_matching.json @@ -0,0 +1,130 @@ +[ + { + "fabricNode": { + "attributes": { + "adSt": "on", + "address": "10.0.0.12", + "annotation": "", + "apicType": "not-applicable", + "childAction": "", + "delayedHeartbeat": "no", + "dn": "topology/pod-2/node-302", + "extMngdBy": "", + "fabricSt": "active", + "id": "302", + "lastStateModTs": "2025-11-19T08:23:42.755-08:00", + "lcOwn": "local", + "mfgTm": "2019-01-27T16:00:00.000-08:00", + "modTs": "2025-11-19T08:23:55.984-08:00", + "model": "N9K-C9316D-GX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf12", + "nameAlias": "", + "nodeType": "unspecified", + "role": "leaf", + "serial": "FDO23051B9A", + "status": "", + "uid": "0", + "userdom": "all", + "vendor": "Cisco Systems, Inc", + "version": "n9000-16.2(0.167)" + } + } + }, + { + "fabricNode": { + "attributes": { + "adSt": "on", + "address": "10.0.0.15", + "annotation": "", + "apicType": "not-applicable", + "childAction": "", + "delayedHeartbeat": "no", + "dn": "topology/pod-3/node-303", + "extMngdBy": "", + "fabricSt": "active", + "id": "303", + "lastStateModTs": "2025-11-19T03:51:11.090-08:00", + "lcOwn": "local", + "mfgTm": "2019-01-13T16:00:00.000-08:00", + "modTs": "2025-11-19T03:51:11.348-08:00", + "model": "N9K-C93600CD-GX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf15", + "nameAlias": "", + "nodeType": "unspecified", + "role": "leaf", + "serial": "FDO2303039T", + "status": "", + "uid": "0", + "userdom": "all", + "vendor": "Cisco Systems, Inc", + "version": "n9000-16.2(0.167)" + } + } + }, + { + "fabricNode": { + "attributes": { + "adSt": "on", + "address": "10.0.0.18", + "annotation": "", + "apicType": "not-applicable", + "childAction": "", + "delayedHeartbeat": "no", + "dn": "topology/pod-2/node-3002", + "extMngdBy": "", + "fabricSt": "active", + "id": "3002", + "lastStateModTs": "2025-11-10T04:19:08.368-08:00", + "lcOwn": "local", + "mfgTm": "2019-03-10T16:00:00.000-08:00", + "modTs": "2025-11-10T04:20:00.475-08:00", + "model": "N9K-C93600CD-GX", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine8", + "nameAlias": "", + "nodeType": "unspecified", + "role": "spine", + "serial": "FDO2311073Q", + "status": "", + "uid": "0", + "userdom": "all", + "vendor": "Cisco Systems, Inc", + "version": "n9000-16.1(4h)" + } + } + }, + { + "fabricNode": { + "attributes": { + "adSt": "on", + "address": "10.0.0.19", + "annotation": "", + "apicType": "not-applicable", + "childAction": "", + "delayedHeartbeat": "no", + "dn": "topology/pod-3/node-3003", + "extMngdBy": "", + "fabricSt": "active", + "id": "3003", + "lastStateModTs": "2025-11-08T20:08:39.435-08:00", + "lcOwn": "local", + "mfgTm": "2019-03-03T16:00:00.000-08:00", + "modTs": "2025-11-08T20:08:56.864-08:00", + "model": "N9K-C93600CD-GX", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine9", + "nameAlias": "", + "nodeType": "unspecified", + "role": "spine", + "serial": "FDO23100QX8", + "status": "", + "uid": "0", + "userdom": "all", + "vendor": "Cisco Systems, Inc", + "version": "n9000-16.2(0.167)" + } + } + } +] \ No newline at end of file diff --git a/tests/checks/HW_changes_bit_check/test_HW_changes_bit_check.py b/tests/checks/HW_changes_bit_check/test_HW_changes_bit_check.py new file mode 100644 index 00000000..e4bd976e --- /dev/null +++ b/tests/checks/HW_changes_bit_check/test_HW_changes_bit_check.py @@ -0,0 +1,249 @@ +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 = "HW_changes_bit_check" + +# Define API endpoints +fabricNode_api = 'fabricNode.json' +fabricNode_api += '?query-target-filter=or(eq(fabricNode.model,"N9K-C9316D-GX"),eq(fabricNode.model,"N9K-C93600CD-GX"))' + + +fabricNode = read_data(dir, "FabricNodes_matching.json") + +node_names = [ + mo["fabricNode"]["attributes"]["name"] + for mo in fabricNode +] + +ifconfig_cmd = "ifconfig | grep 255.255.255.255" + +ifconfig_output = "inet 10.0.0.1 netmask 255.255.255.255 broadcast 0.0.0.0" + +hw_changes_bit_cmd = "vsh -c 'show sprom cpu-info' | grep \"HW Changes Bits\"" + +hw_changes_lower_bit_output = """\ +vsh -c 'show sprom cpu-info' | grep 'HW Changes Bits' + HW Changes Bits : 0x0 +leaf12#""" + +hw_changes_higher_bit_output = """\ +vsh -c 'show sprom cpu-info' | grep 'HW Changes Bits' + HW Changes Bits : 0x3 +leaf12#""" + +hw_changes_no_output = """\ +vsh -c 'show sprom cpu-info' | grep 'HW Changes Bits' + +leaf12#""" + + +@pytest.mark.parametrize( + "fabric_nodes, conn_failure, conn_cmds, cmd_outputs, tversion, expected_result", + [ + # tversion not given + ( + [], + False, + {}, + {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + None, + script.MANUAL, + ), + + # Connection failure + ( + read_data(dir, "FabricNodes_matching.json"), + True, + {}, + {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + "14.2(5a)", + script.ERROR, + ), + + # No Matching model nodes found + ( + [], + False, + {}, + {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + "14.2(5a)", + script.PASS, + ), + + # All nodes with HW Changes Bits = 0x0 + ( + read_data(dir, "FabricNodes_matching.json"), + False, + { + node_name: [ + { + "cmd": hw_changes_bit_cmd, + "output": hw_changes_lower_bit_output, + "exception": None, + } + ] + for node_name in node_names + }, + {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + "14.2(5a)", + script.FAIL_O, + ), + + # All nodes with HW Changes Bits = 0x3 + ( + read_data(dir, "FabricNodes_matching.json"), + False, + { + node_name: [ + { + "cmd": hw_changes_bit_cmd, + "output": hw_changes_higher_bit_output, + "exception": None, + } + ] + for node_name in node_names + }, + {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + "14.2(5a)", + script.PASS, + ), + + # One node fails (0x0), another node pass (0x3) + ( + read_data(dir, "FabricNodes_matching.json"), + False, + { + node_names[0]: [ + { + "cmd": hw_changes_bit_cmd, + "output": hw_changes_lower_bit_output, + "exception": None, + } + ], + node_names[1]: [ + { + "cmd": hw_changes_bit_cmd, + "output": hw_changes_higher_bit_output, + "exception": None, + } + ], + node_names[2]: [ + { + "cmd": hw_changes_bit_cmd, + "output": hw_changes_lower_bit_output, + "exception": None, + } + ], + node_names[3]: [ + { + "cmd": hw_changes_bit_cmd, + "output": hw_changes_higher_bit_output, + "exception": None, + } + ] + }, + {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + "14.2(5a)", + script.FAIL_O, + ), + + # No output from command execution + ( + read_data(dir, "FabricNodes_matching.json"), + False, + { + node_name: [ + { + "cmd": hw_changes_bit_cmd, + "output": hw_changes_no_output, + "exception": None, + } + ] + for node_name in node_names + }, + {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + "14.2(5a)", + script.ERROR, + ), + + # One node connection fails, others succeed + ( + read_data(dir, "FabricNodes_matching.json"), + False, + { + node_names[0]: [ + { + "cmd": hw_changes_bit_cmd, + "output": hw_changes_lower_bit_output, + "exception": None, + } + ], + node_names[1]: [ + { + "cmd": hw_changes_bit_cmd, + "output": "", + "exception": Exception("connection_failure"), + } + ], + node_names[2]: [ + { + "cmd": hw_changes_bit_cmd, + "output": hw_changes_lower_bit_output, + "exception": None, + } + ], + node_names[3]: [ + { + "cmd": hw_changes_bit_cmd, + "output": hw_changes_higher_bit_output, + "exception": None, + } + ] + }, + {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + "14.2(5a)", + script.FAIL_O, + ), + + # Command execution exception + ( + read_data(dir, "FabricNodes_matching.json"), + False, + { + node_name: [ + { + "cmd": hw_changes_bit_cmd, + "output": "", + "exception": Exception("Command execution failed"), + } + ] + for node_name in node_names + }, + {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + "14.2(5a)", + script.ERROR, + ), + + # ERROR when APIC IP cannot be determined (empty ifconfig output) + ( + read_data(dir, "FabricNodes_matching.json"), + False, + {}, + {ifconfig_cmd: {"splitlines": True, "output": ""}}, + "14.2(5a)", + script.ERROR, + ), + ], +) + +def test_logic(run_check, fabric_nodes, mock_conn, mock_run_cmd, tversion, expected_result): + + result = run_check(tversion=script.AciVersion(tversion) if tversion else None, username=None, password=None, fabric_nodes=fabric_nodes) + assert result.result == expected_result \ No newline at end of file From 920fbe53edffb150b7cbeb3c4569ac1f45af51a4 Mon Sep 17 00:00:00 2001 From: ASRAF KHAN NAZAR Date: Mon, 8 Dec 2025 10:06:58 +0000 Subject: [PATCH 2/5] Added proper variable Name --- aci-preupgrade-validation-script.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 59322bbb..fe65f679 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -5995,9 +5995,9 @@ def HW_changes_bit_check(tversion, username, password, fabric_nodes, **kwargs): try: ifconfig_lines = run_cmd("ifconfig | grep 255.255.255.255", splitlines=True) for line in ifconfig_lines: - m = re.search(r'inet\s+([0-9\.]+)\s+netmask\s+255\.255\.255\.255', line) - if m: - apic_ip = m.group(1) + match = re.search(r'inet\s+([0-9\.]+)\s+netmask\s+255\.255\.255\.255', line) + if match: + apic_ip = match.group(1) break except Exception as e: return Result(result=ERROR, msg="Failed to get APIC IP: {}".format(e)) From 4c777101239fc75cfa95e3831414f323862a345a Mon Sep 17 00:00:00 2001 From: ASRAF KHAN NAZAR Date: Tue, 9 Dec 2025 12:22:36 +0000 Subject: [PATCH 3/5] Updated comments for bind_ip and aligned result properly --- aci-preupgrade-validation-script.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index fe65f679..f207088c 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -151,7 +151,7 @@ def __init__(self, hostname): self._term_len = 0 # terminal length for cisco devices self._login = False # set to true at first successful login self._log = None # private variable for tracking logfile state - self.bind_ip = None # optional source IP to bind for SSH (e.g., APIC OoB) + self.bind_ip = None # optional source IP to bind for SSH def __connected(self): # determine if a connection is already open @@ -6063,11 +6063,7 @@ def HW_changes_bit_check(tversion, username, password, fabric_nodes, **kwargs): elif not 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, recommended_action=recommended_action, doc_url=doc_url) # ---- Script Execution ---- From 40ab968a9229df45f9bb0a8fe659d1e59f05e259 Mon Sep 17 00:00:00 2001 From: ASRAF KHAN NAZAR Date: Wed, 17 Dec 2025 04:46:02 +0000 Subject: [PATCH 4/5] Updated fetching APIC IP for bind IP --- aci-preupgrade-validation-script.py | 20 +- .../FabricNodes_matching.json | 206 ++++++++++++++++-- .../test_HW_changes_bit_check.py | 26 +-- 3 files changed, 207 insertions(+), 45 deletions(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index f207088c..cde6c7a8 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -5990,20 +5990,22 @@ def HW_changes_bit_check(tversion, username, password, fabric_nodes, **kwargs): return Result(result=PASS, msg="No switch models found") # Discover APIC IP (bind source) - apic_ip = None - try: - ifconfig_lines = run_cmd("ifconfig | grep 255.255.255.255", splitlines=True) - for line in ifconfig_lines: - match = re.search(r'inet\s+([0-9\.]+)\s+netmask\s+255\.255\.255\.255', line) - if match: - apic_ip = match.group(1) - break + apic_hostname = run_cmd("bash -c \"hostname\"", splitlines=True)[0].strip() + if not apic_hostname: + return Result(result=ERROR, msg="Could not determine APIC hostname") + + apic_ip = next( + (node["fabricNode"]["attributes"].get("address") + for node in fabric_nodes + if node["fabricNode"]["attributes"]["name"] == apic_hostname), + None + ) except Exception as e: return Result(result=ERROR, msg="Failed to get APIC IP: {}".format(e)) if not apic_ip: - return Result(result=ERROR, msg="Could not determine APIC IP from ifconfig output") + return Result(result=ERROR, msg="Could not determine APIC IP from fabricNode attributes") hw_bits_re = re.compile(r"HW Changes Bits\s*:\s*(0x[0-9a-fA-F]+)") has_error = False diff --git a/tests/checks/HW_changes_bit_check/FabricNodes_matching.json b/tests/checks/HW_changes_bit_check/FabricNodes_matching.json index ce5902c4..ef4d3db3 100644 --- a/tests/checks/HW_changes_bit_check/FabricNodes_matching.json +++ b/tests/checks/HW_changes_bit_check/FabricNodes_matching.json @@ -3,7 +3,39 @@ "fabricNode": { "attributes": { "adSt": "on", - "address": "10.0.0.12", + "address": "12.3.136.65", + "annotation": "", + "apicType": "not-applicable", + "childAction": "", + "delayedHeartbeat": "no", + "dn": "topology/pod-3/node-303", + "extMngdBy": "", + "fabricSt": "active", + "id": "303", + "lastStateModTs": "2025-12-11T21:35:56.699-08:00", + "lcOwn": "local", + "mfgTm": "2019-01-13T16:00:00.000-08:00", + "modTs": "2025-12-11T21:35:56.754-08:00", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "ifav42-leaf15", + "nameAlias": "", + "nodeType": "unspecified", + "role": "leaf", + "serial": "FDO2303039T", + "status": "", + "uid": "0", + "userdom": "all", + "vendor": "Cisco Systems, Inc", + "version": "n9000-16.2(0.167)" + } + } + }, + { + "fabricNode": { + "attributes": { + "adSt": "on", + "address": "11.3.208.65", "annotation": "", "apicType": "not-applicable", "childAction": "", @@ -12,13 +44,13 @@ "extMngdBy": "", "fabricSt": "active", "id": "302", - "lastStateModTs": "2025-11-19T08:23:42.755-08:00", + "lastStateModTs": "2025-12-11T18:34:54.381-08:00", "lcOwn": "local", "mfgTm": "2019-01-27T16:00:00.000-08:00", - "modTs": "2025-11-19T08:23:55.984-08:00", - "model": "N9K-C9316D-GX", + "modTs": "2025-12-11T18:34:54.436-08:00", + "model": "N9K-C9336C-FX2", "monPolDn": "uni/fabric/monfab-default", - "name": "leaf12", + "name": "ifav42-leaf12", "nameAlias": "", "nodeType": "unspecified", "role": "leaf", @@ -35,26 +67,26 @@ "fabricNode": { "attributes": { "adSt": "on", - "address": "10.0.0.15", + "address": "10.0.224.65", "annotation": "", "apicType": "not-applicable", "childAction": "", "delayedHeartbeat": "no", - "dn": "topology/pod-3/node-303", + "dn": "topology/pod-1/node-3001", "extMngdBy": "", "fabricSt": "active", - "id": "303", - "lastStateModTs": "2025-11-19T03:51:11.090-08:00", + "id": "3001", + "lastStateModTs": "2025-12-10T10:29:29.923-08:00", "lcOwn": "local", - "mfgTm": "2019-01-13T16:00:00.000-08:00", - "modTs": "2025-11-19T03:51:11.348-08:00", - "model": "N9K-C93600CD-GX", + "mfgTm": "2023-03-12T16:00:00.000-08:00", + "modTs": "2025-12-10T10:29:29.978-08:00", + "model": "N9K-C9316D-GX", "monPolDn": "uni/fabric/monfab-default", - "name": "leaf15", + "name": "ifav42-spine6", "nameAlias": "", "nodeType": "unspecified", - "role": "leaf", - "serial": "FDO2303039T", + "role": "spine", + "serial": "FDO27111UE3", "status": "", "uid": "0", "userdom": "all", @@ -67,7 +99,7 @@ "fabricNode": { "attributes": { "adSt": "on", - "address": "10.0.0.18", + "address": "11.3.208.64", "annotation": "", "apicType": "not-applicable", "childAction": "", @@ -76,13 +108,13 @@ "extMngdBy": "", "fabricSt": "active", "id": "3002", - "lastStateModTs": "2025-11-10T04:19:08.368-08:00", + "lastStateModTs": "2025-12-10T10:29:29.733-08:00", "lcOwn": "local", "mfgTm": "2019-03-10T16:00:00.000-08:00", - "modTs": "2025-11-10T04:20:00.475-08:00", + "modTs": "2025-12-10T10:29:29.787-08:00", "model": "N9K-C93600CD-GX", "monPolDn": "uni/fabric/monfab-default", - "name": "spine8", + "name": "ifav42-spine8", "nameAlias": "", "nodeType": "unspecified", "role": "spine", @@ -99,7 +131,39 @@ "fabricNode": { "attributes": { "adSt": "on", - "address": "10.0.0.19", + "address": "10.0.224.64", + "annotation": "", + "apicType": "not-applicable", + "childAction": "", + "delayedHeartbeat": "no", + "dn": "topology/pod-1/node-301", + "extMngdBy": "", + "fabricSt": "active", + "id": "301", + "lastStateModTs": "2025-12-10T10:29:29.194-08:00", + "lcOwn": "local", + "mfgTm": "2019-01-06T16:00:00.000-08:00", + "modTs": "2025-12-10T10:29:29.246-08:00", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "ifav42-leaf11", + "nameAlias": "", + "nodeType": "unspecified", + "role": "leaf", + "serial": "FDO23020NL0", + "status": "", + "uid": "0", + "userdom": "all", + "vendor": "Cisco Systems, Inc", + "version": "n9000-16.2(0.167)" + } + } + }, + { + "fabricNode": { + "attributes": { + "adSt": "on", + "address": "12.3.136.64", "annotation": "", "apicType": "not-applicable", "childAction": "", @@ -108,13 +172,13 @@ "extMngdBy": "", "fabricSt": "active", "id": "3003", - "lastStateModTs": "2025-11-08T20:08:39.435-08:00", + "lastStateModTs": "2025-12-10T10:29:29.194-08:00", "lcOwn": "local", "mfgTm": "2019-03-03T16:00:00.000-08:00", - "modTs": "2025-11-08T20:08:56.864-08:00", + "modTs": "2025-12-10T10:29:29.246-08:00", "model": "N9K-C93600CD-GX", "monPolDn": "uni/fabric/monfab-default", - "name": "spine9", + "name": "ifav42-spine9", "nameAlias": "", "nodeType": "unspecified", "role": "spine", @@ -126,5 +190,101 @@ "version": "n9000-16.2(0.167)" } } + }, + { + "fabricNode": { + "attributes": { + "adSt": "on", + "address": "10.0.0.3", + "annotation": "", + "apicType": "apic", + "childAction": "", + "delayedHeartbeat": "no", + "dn": "topology/pod-3/node-3", + "extMngdBy": "", + "fabricSt": "commissioned", + "id": "3", + "lastStateModTs": "2025-12-09T23:49:02.749-08:00", + "lcOwn": "local", + "mfgTm": "1969-12-31T16:00:00.000-08:00", + "modTs": "2025-12-09T23:49:03.039-08:00", + "model": "APIC-SERVER-M4T", + "monPolDn": "uni/fabric/monfab-default", + "name": "ifav42-ifc7", + "nameAlias": "", + "nodeType": "unspecified", + "role": "controller", + "serial": "WZP27160Q34", + "status": "", + "uid": "0", + "userdom": "all", + "vendor": "Cisco Systems, Inc", + "version": "6.2(0.198)" + } + } + }, + { + "fabricNode": { + "attributes": { + "adSt": "on", + "address": "10.0.0.2", + "annotation": "", + "apicType": "apic", + "childAction": "", + "delayedHeartbeat": "no", + "dn": "topology/pod-2/node-2", + "extMngdBy": "", + "fabricSt": "commissioned", + "id": "2", + "lastStateModTs": "2025-12-09T23:45:18.617-08:00", + "lcOwn": "local", + "mfgTm": "1969-12-31T16:00:00.000-08:00", + "modTs": "2025-12-09T23:45:59.424-08:00", + "model": "APIC-SERVER-M4T", + "monPolDn": "uni/fabric/monfab-default", + "name": "ifav42-ifc6", + "nameAlias": "", + "nodeType": "unspecified", + "role": "controller", + "serial": "WZP27150MYQ", + "status": "", + "uid": "0", + "userdom": "all", + "vendor": "Cisco Systems, Inc", + "version": "6.2(0.198)" + } + } + }, + { + "fabricNode": { + "attributes": { + "adSt": "on", + "address": "10.0.0.1", + "annotation": "", + "apicType": "apic", + "childAction": "", + "delayedHeartbeat": "no", + "dn": "topology/pod-1/node-1", + "extMngdBy": "", + "fabricSt": "commissioned", + "id": "1", + "lastStateModTs": "2025-12-09T23:37:37.436-08:00", + "lcOwn": "local", + "mfgTm": "1969-12-31T16:00:00.000-08:00", + "modTs": "2025-12-09T23:38:59.204-08:00", + "model": "APIC-SERVER-M2", + "monPolDn": "uni/fabric/monfab-default", + "name": "ifav42-ifc5", + "nameAlias": "", + "nodeType": "unspecified", + "role": "controller", + "serial": "FCH2313V0KV", + "status": "", + "uid": "0", + "userdom": "all", + "vendor": "Cisco Systems, Inc", + "version": "6.2(0.198)" + } + } } ] \ No newline at end of file diff --git a/tests/checks/HW_changes_bit_check/test_HW_changes_bit_check.py b/tests/checks/HW_changes_bit_check/test_HW_changes_bit_check.py index e4bd976e..fca2578e 100644 --- a/tests/checks/HW_changes_bit_check/test_HW_changes_bit_check.py +++ b/tests/checks/HW_changes_bit_check/test_HW_changes_bit_check.py @@ -22,9 +22,9 @@ for mo in fabricNode ] -ifconfig_cmd = "ifconfig | grep 255.255.255.255" +hostname_cmd = "bash -c \"hostname\"" -ifconfig_output = "inet 10.0.0.1 netmask 255.255.255.255 broadcast 0.0.0.0" +hostname_output = "ifav42-ifc5" hw_changes_bit_cmd = "vsh -c 'show sprom cpu-info' | grep \"HW Changes Bits\"" @@ -52,7 +52,7 @@ [], False, {}, - {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + {hostname_cmd: {"splitlines": True, "output": hostname_output}}, None, script.MANUAL, ), @@ -62,7 +62,7 @@ read_data(dir, "FabricNodes_matching.json"), True, {}, - {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + {hostname_cmd: {"splitlines": True, "output": hostname_output}}, "14.2(5a)", script.ERROR, ), @@ -72,7 +72,7 @@ [], False, {}, - {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + {hostname_cmd: {"splitlines": True, "output": hostname_output}}, "14.2(5a)", script.PASS, ), @@ -91,7 +91,7 @@ ] for node_name in node_names }, - {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + {hostname_cmd: {"splitlines": True, "output": hostname_output}}, "14.2(5a)", script.FAIL_O, ), @@ -110,7 +110,7 @@ ] for node_name in node_names }, - {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + {hostname_cmd: {"splitlines": True, "output": hostname_output}}, "14.2(5a)", script.PASS, ), @@ -149,7 +149,7 @@ } ] }, - {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + {hostname_cmd: {"splitlines": True, "output": hostname_output}}, "14.2(5a)", script.FAIL_O, ), @@ -168,7 +168,7 @@ ] for node_name in node_names }, - {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + {hostname_cmd: {"splitlines": True, "output": hostname_output}}, "14.2(5a)", script.ERROR, ), @@ -207,7 +207,7 @@ } ] }, - {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + {hostname_cmd: {"splitlines": True, "output": hostname_output}}, "14.2(5a)", script.FAIL_O, ), @@ -226,17 +226,17 @@ ] for node_name in node_names }, - {ifconfig_cmd: {"splitlines": True, "output": ifconfig_output}}, + {hostname_cmd: {"splitlines": True, "output": hostname_output}}, "14.2(5a)", script.ERROR, ), - # ERROR when APIC IP cannot be determined (empty ifconfig output) + # ERROR when APIC hostname cannot be determined (empty ifconfig output) ( read_data(dir, "FabricNodes_matching.json"), False, {}, - {ifconfig_cmd: {"splitlines": True, "output": ""}}, + {hostname_cmd: {"splitlines": True, "output": ""}}, "14.2(5a)", script.ERROR, ), From 56a50066433de145854c238393d31a49e0c3c1a4 Mon Sep 17 00:00:00 2001 From: ASRAF KHAN NAZAR Date: Fri, 19 Dec 2025 06:53:54 +0000 Subject: [PATCH 5/5] Added takuya changes on get_vpc_nodes function --- aci-preupgrade-validation-script.py | 12 +++++-- tests/test_get_vpc_node.py | 49 +++++++++-------------------- 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index cde6c7a8..cc39b589 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -38,7 +38,7 @@ import os import re -SCRIPT_VERSION = "v4.0.0" +SCRIPT_VERSION = "v4.0.1" DEFAULT_TIMEOUT = 600 # sec # result constants DONE = 'DONE' @@ -1767,7 +1767,15 @@ def get_vpc_nodes(): """ Returns list of VPC Node IDs; ['101', '102', etc...] """ prints("Collecting VPC Node IDs...", end='') vpc_nodes = [] - prot_pols = icurl('class', 'fabricNodePEp.json') + try: + prot_pols = icurl('class', 'fabricNodePEp.json') + except Exception as e: + # CSCws30568: expected for fabricNodePEp to return non-zero totalCount + # incorrectly for an empty response. + if str(e).startswith("API response empty with totalCount:"): + prot_pols = [] + else: + raise e for vpc_node in prot_pols: vpc_nodes.append(vpc_node['fabricNodePEp']['attributes']['id']) vpc_nodes.sort() diff --git a/tests/test_get_vpc_node.py b/tests/test_get_vpc_node.py index 956377cc..613e4587 100644 --- a/tests/test_get_vpc_node.py +++ b/tests/test_get_vpc_node.py @@ -7,38 +7,10 @@ fabricNodePEps = "fabricNodePEp.json" data = [ - { - "fabricNodePEp": { - "attributes": { - "dn": "uni/fabric/protpol/expgep-101-103/nodepep-101", - "id": "101" - } - } - }, - { - "fabricNodePEp": { - "attributes": { - "dn": "uni/fabric/protpol/expgep-204-206/nodepep-206", - "id": "206" - } - } - }, - { - "fabricNodePEp": { - "attributes": { - "dn": "uni/fabric/protpol/expgep-101-103/nodepep-103", - "id": "103" - } - } - }, - { - "fabricNodePEp": { - "attributes": { - "dn": "uni/fabric/protpol/expgep-204-206/nodepep-204", - "id": "204" - } - } - } + {"fabricNodePEp": {"attributes": {"dn": "uni/fabric/protpol/expgep-101-103/nodepep-101", "id": "101"}}}, + {"fabricNodePEp": {"attributes": {"dn": "uni/fabric/protpol/expgep-204-206/nodepep-206", "id": "206"}}}, + {"fabricNodePEp": {"attributes": {"dn": "uni/fabric/protpol/expgep-101-103/nodepep-103", "id": "103"}}}, + {"fabricNodePEp": {"attributes": {"dn": "uni/fabric/protpol/expgep-204-206/nodepep-204", "id": "204"}}}, ] data2 = [ @@ -68,8 +40,15 @@ {fabricNodePEps: data2}, ["101", "102", "103", "104", "105", "106"], "Collecting VPC Node IDs...101, 102, 103, 104, ... (and 2 more)\n\n", - ) - ] + ), + # CSCws30568: expected for fabricNodePEp to return non-zero totalCount + # incorrectly for an empty response. + ( + {fabricNodePEps: {"totalCount": "8", "imdata": []}}, + [], + "Collecting VPC Node IDs...\n\n", + ), + ], ) def test_get_vpc_nodes(capsys, mock_icurl, expected_result, expected_stdout): vpc_nodes = script.get_vpc_nodes() @@ -77,4 +56,4 @@ def test_get_vpc_nodes(capsys, mock_icurl, expected_result, expected_stdout): captured = capsys.readouterr() print(captured.out) - assert captured.out == expected_stdout + assert captured.out == expected_stdout \ No newline at end of file