mesh/merge-gate fail: builds mounts → shanks; no bus step; a manifest the change touches fails the module check: modules/mounts/module.json: this manifest …
mesh/repo-check fail: its merge-check.sh failed: files asking for a setting without saying whether it is trusted: 24
mesh/delivery superseded: a newer head of the same pull request
The one INI secret smb-credentials could not be given through the desk prompt, which takes one hidden line, and a missing login was said as a fault: the first-node gate put the build back and the operator was told nothing was theirs to do. The username is now the setting smb-users and each password the one-line secret smb-password-<source>; a source missing either answers waits through mesh-sdk's health answer (hq ADR 0283, issue 386, issue 379).
109 lines
4.2 KiB
Go
109 lines
4.2 KiB
Go
package main
|
|
|
|
// The module's health (novox/hq ADR 0240, ADR 0263 rule 8), judged from what the process last found.
|
|
//
|
|
// Unhealthy when a share that should always be there has been unreachable or failed for five minutes,
|
|
// when an automount is not armed, when something the settings name cannot be set up, or when the
|
|
// process has not looked for two minutes. An occasional source that is absent is healthy: it is said as
|
|
// not present since when, and raises nothing. A source waiting for the operator's username or password is
|
|
// not a fault: when nothing else is found the answer is *waits*, naming each (novox/hq ADR 0283).
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/health"
|
|
)
|
|
|
|
// Health is the answer the node-engine reads from a tool check: mesh-sdk's answer (state, healthy, why,
|
|
// waits), with what was found and what was said beside it.
|
|
type Health struct {
|
|
health.Answer
|
|
Found []string `json:"found,omitempty"`
|
|
Said []string `json:"said,omitempty"`
|
|
}
|
|
|
|
// Grace is how long a share may be away before it is a fault; Stale how old the process's look may be.
|
|
const (
|
|
Grace = 5 * time.Minute
|
|
Stale = 2 * time.Minute
|
|
)
|
|
|
|
func clock(ts string) string {
|
|
t, err := time.Parse(time.RFC3339, ts)
|
|
if err != nil {
|
|
return ts
|
|
}
|
|
return t.Local().Format("15:04")
|
|
}
|
|
|
|
// Judge is the health of one status at a moment.
|
|
func Judge(st Status, readErr error, now time.Time) Health {
|
|
if readErr != nil {
|
|
return Health{Answer: health.Unhealthy("The module's process has left no account of this machine's mounts (" + readErr.Error() + ")."),
|
|
Found: []string{"no status"}}
|
|
}
|
|
var found, said, waiting []string
|
|
var waits []health.Wait
|
|
if looked, err := time.Parse(time.RFC3339, st.Looked); err != nil || now.Sub(looked) > Stale {
|
|
found = append(found, fmt.Sprintf("The module's process last looked at %s, more than two minutes ago.", clock(st.Looked)))
|
|
}
|
|
for _, p := range st.Problems {
|
|
found = append(found, "The module's settings or secret: "+p+".")
|
|
}
|
|
from := ""
|
|
if st.Server != "" {
|
|
from = " of " + st.Server
|
|
}
|
|
for _, s := range st.Shares {
|
|
since, _ := time.Parse(time.RFC3339, s.Since)
|
|
long := now.Sub(since) >= Grace
|
|
switch s.State {
|
|
case StateMounted, StateArmed:
|
|
said = append(said, fmt.Sprintf("%s at %s: %s since %s.", s.Name, s.Mountpoint, s.State, clock(s.Since)))
|
|
case StateWaiting:
|
|
waiting = append(waiting, fmt.Sprintf("The source %s at %s is not mounted since %s: %s.", s.Name, s.Mountpoint, clock(s.Since),
|
|
strings.TrimSuffix(s.Why, ".")))
|
|
waits = append(waits, s.Waits...)
|
|
case StateAbsent:
|
|
said = append(said, fmt.Sprintf("%s at %s: not present since %s.", s.Name, s.Mountpoint, clock(s.Since)))
|
|
case StateUnreachable:
|
|
line := fmt.Sprintf("The share %s%s is not reachable from this machine since %s (%s).", s.Name, from, clock(s.Since), s.Why)
|
|
if s.Kind != "nfs" {
|
|
line = fmt.Sprintf("The source %s is not reachable from this machine since %s (%s).", s.Name, clock(s.Since), s.Why)
|
|
}
|
|
if long {
|
|
found = append(found, line)
|
|
} else {
|
|
said = append(said, line+" Raised after five minutes.")
|
|
}
|
|
default:
|
|
line := fmt.Sprintf("The %s %s at %s cannot be mounted since %s: %s.", map[bool]string{true: "source", false: "share"}[s.Occasional],
|
|
s.Name, s.Mountpoint, clock(s.Since), strings.TrimSuffix(s.Why, "."))
|
|
if long || strings.Contains(s.Why, "not armed") || strings.Contains(s.Why, "not the module's") {
|
|
found = append(found, line)
|
|
} else {
|
|
said = append(said, line+" Raised after five minutes.")
|
|
}
|
|
}
|
|
}
|
|
if len(found) > 0 {
|
|
return Health{Answer: health.Unhealthy(strings.Join(append(found, waiting...), " "), waits...), Found: found, Said: append(said, waiting...)}
|
|
}
|
|
if len(waits) > 0 {
|
|
return Health{Answer: health.Waiting(strings.Join(waiting, " "), waits...), Said: append(said, waiting...)}
|
|
}
|
|
why := "Every share this machine is asked to mount is armed or mounted, and every occasional source is armed, mounted or not present."
|
|
if len(st.Shares) == 0 {
|
|
why = "This machine is asked to mount nothing."
|
|
}
|
|
return Health{Answer: health.Healthy(why), Said: said}
|
|
}
|
|
|
|
// Health reads the process's last look and judges it.
|
|
func (m *Machine) Health(now time.Time) Health {
|
|
st, err := readJSON[Status](m, StatusFile)
|
|
return Judge(st, err, now)
|
|
}
|