health check: the last six producers carry their dashboard sentence (R-79 option A, D3)
Docker unreachable, protected container down, and the four storage-path entries now carry a bundle key beside their wire text; the wire text is unchanged byte for byte. Hungarian values of the four storage keys equal the frozen formats (i18n_go_parity). Red-proven: a zero MsgRef on the unavailable-path warning fails TestR79_RemainingProducersCarryTheirDashboardSentence and TestR79_HealthBannersFollowTheHousehold (producer-driven, English household sees Hungarian). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -135,7 +135,7 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
|
||||
|
||||
// 5. Docker health
|
||||
if err := checkDocker(); err != nil {
|
||||
report.addIssue(fmt.Sprintf("Docker: %v", err), MsgRef{})
|
||||
addDockerIssue(report, err)
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [monitor] Docker daemon: FAIL (%v)", err)
|
||||
}
|
||||
@@ -153,9 +153,7 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
|
||||
logger.Printf("[DEBUG] [monitor] Checking %d protected containers: %v", len(protected), protected)
|
||||
}
|
||||
missingProtected := checkProtectedContainers(protected)
|
||||
for _, name := range missingProtected {
|
||||
report.addIssue(fmt.Sprintf("Protected container not running: %s", name), MsgRef{})
|
||||
}
|
||||
addProtectedIssues(report, missingProtected)
|
||||
if debug {
|
||||
if len(missingProtected) > 0 {
|
||||
logger.Printf("[DEBUG] [monitor] Protected containers missing: %v", missingProtected)
|
||||
@@ -165,13 +163,7 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
|
||||
}
|
||||
|
||||
// 7. Storage paths
|
||||
storageIssues, storageWarnings, storageKinds := checkStoragePaths(storagePaths)
|
||||
for _, is := range storageIssues {
|
||||
report.addIssue(is, MsgRef{})
|
||||
}
|
||||
for i, w := range storageWarnings {
|
||||
report.addWarning(w, storageKinds[i])
|
||||
}
|
||||
addStorage(report, storagePaths)
|
||||
|
||||
// Determine status
|
||||
if len(report.Issues) > 0 {
|
||||
@@ -225,6 +217,38 @@ func (r *HealthReport) FormatMessage() string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// addDockerIssue / addProtectedIssues / addStorage are RunHealthCheck's Docker, protected-container
|
||||
// and storage-path halves, split out so a test can drive them without a Docker daemon or a real drive
|
||||
// (R-79 option A: TestR79_RemainingProducersCarryTheirDashboardSentence). Each entry keeps its wire
|
||||
// text and carries the dashboard's own sentence beside it (R-516 item 10).
|
||||
func addDockerIssue(report *HealthReport, err error) {
|
||||
// The argument is Docker's own error text, which is English in every language (it comes from
|
||||
// the docker client); the sentence around it follows the household.
|
||||
report.addIssue(fmt.Sprintf("Docker: %v", err), MsgRef{"health.docker_unreachable", []interface{}{err.Error()}})
|
||||
}
|
||||
|
||||
func addProtectedIssues(report *HealthReport, missing []string) {
|
||||
for _, name := range missing {
|
||||
report.addIssue(fmt.Sprintf("Protected container not running: %s", name), MsgRef{"health.protected_container_down", []interface{}{name}})
|
||||
}
|
||||
}
|
||||
|
||||
func addStorage(report *HealthReport, paths []settings.StoragePath) {
|
||||
issues, issueMsgs, warnings, kinds, warnMsgs := checkStoragePaths(paths)
|
||||
for i, is := range issues {
|
||||
report.addIssue(is, issueMsgs[i])
|
||||
}
|
||||
for i, w := range warnings {
|
||||
report.addWarningMsg(w, kinds[i], warnMsgs[i])
|
||||
}
|
||||
}
|
||||
|
||||
// isMountPointFn / diskUsageFn are test seams over the two drive probes checkStoragePaths makes.
|
||||
var (
|
||||
isMountPointFn = system.IsMountPoint
|
||||
diskUsageFn = system.GetDiskUsage
|
||||
)
|
||||
|
||||
func checkDocker() error {
|
||||
cmd := dockerexec.Command("docker", "info", "--format", "{{.ServerVersion}}")
|
||||
out, err := cmd.Output()
|
||||
@@ -303,39 +327,47 @@ func checkProtectedContainers(protected []string) []string {
|
||||
|
||||
// checkStoragePaths returns the storage issues and, beside each warning, its KIND (R-553) — the
|
||||
// dashboard places the "not on a separate drive" warning inline under the storage bars, and it must
|
||||
// find it by kind rather than by the words the sentence happens to contain today.
|
||||
func checkStoragePaths(paths []settings.StoragePath) (issues, warnings, kinds []string) {
|
||||
// find it by kind rather than by the words the sentence happens to contain today. issueMsgs and
|
||||
// warnMsgs are parallel to issues and warnings: the dashboard sentence of each (R-79 option A; the
|
||||
// Hungarian bundle value of each key equals the frozen wire format above, byte for byte).
|
||||
func checkStoragePaths(paths []settings.StoragePath) (issues []string, issueMsgs []MsgRef, warnings, kinds []string, warnMsgs []MsgRef) {
|
||||
warn := func(text, kind string, msg MsgRef) {
|
||||
warnings, kinds, warnMsgs = append(warnings, text), append(kinds, kind), append(warnMsgs, msg)
|
||||
}
|
||||
for _, sp := range paths {
|
||||
// Skip decommissioned paths — no longer in active use
|
||||
if sp.Decommissioned {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip disconnected paths — handled by the storage watchdog
|
||||
// Skip disconnected paths — handled by the storage watchdog. No dashboard sentence: the
|
||||
// dashboard drops this warning for its own alert.storage.disconnected banner (R-516 item 8).
|
||||
if sp.Disconnected {
|
||||
warnings, kinds = append(warnings, fmt.Sprintf(warnFmtStorageDisconnected, sp.Label, sp.Path)), append(kinds, WarnKindStorageDisconnected)
|
||||
warn(fmt.Sprintf(warnFmtStorageDisconnected, sp.Label, sp.Path), WarnKindStorageDisconnected, MsgRef{})
|
||||
continue
|
||||
}
|
||||
|
||||
// Path accessible?
|
||||
if _, err := os.Stat(sp.Path); err != nil {
|
||||
warnings, kinds = append(warnings, fmt.Sprintf(warnFmtStorageUnavailable, sp.Path)), append(kinds, WarnKindStorageUnavailable)
|
||||
warn(fmt.Sprintf(warnFmtStorageUnavailable, sp.Path), WarnKindStorageUnavailable,
|
||||
MsgRef{"health.storage_unavailable", []interface{}{sp.Path}})
|
||||
continue
|
||||
}
|
||||
|
||||
// Mount point check — warning, not issue (avoids false FAIL on demo/test environments)
|
||||
if !system.IsMountPoint(sp.Path) {
|
||||
warnings = append(warnings, fmt.Sprintf(
|
||||
warnFmtStorageNotSeparate, sp.Path))
|
||||
kinds = append(kinds, WarnKindStorageNotSeparate)
|
||||
if !isMountPointFn(sp.Path) {
|
||||
warn(fmt.Sprintf(warnFmtStorageNotSeparate, sp.Path), WarnKindStorageNotSeparate,
|
||||
MsgRef{"health.storage_not_separate", []interface{}{sp.Path}})
|
||||
}
|
||||
|
||||
// Disk usage
|
||||
if di := system.GetDiskUsage(sp.Path); di != nil {
|
||||
if di := diskUsageFn(sp.Path); di != nil {
|
||||
if di.UsedPercent >= 95 {
|
||||
issues = append(issues, fmt.Sprintf(issueFmtStorageAlmostFull, sp.Path, di.UsedPercent))
|
||||
issueMsgs = append(issueMsgs, MsgRef{"health.storage_almost_full", []interface{}{sp.Path, di.UsedPercent}})
|
||||
} else if di.UsedPercent >= 90 {
|
||||
warnings, kinds = append(warnings, fmt.Sprintf(warnFmtStorageUsageHigh, sp.Path, di.UsedPercent)), append(kinds, WarnKindStorageUsageHigh)
|
||||
warn(fmt.Sprintf(warnFmtStorageUsageHigh, sp.Path, di.UsedPercent), WarnKindStorageUsageHigh,
|
||||
MsgRef{"health.storage_usage_high", []interface{}{sp.Path, di.UsedPercent}})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user