Keep what each machine did, so status can say what is wrong
A node reports back after applying a declaration: it worked, some of it failed, or the whole thing was refused. A refusal or a failure moved last_seen and the reason went to a log line — so "which machine is not doing what it was told" had no answer the next morning, which is the question a mesh exists to answer. Refused and failed are kept as different things, because they are different situations with different remedies: refused means the machine is exactly as it was and what is wrong is in what was sent; failed means it is in a state nobody declared and what is wrong is on the machine. One word for both would make the record say less than the node did. One row per node, replaced. The question is the machine's current state — "this failed an hour ago and then succeeded" is not a machine anybody needs to look at, and a table of every report would bury the ones that matter under the ones that do not. `status` now answers three questions in the order somebody asks them: is anything broken, is anything not answering, is anything out of date. The first has consequences now, the third is a plan for later, and a status leading with the third would bury the first. A machine that has never spoken is reported as quiet rather than as broken — new, switched off and unreachable are not the same as tried and could not. The mapping from a report to an outcome had no test at all, which the injection caught: it is the code deciding which of those situations a machine is in. It has four now, including that a partial report never becomes the account of what the machine holds — the fault that destroyed a substrate once.
This commit is contained in:
+69
-23
@@ -1500,35 +1500,81 @@ func statusCommand(ctx context.Context) error {
|
||||
}
|
||||
defer inv.Close()
|
||||
|
||||
// Three questions, in the order somebody asks them: is anything broken, is anything not
|
||||
// answering, is anything out of date. The first has consequences now, the second may, and
|
||||
// the third is a plan for later — and a status that led with the third would bury the first.
|
||||
wrong, err := inv.NotDoingWhatTheyWereTold(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(wrong) > 0 {
|
||||
fmt.Printf("%d machine(s) are not doing what they were told:\n\n", len(wrong))
|
||||
for _, d := range wrong {
|
||||
fmt.Printf(" %-18s %-9s %s\n", d.Node, d.Outcome, d.At.Local().Format("2006-01-02 15:04"))
|
||||
if d.Refused != "" {
|
||||
// The host's own words. It says exactly what it could not accept, and nothing
|
||||
// written here would say it better.
|
||||
fmt.Printf(" %-18s %s\n", "", firstLine(d.Refused))
|
||||
}
|
||||
for _, f := range d.Failed {
|
||||
fmt.Printf(" %-18s %s: %s\n", "", f.ID, firstLine(f.Error))
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
nodes, err := inv.Nodes(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var quiet []string
|
||||
for _, n := range nodes {
|
||||
// Never heard from, or not lately. Different from failing: a machine that says nothing
|
||||
// may be new, switched off, or unreachable, and none of those is a machine that tried
|
||||
// and could not.
|
||||
if n.LastSeen.IsZero() || time.Since(n.LastSeen) > time.Hour {
|
||||
quiet = append(quiet, n.Name+" ("+heardFrom(n)+")")
|
||||
}
|
||||
}
|
||||
if len(quiet) > 0 {
|
||||
fmt.Printf("%d machine(s) not heard from lately:\n %s\n\n",
|
||||
len(quiet), strings.Join(quiet, "\n "))
|
||||
}
|
||||
|
||||
behind, err := inv.Behind(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(behind) == 0 {
|
||||
fmt.Println("every module with a source is built from what that source has")
|
||||
return nil
|
||||
if len(behind) > 0 {
|
||||
var names []string
|
||||
for m := range behind {
|
||||
names = append(names, m)
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
fmt.Printf("%d module(s) behind their source:\n\n", len(behind))
|
||||
for _, m := range names {
|
||||
from, err := inv.SourceOf(ctx, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %-18s holds %s, source has %s\n", m, short(from.BuiltFrom), short(from.Head))
|
||||
if on := behind[m]; len(on) > 0 {
|
||||
// The part somebody actually wants. A module being out of date is a fact about
|
||||
// the catalogue; machines running the old one is the thing with consequences.
|
||||
fmt.Printf(" %-18s running on %s\n", "", strings.Join(on, ", "))
|
||||
} else {
|
||||
fmt.Printf(" %-18s assigned to nothing\n", "")
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
var names []string
|
||||
for m := range behind {
|
||||
names = append(names, m)
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
fmt.Printf("%d module(s) behind their source:\n\n", len(behind))
|
||||
for _, m := range names {
|
||||
from, err := inv.SourceOf(ctx, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %-20s holds %s, source has %s\n", m, short(from.BuiltFrom), short(from.Head))
|
||||
if nodes := behind[m]; len(nodes) > 0 {
|
||||
// The part somebody actually wants. A module being out of date is a fact about the
|
||||
// catalogue; machines running the old one is the thing with consequences.
|
||||
fmt.Printf(" %-20s running on %s\n", "", strings.Join(nodes, ", "))
|
||||
} else {
|
||||
fmt.Printf(" %-20s assigned to nothing\n", "")
|
||||
}
|
||||
if len(wrong) == 0 && len(quiet) == 0 && len(behind) == 0 {
|
||||
// Said plainly. "Nothing to report" and "nothing was checked" must never look the same,
|
||||
// and getting here means all three questions were asked and answered.
|
||||
fmt.Printf("%d machine(s), all doing what they were told, all heard from, "+
|
||||
"and every module current with its source\n", len(nodes))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user