A named push flushed every other machine whose declaration differed from what it was last sent (hq ADR 0083). Under an upgrade policy of `record`, or a plan still waiting on its first machine (ADR 0218), every machine running the module differs, so `push <one>` sent the held build to all of them (hq issue 259). Each send now records which build of each module it carried (node.sent_builds, migration 0061). The cascade, and the bus holder added to a named push, skip a machine any of whose modules would move to a build its policy records or an open plan has not sent it, and say which module, which build, why, and that `push <node>` sends it. A machine whose last send was not recorded is held until it is named. The named machine itself, a whole-mesh push and `push --behind` are unchanged.
94 lines
4.0 KiB
Go
94 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// A declaration composed earlier is numbered lower than one composed later, whatever order the two
|
|
// are sent in (novox/hq issue 204). The number used to be taken at send time, after composing, so a
|
|
// declaration composed before an assignment changed and sent after a newer one carried the higher
|
|
// number — and the machine, which refuses a lower number, took the older content as the mesh's
|
|
// newest word. Taken before the composition reads anything, the order of numbers is the order of
|
|
// compositions, and the host's refusal does what it is for.
|
|
func TestADeclarationComposedEarlierIsNumberedLowerWhateverOrderItIsSent(t *testing.T) {
|
|
allot := numbered()
|
|
var composed []string
|
|
compose := func(stamp string) func(string) (sendable, error) {
|
|
return func(node string) (sendable, error) {
|
|
composed = append(composed, stamp)
|
|
return sendable{Resources: []map[string]any{{"id": node + "." + stamp}}}, nil
|
|
}
|
|
}
|
|
// Composed first — before an assignment changed — and sent last.
|
|
stale, _ := composeEach([]string{"anchor"}, allot, compose("before"))
|
|
// Composed after the change, sent first.
|
|
fresh, _ := composeEach([]string{"anchor"}, allot, compose("after"))
|
|
|
|
if stale[0].declared.Sequence != 1 || fresh[0].declared.Sequence != 2 {
|
|
t.Fatalf("the numbers do not follow the compositions: before=%d after=%d",
|
|
stale[0].declared.Sequence, fresh[0].declared.Sequence)
|
|
}
|
|
// Sent in the other order, the numbers do not change — so the machine that has applied the
|
|
// fresh one (2) refuses the stale one (1) when it arrives late.
|
|
if !(stale[0].declared.Sequence < fresh[0].declared.Sequence) {
|
|
t.Fatal("a declaration composed earlier must carry the lower number, however late it is sent")
|
|
}
|
|
if len(composed) != 2 || composed[0] != "before" {
|
|
t.Fatalf("compositions happened in an unexpected order: %v", composed)
|
|
}
|
|
}
|
|
|
|
// The number is taken before the first read of the composition, not after it: an allotter that
|
|
// fails leaves nothing composed for that machine, and the others are still composed.
|
|
func TestTheNumberIsTakenBeforeComposingAndItsFailureIsARefusal(t *testing.T) {
|
|
calls := 0
|
|
allot := func(node string) (int64, error) {
|
|
if node == "anchor" {
|
|
return 0, context.DeadlineExceeded
|
|
}
|
|
return 7, nil
|
|
}
|
|
sending, refusals := composeEach([]string{"anchor", "laptop"}, allot, func(node string) (sendable, error) {
|
|
calls++
|
|
if node == "anchor" {
|
|
t.Fatal("anchor was composed although its number could not be taken")
|
|
}
|
|
return sendable{}, nil
|
|
})
|
|
if calls != 1 || len(sending) != 1 || sending[0].node != "laptop" || sending[0].declared.Sequence != 7 {
|
|
t.Fatalf("laptop should be composed with its number and anchor refused: %v / %v", sending, refusals)
|
|
}
|
|
if len(refusals) != 1 {
|
|
t.Fatalf("anchor's failed number should be a refusal naming it: %v", refusals)
|
|
}
|
|
}
|
|
|
|
// What was sent is written down even when the sender's context is already cancelled (issue 204): a
|
|
// controller replaced mid-send had told the machine and never recorded it, so status read "applied,
|
|
// current" over a machine that had just been sent something else.
|
|
func TestASendIsRecordedEvenWhenTheSenderIsBeingCancelled(t *testing.T) {
|
|
inv := inventory.ForTest(t)
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
if _, err := inv.AddNode(ctx, "anchor"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cancel() // the sender is going away: its context is cancelled between the send and the record
|
|
body := []byte(`{"declaration":1,"resources":[]}`)
|
|
digest, err := recordSent(ctx, inv, "anchor", body, nil)
|
|
if err != nil {
|
|
// NodeByName on the cancelled context may itself refuse; the record must still be possible
|
|
// through the detached context, so look the node up again on a live one.
|
|
t.Fatalf("recording a send after cancellation failed: %v", err)
|
|
}
|
|
outstanding, err := inv.Outstanding(t.Context(), "anchor")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if outstanding != digest || digest != digestOf(body) {
|
|
t.Fatalf("the send was not recorded: outstanding %q, sent %q", outstanding, digest)
|
|
}
|
|
}
|