mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-25 13:10:03 +00:00
Explain Unraid empty slots without hiding missing members
Distinguish configured placeholders from assigned failures, explain online parity counts, and preserve existing evidence without array changes or another retest. Contract-Neutral: Documentation-only guidance for existing Unraid collection and alert semantics; no runtime, wire, access or trust contract changes. Change-source: pulse-maintainer
This commit is contained in:
@@ -51,6 +51,10 @@ establish safe recovery.
|
||||
[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.
|
||||
- **Unraid reports missing disks in empty slots:** distinguish
|
||||
[unassigned slots, missing members and parity counts](TROUBLESHOOTING.md#unraid-reports-missing-disks-in-empty-slots).
|
||||
Unraid parity is not Linux MD RAID; do not rebuild or change disk assignments
|
||||
just to make Pulse's count agree.
|
||||
- **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).
|
||||
@@ -415,6 +419,42 @@ restore, not by overwriting today's history. Follow
|
||||
|
||||
### Monitoring Data
|
||||
|
||||
#### Unraid reports missing disks in empty slots
|
||||
|
||||
Compare the same host's **existing** Unraid status with Pulse's disk inventory,
|
||||
storage-topology alert and observation time. Keep the Pulse server and Unraid
|
||||
Unified Agent versions separate: updating the server does not establish that
|
||||
the agent updated. No new command, Diagnostics run or array operation is needed
|
||||
to report a disagreement.
|
||||
|
||||
| Existing observation | Meaning |
|
||||
| --- | --- |
|
||||
| `DISK_NP`, no device or size, and only `fsType=auto` or a transport-only identity such as `ata-_` | A configured-but-unassigned slot, not evidence of a missing assigned disk. A slot name such as `disk6` or `parity2` alone is not assignment evidence. |
|
||||
| `DISK_NP_MISSING`, even without a device or identity | Unraid explicitly reports an assigned-but-absent member. Do not discard it as an empty slot. |
|
||||
| A missing/disabled slot with a real device, model/serial, filesystem or size | Assignment evidence remains; do not dismiss the failure because other slots are empty. `auto` alone is not a real filesystem. |
|
||||
| Single online parity disk and five data disks | Pulse's `numProtected` counts online parity devices: **1**, not five protected data disks. That count alone does not prove complete protection or healthy disks. |
|
||||
| No matching native observation, an old reading or an unknown version | The comparison is unavailable, not a healthy result. `STARTED` alone does not establish array health. |
|
||||
|
||||
For the healthy partially populated topology in [#1788](https://github.com/rcourtman/Pulse/issues/1788),
|
||||
seventeen empty data slots wrongly contributed to `numMissing`. The final
|
||||
correction treats `fsType=auto` as a placeholder at agent collection, server
|
||||
ingestion and storage-health assessment. **Pulse v6.5.0 contains this correction
|
||||
in the server and Unified Agent**; an earlier preview retest failed before
|
||||
this final correction. This does not establish that a current alert has the
|
||||
same cause or that the reporter's installation has recovered.
|
||||
|
||||
If existing native status shows no missing assigned members but Pulse disagrees,
|
||||
preserve the mismatch rather than suppressing the check or changing the array.
|
||||
Do not unassign a disk, rebuild, start a parity check, restart or re-enrol the
|
||||
agent, or enable Debug just to diagnose the alert. A genuine missing, disabled,
|
||||
invalid or unavailable parity member remains a separate safety concern.
|
||||
|
||||
For a report, share only already available versions, the original time, relevant
|
||||
status/counts and redacted alert text. Keep disk serials, paths and raw agent
|
||||
payloads private; use consistent aliases and leave unknowns unknown. Follow
|
||||
[Getting Help](TROUBLESHOOTING.md#-getting-help), without repeating a failed
|
||||
update or array operation to supply evidence.
|
||||
|
||||
#### RAID array is degraded with a spare
|
||||
|
||||
For a Linux MD RAID alert, compare the same array's **existing** native status
|
||||
|
||||
@@ -51,6 +51,10 @@ establish safe recovery.
|
||||
[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.
|
||||
- **Unraid reports missing disks in empty slots:** distinguish
|
||||
[unassigned slots, missing members and parity counts](TROUBLESHOOTING.md#unraid-reports-missing-disks-in-empty-slots).
|
||||
Unraid parity is not Linux MD RAID; do not rebuild or change disk assignments
|
||||
just to make Pulse's count agree.
|
||||
- **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).
|
||||
@@ -415,6 +419,42 @@ restore, not by overwriting today's history. Follow
|
||||
|
||||
### Monitoring Data
|
||||
|
||||
#### Unraid reports missing disks in empty slots
|
||||
|
||||
Compare the same host's **existing** Unraid status with Pulse's disk inventory,
|
||||
storage-topology alert and observation time. Keep the Pulse server and Unraid
|
||||
Unified Agent versions separate: updating the server does not establish that
|
||||
the agent updated. No new command, Diagnostics run or array operation is needed
|
||||
to report a disagreement.
|
||||
|
||||
| Existing observation | Meaning |
|
||||
| --- | --- |
|
||||
| `DISK_NP`, no device or size, and only `fsType=auto` or a transport-only identity such as `ata-_` | A configured-but-unassigned slot, not evidence of a missing assigned disk. A slot name such as `disk6` or `parity2` alone is not assignment evidence. |
|
||||
| `DISK_NP_MISSING`, even without a device or identity | Unraid explicitly reports an assigned-but-absent member. Do not discard it as an empty slot. |
|
||||
| A missing/disabled slot with a real device, model/serial, filesystem or size | Assignment evidence remains; do not dismiss the failure because other slots are empty. `auto` alone is not a real filesystem. |
|
||||
| Single online parity disk and five data disks | Pulse's `numProtected` counts online parity devices: **1**, not five protected data disks. That count alone does not prove complete protection or healthy disks. |
|
||||
| No matching native observation, an old reading or an unknown version | The comparison is unavailable, not a healthy result. `STARTED` alone does not establish array health. |
|
||||
|
||||
For the healthy partially populated topology in [#1788](https://github.com/rcourtman/Pulse/issues/1788),
|
||||
seventeen empty data slots wrongly contributed to `numMissing`. The final
|
||||
correction treats `fsType=auto` as a placeholder at agent collection, server
|
||||
ingestion and storage-health assessment. **Pulse v6.5.0 contains this correction
|
||||
in the server and Unified Agent**; an earlier preview retest failed before
|
||||
this final correction. This does not establish that a current alert has the
|
||||
same cause or that the reporter's installation has recovered.
|
||||
|
||||
If existing native status shows no missing assigned members but Pulse disagrees,
|
||||
preserve the mismatch rather than suppressing the check or changing the array.
|
||||
Do not unassign a disk, rebuild, start a parity check, restart or re-enrol the
|
||||
agent, or enable Debug just to diagnose the alert. A genuine missing, disabled,
|
||||
invalid or unavailable parity member remains a separate safety concern.
|
||||
|
||||
For a report, share only already available versions, the original time, relevant
|
||||
status/counts and redacted alert text. Keep disk serials, paths and raw agent
|
||||
payloads private; use consistent aliases and leave unknowns unknown. Follow
|
||||
[Getting Help](TROUBLESHOOTING.md#-getting-help), without repeating a failed
|
||||
update or array operation to supply evidence.
|
||||
|
||||
#### RAID array is degraded with a spare
|
||||
|
||||
For a Linux MD RAID alert, compare the same array's **existing** native status
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep Unraid help distinct from MD spares and unsafe array-repair advice.
|
||||
|
||||
These checks guard the instructions and shipped mirror, not an installed array's
|
||||
recovery. Source-bound collector/assessment fixtures and browser proof are retained
|
||||
separately when the guidance changes.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
HEADING = "#### Unraid reports missing disks in empty slots\n"
|
||||
ANCHOR = "TROUBLESHOOTING.md#unraid-reports-missing-disks-in-empty-slots"
|
||||
|
||||
|
||||
def section(text):
|
||||
return " ".join(text.split(HEADING, 1)[1].split("\n#### ", 1)[0].split())
|
||||
|
||||
|
||||
class UnraidTroubleshootingDocsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.source = (ROOT / "docs/TROUBLESHOOTING.md").read_text()
|
||||
self.guide = section(self.source)
|
||||
|
||||
def test_existing_symptom_entry_reaches_the_distinct_unraid_workflow(self):
|
||||
self.assertIn(ANCHOR, self.source.split("### Monitoring Data", 1)[0])
|
||||
self.assertIn("Unraid parity is not Linux MD RAID", self.source)
|
||||
self.assertIn("do not rebuild or change disk assignments", self.source)
|
||||
|
||||
def test_empty_slot_evidence_does_not_hide_assigned_missing_members(self):
|
||||
for phrase in (
|
||||
"`DISK_NP`, no device or size", "`fsType=auto`",
|
||||
"transport-only identity such as `ata-_`", "not assignment evidence",
|
||||
"`DISK_NP_MISSING`, even without a device or identity",
|
||||
"assigned-but-absent member", "Do not discard it as an empty slot",
|
||||
"real device, model/serial, filesystem or size",
|
||||
"Assignment evidence remains", "`auto` alone is not a real filesystem",
|
||||
):
|
||||
with self.subTest(phrase=phrase):
|
||||
self.assertIn(phrase, self.guide)
|
||||
|
||||
def test_parity_and_unknowns_are_not_misrepresented_as_recovery(self):
|
||||
for phrase in (
|
||||
"Single online parity disk and five data disks",
|
||||
"`numProtected` counts online parity devices: **1**",
|
||||
"does not prove complete protection", "not a healthy result",
|
||||
"`STARTED` alone does not establish array health",
|
||||
"Pulse server and Unraid Unified Agent versions separate",
|
||||
"updating the server does not establish that the agent updated",
|
||||
"agent collection, server ingestion and storage-health assessment",
|
||||
"an earlier preview retest failed", "installation has recovered",
|
||||
):
|
||||
with self.subTest(phrase=phrase):
|
||||
self.assertIn(phrase, self.guide)
|
||||
|
||||
def test_no_replay_array_change_or_private_payload_is_requested(self):
|
||||
self.assertNotIn("```", self.source.split(HEADING, 1)[1].split("\n#### ", 1)[0])
|
||||
for phrase in (
|
||||
"existing", "No new command, Diagnostics run or array operation",
|
||||
"rather than suppressing the check or changing the array",
|
||||
"Do not unassign a disk, rebuild, start a parity check",
|
||||
"restart or re-enrol the agent", "enable Debug just to diagnose",
|
||||
"genuine missing, disabled, invalid or unavailable parity member",
|
||||
"already available versions", "original time", "redacted alert text",
|
||||
"Keep disk serials, paths and raw agent payloads private",
|
||||
"consistent aliases", "leave unknowns unknown", "without repeating a failed",
|
||||
):
|
||||
with self.subTest(phrase=phrase):
|
||||
self.assertIn(phrase, self.guide)
|
||||
|
||||
def test_shipped_help_matches_the_source(self):
|
||||
self.assertEqual((ROOT / "docs/TROUBLESHOOTING.md").read_bytes(),
|
||||
(ROOT / "frontend-modern/public/docs/TROUBLESHOOTING.md").read_bytes())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user