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
54 changes: 54 additions & 0 deletions aci-preupgrade-validation-script.py
Original file line number Diff line number Diff line change
Expand Up @@ -6404,6 +6404,59 @@ def apic_storage_inode_check(**kwargs):
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="Switch RTC Battery Voltage (F2421 equipment-diags-failed)")
def rtc_battery_voltage_low_check(**kwargs):
result = FAIL_UF

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Blocking: Please classify this as FAIL_O rather than FAIL_UF and update the active/malformed-fault test expectations accordingly. Issue #351 and the new validation documentation state that a low RTC battery can reset the switch clock during an upgrade or power cycle, causing certificate validation to fail and preventing the switch from rejoining the fabric. That is an outage risk, so FAIL - OUTAGE WARNING!! is the appropriate result category.

headers = ['Fault', 'Pod', 'Node', 'Supervisor', 'Severity', 'Lifecycle']
data = []
unformatted_headers = ['Fault', 'Fault DN', 'Description', 'Severity', 'Lifecycle']
unformatted_data = []
recommended_action = 'Contact Cisco TAC to replace the RTC battery before upgrading or power cycling the affected switch'
doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#switch-rtc-battery-voltage'
dn_regex = node_regex + r'/.+/supslot-(?P<slot>\d+)/sup\]/fault-F2421$'
fault_reason = 'The RTC battery voltage is low'
fault_api = 'faultInst.json?query-target-filter=and(eq(faultInst.code,"F2421"),wcard(faultInst.descr,"reason:The RTC battery voltage is low"))'

faultInsts = icurl('class', fault_api)
for faultInst in faultInsts:
attributes = faultInst['faultInst']['attributes']
lc = attributes['lc']
if lc not in ['raised', 'soaking']:
continue
description = attributes['descr']
if 'reason:' not in description or description.split('reason:', 1)[1] != fault_reason:
continue
dn = re.search(dn_regex, attributes['dn'])
if dn:
data.append([
attributes['code'],
dn.group('pod'),
dn.group('node'),
dn.group('slot'),
attributes['severity'],
lc,
])
else:
unformatted_data.append([
attributes['code'],
attributes['dn'],
description,
attributes['severity'],
lc,
])
if not data and not unformatted_data:
result = PASS
return Result(
result=result,
headers=headers,
data=data,
unformatted_headers=unformatted_headers,
unformatted_data=unformatted_data,
recommended_action=recommended_action,
doc_url=doc_url,
)


# Connection Based Check
@check_wrapper(check_title="Multi-Pod Modular Spine Bootscript File")
def multipod_modular_spine_bootscript_check(tversion, fabric_nodes, username, password, **kwargs):
Expand Down Expand Up @@ -6816,6 +6869,7 @@ class CheckManager:
equipment_disk_limits_exceeded,
apic_vmm_inventory_sync_faults_check,
apic_storage_inode_check,
rtc_battery_voltage_low_check,

# Configurations
vpc_paired_switches_check,
Expand Down
13 changes: 10 additions & 3 deletions docs/docs/validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Items | Faults | This Script
[Equipment Disk Limits][f20] | F1820: 80% -minor<br>F1821: -major<br>F1822: -critical | :white_check_mark: | :no_entry_sign:
[VMM Inventory Partially Synced][f21] | F0132: comp-ctrlr-operational-issues | :white_check_mark: | :no_entry_sign:
[APIC Storage Inode Usage][f22] | F4388: 75% - 85% -warning<br>F4389: 85% - 90% -major<br>F4390: 90% or more -critical | :white_check_mark: | :no_entry_sign:
[Switch RTC Battery Voltage][f23] | F2421: RTC battery voltage is low | :white_check_mark: | :no_entry_sign:

[f1]: #apic-disk-space-usage
[f2]: #standby-apic-disk-space-usage
Expand All @@ -110,6 +111,7 @@ Items | Faults | This Script
[f20]: #equipment-disk-limits
[f21]: #vmm-inventory-partially-synced
[f22]: #apic-storage-inode-usage
[f23]: #switch-rtc-battery-voltage

### Configuration Checks

Expand Down Expand Up @@ -1638,8 +1640,14 @@ To recover from this fault, try the following action
subject : equipment-full
type : operational
```



### Switch RTC Battery Voltage

This check detects active F2421 equipment diagnostic faults whose reason is `The RTC battery voltage is low`. The RTC battery maintains the switch system clock while the switch is powered off. If the battery voltage is low, a power cycle during an upgrade can reset the clock and prevent certificate validation, which can stop the switch from rejoining the fabric.

The RTC battery should be replaced before upgrading or power cycling an affected switch. Contact Cisco TAC to coordinate replacement and confirm that the fault has cleared.


## Configuration Check Details

### VPC-paired Leaf switches
Expand Down Expand Up @@ -2923,4 +2931,3 @@ Contact Cisco TAC for next steps. For more details, refer to the workaround in [
[74]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwm42741
[75]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwt69100
[76]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwt38698

37 changes: 37 additions & 0 deletions tests/checks/rtc_battery_voltage_low_check/f2421.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"totalCount": "1",
"imdata": [
{
"faultInst": {
"attributes": {
"ack": "no",
"alert": "no",
"cause": "equipment-diags-failed",
"changeSet": "operStQual:The RTC battery voltage is low",
"childAction": "",
"code": "F2421",
"created": "2026-02-09T16:27:10.975+00:00",
"delegated": "no",
"descr": "Diagnostics test failed. reason:The RTC battery voltage is low",
"dn": "topology/pod-1/node-122/sys/diag/rule-rtc-test-trig-forever/subj-[topology/pod-1/node-122/sys/ch/supslot-1/sup]/fault-F2421",
"domain": "infra",
"extMngdBy": "",
"highestSeverity": "minor",
"lastTransition": "2026-02-09T16:27:10.975+00:00",
"lc": "raised",
"modTs": "never",
"occur": "1",
"origSeverity": "minor",
"prevSeverity": "cleared",
"rn": "fault-F2421",
"rule": "diag-rtctest-failed",
"severity": "minor",
"status": "",
"subject": "equipment-diags-failed",
"title": "",
"type": "operational"
}
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import copy
import importlib
import logging
import os

import pytest

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 = "rtc_battery_voltage_low_check"
faultInst_api = (
'faultInst.json?query-target-filter=and(eq(faultInst.code,"F2421"),'
'wcard(faultInst.descr,"reason:The RTC battery voltage is low"))'
)
active_faults = read_data(dir, "f2421.json")["imdata"]
retaining_faults = copy.deepcopy(active_faults)
retaining_faults[0]["faultInst"]["attributes"]["lc"] = "retaining"
malformed_faults = copy.deepcopy(active_faults)
malformed_faults[0]["faultInst"]["attributes"]["dn"] = (
"topology/pod-1/node-122/sys/diag/rule-rtc-test-trig-forever/"
"subj-[topology/pod-1/node-122/sys/ch/supslot-1/sup-bad]/fault-F2421"
)
non_exact_reason_faults = []
for reason in [
"The RTC battery voltage is low or unavailable",
"Warning: The RTC battery voltage is low",
"Power-on self-test failed",
]:
fault = copy.deepcopy(active_faults[0])
fault["faultInst"]["attributes"]["descr"] = (
"Diagnostics test failed. reason:" + reason
)
non_exact_reason_faults.append(fault)


@pytest.mark.parametrize(
"icurl_outputs, expected_result, expected_data, expected_unformatted_data",
[
(
{faultInst_api: active_faults},
script.FAIL_UF,
[["F2421", "1", "122", "1", "minor", "raised"]],
[],
),
({faultInst_api: []}, script.PASS, [], []),
({faultInst_api: retaining_faults}, script.PASS, [], []),
({faultInst_api: non_exact_reason_faults}, script.PASS, [], []),
(
{faultInst_api: active_faults + malformed_faults},
script.FAIL_UF,
[["F2421", "1", "122", "1", "minor", "raised"]],
[[
"F2421",
"topology/pod-1/node-122/sys/diag/rule-rtc-test-trig-forever/"
"subj-[topology/pod-1/node-122/sys/ch/supslot-1/sup-bad]/fault-F2421",
"Diagnostics test failed. reason:The RTC battery voltage is low",
"minor",
"raised",
]],
),
],
)
def test_logic(
run_check,
mock_icurl,
expected_result,
expected_data,
expected_unformatted_data,
):
result = run_check()
assert result.result == expected_result
assert result.data == expected_data
assert result.unformatted_data == expected_unformatted_data
Loading