A delivery and the five-minute reconcile were two paths that applied, ordered only by a lock, and each order it allowed was met live (issues 257, 261, 267). Now both only enqueue: one worker takes the newest declaration held when it starts, applies it once and makes one report, and reports leave in the order they are made. A declaration may carry the controller's lease epoch beside its sequence; one older than what this node applied is refused before anything is touched, counted, logged and reported. A report carries the declaration's epoch and sequence and the host's own report sequence, kept on disk so it goes on increasing across restarts and self-updates. Without an epoch, today's behaviour stands.
96 lines
3.9 KiB
Go
96 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-host/internal/link"
|
|
"github.com/novox/mesh-host/internal/store"
|
|
)
|
|
|
|
// A declaration carries no order, so a host cannot tell an older one from a newer (novox/hq
|
|
// 04-ISSUES/107). Its only identity was the digest of its bytes: "not the last" could be said,
|
|
// "older" could not.
|
|
|
|
func keptWith(t *testing.T, sequence int64) store.Declared {
|
|
t.Helper()
|
|
return keptAt(t, link.Order{Sequence: sequence})
|
|
}
|
|
|
|
func keptAt(t *testing.T, order link.Order) store.Declared {
|
|
t.Helper()
|
|
body, err := json.Marshal(map[string]any{
|
|
"declaration": 1, "resources": []any{}, "owns_nothing": true, "sequence": order.Sequence,
|
|
"epoch": order.Epoch,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return store.Declared{Declaration: body, Signature: []byte("x")}
|
|
}
|
|
|
|
func TestAnOlderDeclarationFromTheMeshIsRefused(t *testing.T) {
|
|
err := refuseOlder(keptWith(t, 7), nil, link.Order{Sequence: 5})
|
|
if err == nil {
|
|
t.Fatal("sequence 5 was accepted over a kept 7")
|
|
}
|
|
if !strings.Contains(err.Error(), "older") || !strings.Contains(err.Error(), "nothing was applied") {
|
|
t.Fatalf("the refusal does not say what it is: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestANewerOrEqualDeclarationIsNot(t *testing.T) {
|
|
if err := refuseOlder(keptWith(t, 7), nil, link.Order{Sequence: 8}); err != nil {
|
|
t.Fatalf("sequence 8 was refused over a kept 7: %v", err)
|
|
}
|
|
// Equal is the same declaration again, which reconciling is for.
|
|
if err := refuseOlder(keptWith(t, 7), nil, link.Order{Sequence: 7}); err != nil {
|
|
t.Fatalf("the same sequence was refused: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestNoOrderClaimedMeansNoOrderCompared(t *testing.T) {
|
|
// An older controller sends none; a host that received before it understood them kept none.
|
|
// Refusing on a guess would strand a node the moment the controller is older than the host.
|
|
if err := refuseOlder(keptWith(t, 7), nil, link.Order{Sequence: 0}); err != nil {
|
|
t.Fatalf("a declaration claiming no order was refused: %v", err)
|
|
}
|
|
if err := refuseOlder(keptWith(t, 0), nil, link.Order{Sequence: 3}); err != nil {
|
|
t.Fatalf("a declaration was refused against a kept one that claimed no order: %v", err)
|
|
}
|
|
if err := refuseOlder(store.Declared{}, store.ErrNothingDeclared, link.Order{Sequence: 3}); err != nil {
|
|
t.Fatalf("a first declaration was refused: %v", err)
|
|
}
|
|
}
|
|
|
|
// **The lease epoch decides first** (novox/hq to-be 45 §6): a declaration from a controller that lost
|
|
// its lease is refused whatever its sequence, and a new lease holder's is taken whatever its sequence.
|
|
func TestAnOlderEpochIsRefusedAndANewerTaken(t *testing.T) {
|
|
kept := keptAt(t, link.Order{Epoch: 57, Sequence: 3})
|
|
err := refuseOlder(kept, nil, link.Order{Epoch: 41, Sequence: 12})
|
|
if err == nil || !strings.Contains(err.Error(), "epoch 41") || !strings.Contains(err.Error(), "epoch 57") {
|
|
t.Fatalf("an older epoch was not refused by name: %v", err)
|
|
}
|
|
if err := refuseOlder(kept, nil, link.Order{Epoch: 58, Sequence: 1}); err != nil {
|
|
t.Fatalf("a new lease holder's first declaration was refused for its sequence: %v", err)
|
|
}
|
|
if err := refuseOlder(kept, nil, link.Order{Epoch: 57, Sequence: 2}); err == nil {
|
|
t.Fatal("a lower sequence under the same epoch was accepted")
|
|
}
|
|
// A controller with no lease — older, or rolled back to a build from before it — is today's
|
|
// behaviour: no epoch compared.
|
|
if err := refuseOlder(kept, nil, link.Order{Sequence: 4}); err != nil {
|
|
t.Fatalf("a declaration with no epoch was refused for one: %v", err)
|
|
}
|
|
if held, older := olderThanKept(kept, nil, link.Order{Epoch: 41, Sequence: 12}); !older ||
|
|
held != (link.Order{Epoch: 57, Sequence: 3}) {
|
|
t.Fatalf("olderThanKept said %v, holding %+v", older, held)
|
|
}
|
|
// What is kept cannot be read: no order to compare, so nothing is refused on a guess.
|
|
if _, older := olderThanKept(store.Declared{Declaration: []byte("garbled")}, nil,
|
|
link.Order{Epoch: 41, Sequence: 1}); older {
|
|
t.Fatal("refused against a kept declaration nobody can read")
|
|
}
|
|
}
|