push skipped any node whose declaration composed to zero resources. A node that HELD something before — the broker opening a placement gave an adopted node, say — then kept it forever: the empty declaration that would drop it was never sent, and the node's own heartbeat re-applied the stale resource with no way for the mesh to say it is gone. Now the empty declaration is sent; the host drops what the mesh owned and keeps what it found. A node that never held anything applies it as a no-op. Surfaced on ace: the foundation-opening fix (#74) removed its only resource, and the correction could not reach it until this.
77 lines
3.1 KiB
Go
77 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// One machine that cannot be worked out is not a reason to leave the mesh unconverged.
|
|
//
|
|
// A whole-mesh push refused outright the moment any single node failed to resolve, so a module on
|
|
// the anchor requiring a provision nobody had assigned a provider for stopped every OTHER machine
|
|
// from being sent anything — machines with no relation to the fault, and nothing wrong with them.
|
|
// The failure and the punishment were on different machines.
|
|
//
|
|
// It is the same rule an un-hostable module already follows one level down (a92c11b: one module on
|
|
// the wrong machine no longer refuses the whole node), applied one level up.
|
|
func TestOneUnresolvableNodeStillLetsTheRestBeSent(t *testing.T) {
|
|
sending, refusals := composeEach(
|
|
[]string{"anchor", "home-server", "laptop"},
|
|
func(node string) (sendable, error) {
|
|
if node == "anchor" {
|
|
return sendable{}, errors.New(`nothing provides "acme-ca", wanted by route-proxy`)
|
|
}
|
|
return sendable{Resources: []map[string]any{{"id": node + ".thing"}}}, nil
|
|
})
|
|
|
|
var told []string
|
|
for _, s := range sending {
|
|
told = append(told, s.node)
|
|
}
|
|
if strings.Join(told, ",") != "home-server,laptop" {
|
|
t.Errorf("a machine with nothing wrong with it was not sent: %v", told)
|
|
}
|
|
if len(refusals) != 1 || !strings.Contains(refusals[0], "anchor") ||
|
|
!strings.Contains(refusals[0], "acme-ca") {
|
|
t.Errorf("the machine that could not be worked out was not named with its reason: %v",
|
|
refusals)
|
|
}
|
|
}
|
|
|
|
// A machine whose declaration composes to nothing is SENT the empty declaration, not skipped
|
|
// (novox/hq issue 127): it may have held something before, and only sending the empty
|
|
// declaration tells it to drop what the mesh owned. It is never a refusal.
|
|
func TestAnEmptyDeclarationIsSentSoTheNodeDropsWhatItHeld(t *testing.T) {
|
|
sending, refusals := composeEach([]string{"spare"},
|
|
func(string) (sendable, error) { return sendable{}, nil })
|
|
if len(sending) != 1 || len(refusals) != 0 {
|
|
t.Errorf("an empty declaration must be sent, not skipped or refused: %v / %v", sending, refusals)
|
|
}
|
|
}
|
|
|
|
// A push that skipped a machine still ends badly, and says what was sent.
|
|
//
|
|
// **Skipping is not succeeding.** The mesh is not in the state somebody asked for, so the command
|
|
// exits non-zero — but it says how many machines it did reach, because the old message ("nothing
|
|
// was sent") was the very claim that had become untrue.
|
|
func TestASkippedMachineIsStillAnError(t *testing.T) {
|
|
if err := couldNotBeResolved(nil, 3); err != nil {
|
|
t.Fatalf("a push that resolved every machine reported a problem: %v", err)
|
|
}
|
|
|
|
err := couldNotBeResolved([]string{"anchor:\nnothing provides \"acme-ca\""}, 2)
|
|
if err == nil {
|
|
t.Fatal("a push that could not work out a machine reported success")
|
|
}
|
|
said := err.Error()
|
|
if strings.Contains(said, "nothing was sent") {
|
|
t.Errorf("the push says nothing was sent, and it sent two machines: %q", said)
|
|
}
|
|
for _, want := range []string{"anchor", "acme-ca", "2 other node(s) were"} {
|
|
if !strings.Contains(said, want) {
|
|
t.Errorf("the refusal does not say %q: %q", want, said)
|
|
}
|
|
}
|
|
}
|