Integrate reviewed spare-aware RAID troubleshooting guidance

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-14 14:41:54 +01:00
3 changed files with 151 additions and 0 deletions
+37
View File
@@ -43,6 +43,10 @@ establish safe recovery.
[server, backup inventory and History](PBS.md#pbs-is-active-but-missing-from-monitoring).
An Active API connection does not establish a visible PBS resource; installing
an agent does not prove API recovery.
- **RAID is reported degraded despite a spare:** compare
[required members, active members and spares](TROUBLESHOOTING.md#raid-array-is-degraded-with-a-spare).
A spare-inclusive total is not the required member count; do not change the
array just to clear a Pulse alert.
- **Pulse itself uses more memory, CPU or disk writes:** use
[memory baseline and growth checks](TROUBLESHOOTING.md#memory-use-keeps-growing)
or [CPU and write-rate checks](TROUBLESHOOTING.md#excessive-cpu-writes-or-database-growth).
@@ -338,6 +342,39 @@ restore, not by overwriting today's history. Follow
### Monitoring Data
#### RAID array is degraded with a spare
For a Linux MD RAID alert, compare the same array's **existing** native status
with Pulse's reading and observation time. In `mdadm` output, **Raid Devices**
is the required member count; **Total Devices** can include a spare. A spare
does not replace an active member until it has joined the array.
| Existing observation | Meaning |
| --- | --- |
| `clean`; Raid Devices **4**, Active **4**, Total **5**, Working **5**, Spare **1**, Failed **0** | Four required members are active, with one spare. The total of five does not by itself mean a member is missing. |
| Raid Devices **4**, Active **3**, with a spare still present | A required member is missing. Do not subtract the spare from the required count or treat it as restored redundancy. |
| Failed devices, a degraded/unavailable state, or recovery/reshape in progress | These are separate health or recovery observations. A spare or the word `clean` alone does not establish that recovery finished. |
| Missing counts, an old reading or different array identities | The comparison is unavailable or unlike-for-like, not a healthy result. |
Pulse **v6.5.0** includes the [spare-count correction reported in #2369](https://github.com/rcourtman/Pulse/issues/2369)
for the clean four-member-plus-spare case, including reports from older
collectors. That does not establish that every RAID alert has this cause or
that an affected installation has recovered. A healthy count also does not
verify data integrity, backup completeness or restore readiness.
For genuine member loss or recovery, use the storage platform's established
recovery procedure and independent monitoring. Keep RAID alerts enabled; do
not remove a spare, fail a member, assemble/rebuild an array, start a scrub,
reboot or change collection settings just to test or silence Pulse. No new
`mdadm` command, Diagnostics run or induced failure is required to file a report.
For help, retain the original alert/time, running Pulse and collector versions
where known, collection path, and the already available state and counts for
one matching array. A missing collector version or native observation can be
reported as unknown. Use consistent private aliases for the host and array;
keep UUIDs, serial numbers, private paths and full command output out of public
reports. Do not repeat an array operation to obtain evidence.
#### Primary IP shows a Podman or Docker bridge address
On a Proxmox VM or LXC, **Primary IP** is Pulse's selected address, not proof of
@@ -43,6 +43,10 @@ establish safe recovery.
[server, backup inventory and History](PBS.md#pbs-is-active-but-missing-from-monitoring).
An Active API connection does not establish a visible PBS resource; installing
an agent does not prove API recovery.
- **RAID is reported degraded despite a spare:** compare
[required members, active members and spares](TROUBLESHOOTING.md#raid-array-is-degraded-with-a-spare).
A spare-inclusive total is not the required member count; do not change the
array just to clear a Pulse alert.
- **Pulse itself uses more memory, CPU or disk writes:** use
[memory baseline and growth checks](TROUBLESHOOTING.md#memory-use-keeps-growing)
or [CPU and write-rate checks](TROUBLESHOOTING.md#excessive-cpu-writes-or-database-growth).
@@ -338,6 +342,39 @@ restore, not by overwriting today's history. Follow
### Monitoring Data
#### RAID array is degraded with a spare
For a Linux MD RAID alert, compare the same array's **existing** native status
with Pulse's reading and observation time. In `mdadm` output, **Raid Devices**
is the required member count; **Total Devices** can include a spare. A spare
does not replace an active member until it has joined the array.
| Existing observation | Meaning |
| --- | --- |
| `clean`; Raid Devices **4**, Active **4**, Total **5**, Working **5**, Spare **1**, Failed **0** | Four required members are active, with one spare. The total of five does not by itself mean a member is missing. |
| Raid Devices **4**, Active **3**, with a spare still present | A required member is missing. Do not subtract the spare from the required count or treat it as restored redundancy. |
| Failed devices, a degraded/unavailable state, or recovery/reshape in progress | These are separate health or recovery observations. A spare or the word `clean` alone does not establish that recovery finished. |
| Missing counts, an old reading or different array identities | The comparison is unavailable or unlike-for-like, not a healthy result. |
Pulse **v6.5.0** includes the [spare-count correction reported in #2369](https://github.com/rcourtman/Pulse/issues/2369)
for the clean four-member-plus-spare case, including reports from older
collectors. That does not establish that every RAID alert has this cause or
that an affected installation has recovered. A healthy count also does not
verify data integrity, backup completeness or restore readiness.
For genuine member loss or recovery, use the storage platform's established
recovery procedure and independent monitoring. Keep RAID alerts enabled; do
not remove a spare, fail a member, assemble/rebuild an array, start a scrub,
reboot or change collection settings just to test or silence Pulse. No new
`mdadm` command, Diagnostics run or induced failure is required to file a report.
For help, retain the original alert/time, running Pulse and collector versions
where known, collection path, and the already available state and counts for
one matching array. A missing collector version or native observation can be
reported as unknown. Use consistent private aliases for the host and array;
keep UUIDs, serial numbers, private paths and full command output out of public
reports. Do not repeat an array operation to obtain evidence.
#### Primary IP shows a Podman or Docker bridge address
On a Proxmox VM or LXC, **Primary IP** is Pulse's selected address, not proof of
@@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""Preserve spare-aware RAID help without implying storage recovery.
Browser verification covers navigation and the shipped renderer separately.
These checks guard the documented distinctions and avoid unsafe recipes.
"""
from pathlib import Path
import unittest
ROOT = Path(__file__).resolve().parents[2]
HEADING = "#### RAID array is degraded with a spare\n"
class RAIDTroubleshootingDocsTest(unittest.TestCase):
def section(self):
guide = (ROOT / "docs/TROUBLESHOOTING.md").read_text(encoding="utf-8")
self.assertEqual(guide.count(HEADING), 1)
return " ".join(guide.split(HEADING, 1)[1].split("\n#### ", 1)[0].split())
def test_required_members_are_not_spare_inclusive_totals(self):
text = self.section()
for distinction in (
"same array's **existing** native status",
"observation time",
"**Raid Devices** is the required member count",
"**Total Devices** can include a spare",
"Active **4**, Total **5**, Working **5**, Spare **1**, Failed **0**",
"Raid Devices **4**, Active **3**",
"Do not subtract the spare from the required count",
"Missing counts, an old reading or different array identities",
):
with self.subTest(distinction=distinction):
self.assertIn(distinction, text)
def test_correction_is_not_every_alert_or_installed_recovery(self):
text = self.section()
for limit in (
"Pulse **v6.5.0**",
"reports from older collectors",
"does not establish that every RAID alert has this cause",
"an affected installation has recovered",
"data integrity, backup completeness or restore readiness",
"Failed devices, a degraded/unavailable state, or recovery/reshape",
):
with self.subTest(limit=limit):
self.assertIn(limit, text)
def test_diagnosis_does_not_require_mutation_or_raw_public_output(self):
text = self.section()
for boundary in (
"independent monitoring",
"Keep RAID alerts enabled",
"do not remove a spare",
"fail a member",
"assemble/rebuild an array",
"start a scrub",
"reboot or change collection settings",
"No new `mdadm` command, Diagnostics run or induced failure",
"reported as unknown",
"consistent private aliases",
"UUIDs, serial numbers, private paths and full command output",
"Do not repeat an array operation",
):
with self.subTest(boundary=boundary):
self.assertIn(boundary, text)
self.assertNotIn("```", text, "existing observations, not a command recipe")
def test_entrypoint_and_shipped_copy_match(self):
source = (ROOT / "docs/TROUBLESHOOTING.md").read_bytes()
self.assertEqual(source, (ROOT / "frontend-modern/public/docs/TROUBLESHOOTING.md").read_bytes())
self.assertIn(b"[required members, active members and spares](TROUBLESHOOTING.md#raid-array-is-degraded-with-a-spare)", source)
if __name__ == "__main__":
unittest.main()