Drop what a send carried from the node row (migration 0109) and read an image list from the compositions (release 6, issue 1945 B8)

This commit is contained in:
2026-10-24 17:29:03 +02:00
parent b4a62e9e6e
commit 8f90d3ecd6
18 changed files with 148 additions and 125 deletions
+2 -2
View File
@@ -191,7 +191,7 @@ func TestAMachineIsWaitingWhenWhatItWasSentIsNotWhatItShouldBe(t *testing.T) {
}
// Sent what it should be: not waiting.
if err := inv.RecordSent(ctx, anchor.ID, "aaa", nil); err != nil {
if err := inv.RecordSentForTest(ctx, anchor.ID, "aaa"); err != nil {
t.Fatal(err)
}
waiting, err = inv.Waiting(ctx, map[string]string{"anchor": "aaa", "laptop": "bbb"})
@@ -236,7 +236,7 @@ func TestAMachineWithNothingComputedForItIsNotWaiting(t *testing.T) {
// It has been sent something before, which is what makes this the case the guard is for: a
// machine with a digest and nothing computed for it would compare against the empty string
// and look out of date, when the truth is that nobody worked out what it should be.
if err := inv.RecordSent(ctx, node.ID, "what-it-got-last-time", nil); err != nil {
if err := inv.RecordSentForTest(ctx, node.ID, "what-it-got-last-time"); err != nil {
t.Fatal(err)
}
waiting, err := inv.Waiting(ctx, map[string]string{})
+7
View File
@@ -208,3 +208,10 @@ func (i *Inventory) PlanValuesForTest(t *testing.T) map[string][]byte {
}
return out
}
// RecordSentForTest gives a machine the digest and time of a last send, as a test needs one to stand in for a send: nothing
// sends a declaration any more (ADR 0539 §5), so nothing in the controller writes them.
func (i *Inventory) RecordSentForTest(ctx context.Context, node, digest string) error {
_, err := i.store.Pool().Exec(ctx, `update node set sent = $2, sent_at = now() where id = $1`, node, digest)
return err
}
+1 -1
View File
@@ -52,7 +52,7 @@ func sent(t *testing.T, inv *Inventory, ctx context.Context, node, digest string
if err != nil {
t.Fatal(err)
}
if err := inv.RecordSent(ctx, n.ID, digest, nil); err != nil {
if err := inv.RecordSentForTest(ctx, n.ID, digest); err != nil {
t.Fatal(err)
}
return digest
@@ -0,0 +1,12 @@
-- The node row keeps no record of a send's builds, epoch, summary or generation (novox/hq ADR 0539 §5, §17, issue 1945).
--
-- Nothing sends a declaration any more, so nothing writes what a send carried: the commit of each module (sent_builds, which
-- 0105 kept only as the source of the one-time copy onto the assignments, long done), the lease epoch (sent_epoch, 0068), the
-- summary `plan --diff` compared (sent_summary, 0078) and the assignment generation (sent_generation, 0094). **It is one-way**,
-- as 0108. What stays: sent and sent_at (the digest and time of the last send, which `status`' reported-and-current reads and the
-- given secrets' first good start is judged against), sent_bus_users, and sent_provisioning, which now holds the provisioner's
-- plans of each composition (inventory.RecordComposedPlans).
alter table node drop column if exists sent_builds;
alter table node drop column if exists sent_epoch;
alter table node drop column if exists sent_summary;
alter table node drop column if exists sent_generation;
-44
View File
@@ -1242,41 +1242,6 @@ func (i *Inventory) LastReports(ctx context.Context) ([]Reported, error) {
return out, rows.Err()
}
// RecordSent keeps a digest of the declaration a machine was last sent, and the build of each module
// it carried.
//
// **A digest rather than the declaration.** The mesh can compute what a machine should be at any
// moment; keeping a copy would be a second account of it, able to disagree with the first. What
// cannot be recomputed is what was *actually sent*, and that is the whole difference between a
// machine that is out of date and one that has never been told.
//
// **And which build of each module** (novox/hq issue 259, ADR 0221): module name to the commit its
// build was made from. A digest cannot say whether a machine differs because a module moved to a build
// its upgrade policy holds back, or because of something a push made — a grant — and only the second
// is a push's to send to a machine it did not name. Nil records that it is not known, as for a
// declaration sent by hand.
func (i *Inventory) RecordSent(ctx context.Context, node, digest string, builds map[string]string) error {
return i.RecordSentUnder(ctx, node, digest, builds, 0)
}
// RecordSentUnder is RecordSent for a declaration that carried an epoch (novox/hq to-be 45 §6): kept
// beside the digest, so what the mesh would send is composed with it. Zero is one that carried none.
func (i *Inventory) RecordSentUnder(ctx context.Context, node, digest string, builds map[string]string,
epoch uint64) error {
var sentEpoch *int64
if epoch > 0 {
e := int64(epoch)
sentEpoch = &e
}
if _, err := i.store.Pool().Exec(ctx,
`update node set sent = $2, sent_at = now(), sent_builds = null, sent_epoch = $3 where id = $1`, node, digest, sentEpoch); err != nil {
return err
}
// What it was sent of each module is no longer kept in the assignments: they hold placement only (ADR 0539 §12).
_ = builds
return nil
}
// AwaitingSince is since when a machine has had something waiting for its next send (novox/hq issue
// 275): the oldest assignment on it made after it was last sent, or — when nothing was assigned since —
// when it was last sent, or when it joined if it never was. The moment a bound on "not pushed yet" is
@@ -1286,15 +1251,6 @@ func (i *Inventory) AwaitingSince(ctx context.Context, name string) (time.Time,
return i.chainAwaitingSince(ctx, name)
}
// SentBuilds is the build of each module a machine was last sent, by its name: module to the commit
// its build was made from (novox/hq issue 259), read from the keys of the machine's assignments that name a commit — an
// assigned module and one only pulled in (ADR 0408, issue 1109). Known is false when that was not kept: a machine never sent
// anything, or last sent a declaration by hand, which makes every key of it say its commit unknown.
func (i *Inventory) SentBuilds(ctx context.Context, name string) (builds map[string]string, known bool, err error) {
// Never known: the assignments hold placement only (ADR 0539 §12), and what a machine runs is its have.
return nil, false, nil
}
// RecordSentBusUsers keeps a digest of the bus's user list a machine was just sent, by its name
// (novox/hq issue 249): whether the machine holding the bus must go first is whether this differs
// from the list composed now.
-12
View File
@@ -142,18 +142,6 @@ func scanEpochs(rows pgx.Rows) ([]Epoch, error) {
return out, rows.Err()
}
// SentEpoch is the epoch a machine was last sent, by its id; zero for one sent without.
func (i *Inventory) SentEpoch(ctx context.Context, id string) (uint64, error) {
var epoch *int64
if err := i.store.Pool().QueryRow(ctx, `select sent_epoch from node where id = $1`, id).Scan(&epoch); err != nil {
return 0, fmt.Errorf("reading the epoch %s was last sent: %w", id, err)
}
if epoch == nil {
return 0, nil
}
return uint64(*epoch), nil
}
// ReadsEpoch says a machine's node-engine said it reads an epoch in a declaration, by its id.
func (i *Inventory) ReadsEpoch(ctx context.Context, id string) (bool, error) {
var reads bool
@@ -45,3 +45,36 @@ func TestMigration0108DropsTheTablesOfThePlansMachineryAndKeepsWhatTheController
t.Errorf("the function raise_assignment_generation is still in the store")
}
}
// **Migration 0109 drops the columns of the node row that kept what a send carried** (ADR 0539 §17, issue 1945): the builds,
// the epoch, the summary and the generation. The digest and time of the last send stay, with the provisioner's plans.
func TestMigration0109DropsWhatASendCarriedFromTheNodeRow(t *testing.T) {
inv, migrate := ForTestBefore(t, 109)
columns := func() map[string]bool {
t.Helper()
out := map[string]bool{}
for _, row := range inv.QueryForTest(t, `select column_name::text from information_schema.columns where table_name = 'node'`) {
out[strings.Trim(row[0], `"`)] = true
}
return out
}
gone := []string{"sent_builds", "sent_epoch", "sent_summary", "sent_generation"}
before := columns()
for _, name := range gone {
if !before[name] {
t.Fatalf("node.%s is not in the store before the migration", name)
}
}
migrate()
after := columns()
for _, name := range gone {
if after[name] {
t.Errorf("node.%s is still in the store", name)
}
}
for _, stays := range []string{"sent", "sent_at", "sent_bus_users", "sent_provisioning"} {
if !after[stays] {
t.Errorf("node.%s is gone", stays)
}
}
}
-19
View File
@@ -136,22 +136,3 @@ func (i *Inventory) layerNode(ctx context.Context, nodeName string) (*string, er
}
return &n.ID, nil
}
// RecordSentSummary keeps what a machine was last sent, summarised (migration 0078): read only to
// compare what would be sent with it, never as what the machine should be.
func (i *Inventory) RecordSentSummary(ctx context.Context, node string, summary []byte) error {
_, err := i.store.Pool().Exec(ctx, `update node set sent_summary = $2 where id = $1`, node, summary)
return err
}
// SentSummary is what a machine was last sent, summarised, by its name; empty for a machine sent
// nothing since the summary was first kept.
func (i *Inventory) SentSummary(ctx context.Context, name string) ([]byte, error) {
var raw []byte
err := i.store.Pool().QueryRow(ctx,
`select sent_summary from node where name = $1`, name).Scan(&raw)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return raw, err
}
-21
View File
@@ -83,24 +83,3 @@ func TestTheLayerASetReplacesIsKeptAndReadable(t *testing.T) {
t.Fatalf("the node's history changed with the mesh's: %v %v", past, err)
}
}
// What a machine was last sent is kept, summarised, and read back by its name; a machine sent
// nothing since has nothing to compare with.
func TestWhatAMachineWasSentIsKeptForComparison(t *testing.T) {
inv := fresh(t)
ctx := t.Context()
n, err := inv.AddNode(ctx, "anchor")
if err != nil {
t.Fatal(err)
}
if got, err := inv.SentSummary(ctx, "anchor"); err != nil || len(got) != 0 {
t.Fatalf("a machine never sent anything: %s %v", got, err)
}
if err := inv.RecordSentSummary(ctx, n.ID, []byte(`[{"id":"web.server","type":"container"}]`)); err != nil {
t.Fatal(err)
}
got, err := inv.SentSummary(ctx, "anchor")
if err != nil || len(got) == 0 {
t.Fatalf("read back: %s %v", got, err)
}
}