A held module is in status, and it stops the mesh reading as well
novox/hq 04-ISSUES/125. A module assigned to a machine and never taken runs none of what it declares. Status had no vocabulary for it: the machine was heard from, current, and doing what it was told, so the mesh printed "all doing what they were told" — which was true, and was acted on, and every public name on the machine went dark. Status now names each module a machine is holding rather than running, per machine and with a count, read from what the MACHINE reported rather than from the mesh's take-time listing — the machine is the only thing that knows what it found. The JSON form carries the same rows, absent rather than empty when nothing is held. And a hold suppresses the all-well sentence, where being adopted does not: adopted is a mode somebody chose, a module assigned and never taken is a half-finished action with nothing left to finish it. The condition is now a named function so the rule lives in one place and a test binds to the real thing rather than a copy of it. untakenModules raises a read it cannot make rather than answering "holding nothing" from a failed query, which is the shape this whole issue is.
This commit is contained in:
@@ -171,6 +171,39 @@ func statusCommand(ctx context.Context, args []string) error {
|
||||
fmt.Printf("\n `push --behind` sends them\n\n")
|
||||
}
|
||||
|
||||
if len(asked.untaken) > 0 {
|
||||
// **Before the adopted line, and it breaks "all well".** An adopted machine is a state
|
||||
// somebody chose and can leave alone; a module assigned to one and never taken is work
|
||||
// outstanding that reads exactly like work finished. That reading is what stopped a
|
||||
// predecessor's proxy on the strength of four green surfaces (novox/hq 04-ISSUES/125).
|
||||
machines := make([]string, 0, len(asked.untaken))
|
||||
for name := range asked.untaken {
|
||||
machines = append(machines, name)
|
||||
}
|
||||
sort.Strings(machines)
|
||||
total := 0
|
||||
for _, held := range asked.untaken {
|
||||
for _, n := range held {
|
||||
total += n
|
||||
}
|
||||
}
|
||||
fmt.Printf("%d resource(s) are held as found, because their module was assigned and never "+
|
||||
"taken — so it is running none of what it declares:\n", total)
|
||||
for _, name := range machines {
|
||||
modules := make([]string, 0, len(asked.untaken[name]))
|
||||
for m := range asked.untaken[name] {
|
||||
modules = append(modules, m)
|
||||
}
|
||||
sort.Strings(modules)
|
||||
parts := make([]string, 0, len(modules))
|
||||
for _, m := range modules {
|
||||
parts = append(parts, fmt.Sprintf("%s (%d)", m, asked.untaken[name][m]))
|
||||
}
|
||||
fmt.Printf(" %-12s %s\n", name, strings.Join(parts, ", "))
|
||||
}
|
||||
fmt.Printf("\n `take <node> <module>` compares what runs against what it declares, and runs it\n\n")
|
||||
}
|
||||
|
||||
if adopted := adoptedNodes(nodes); len(adopted) > 0 {
|
||||
// Said, because nothing forces the flip: a node left adopted is visible here rather than
|
||||
// read as converged (novox/hq ADR 0100). Not a fault, so it does not break "all well".
|
||||
@@ -178,8 +211,7 @@ func statusCommand(ctx context.Context, args []string) error {
|
||||
fmt.Printf("\n `converge <node>` previews the flip\n\n")
|
||||
}
|
||||
|
||||
if len(wrong) == 0 && len(quiet) == 0 && len(behind) == 0 && len(asked.waiting) == 0 &&
|
||||
len(asked.refused) == 0 && asked.network == "" {
|
||||
if asked.well() {
|
||||
// Said plainly. "Nothing to report" and "nothing was checked" must never look the same,
|
||||
// and getting here means every question was asked and answered.
|
||||
fmt.Printf("%d machine(s), all doing what they were told, all heard from, running what "+
|
||||
@@ -247,6 +279,13 @@ func theThreeQuestions(ctx context.Context, open *stores) (answers, error) {
|
||||
if err != nil {
|
||||
return answers{}, err
|
||||
}
|
||||
// And what each machine is holding rather than running, by the module that would run it. Read
|
||||
// from what the machine itself last reported, not from what take-time computed: the machine is
|
||||
// the only thing that knows what it found (novox/hq 04-ISSUES/125).
|
||||
out.untaken, err = untakenModules(ctx, inv, out.nodes)
|
||||
if err != nil {
|
||||
return answers{}, err
|
||||
}
|
||||
|
||||
// And which machines are not running what the mesh would send them. The same question as a
|
||||
// module being behind its source, one level down: that one says the catalogue is out of date,
|
||||
@@ -281,3 +320,52 @@ func theThreeQuestions(ctx context.Context, open *stores) (answers, error) {
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// untakenModules is, per machine, each module whose resources that machine is holding as found, and
|
||||
// how many.
|
||||
//
|
||||
// **The machine's own account, not the mesh's.** An adopted node decides at apply time what it found
|
||||
// and reports it; the mesh's take-time listing is a different thing and was the one this command used
|
||||
// to have, which is why a module assigned after the listing showed nothing at all
|
||||
// (novox/hq 04-ISSUES/125).
|
||||
//
|
||||
// A machine that reports no holds contributes nothing, so a converged mesh answers an empty map and
|
||||
// the caller prints nothing.
|
||||
func untakenModules(ctx context.Context, inv *inventory.Inventory, nodes []inventory.Node) (
|
||||
map[string]map[string]int, error) {
|
||||
|
||||
out := map[string]map[string]int{}
|
||||
for _, n := range nodes {
|
||||
said, err := inv.AdoptionOf(ctx, n.Name)
|
||||
if err != nil {
|
||||
// A machine whose record cannot be read is not a machine holding nothing. Said, because
|
||||
// answering "nothing held" from a failed read is the shape this whole issue is about.
|
||||
return nil, fmt.Errorf("what %s is holding cannot be read: %w", n.Name, err)
|
||||
}
|
||||
for _, h := range said.Held {
|
||||
if h.Module == "" {
|
||||
continue // a hold the mesh cannot attribute to a module has nothing to take
|
||||
}
|
||||
if out[n.Name] == nil {
|
||||
out[n.Name] = map[string]int{}
|
||||
}
|
||||
out[n.Name][h.Module]++
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// well is whether every question this command asks came back with nothing to say.
|
||||
//
|
||||
// Named, and in one place, because it is the sentence an operator acts on and it has been wrong
|
||||
// twice. It is deliberately NOT "nothing is broken": a machine holding what it found is not broken
|
||||
// and is not doing what it was told either.
|
||||
//
|
||||
// **A hold suppresses it; being adopted does not.** Adopted is a mode somebody chose and can leave
|
||||
// alone. A module assigned to a machine and never taken is a half-finished action with nothing left
|
||||
// to finish it — it runs none of what it declares, and "all doing what they were told" was true and
|
||||
// read as success for the whole of the edge cut-over outage (novox/hq 04-ISSUES/125).
|
||||
func (a answers) well() bool {
|
||||
return len(a.wrong) == 0 && len(a.quiet) == 0 && len(a.behind) == 0 &&
|
||||
len(a.waiting) == 0 && len(a.refused) == 0 && a.network == "" && len(a.untaken) == 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user