mesh/merge-gate head c79591531575 merged into trunk aa02e7bf4e78 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head c79591531575 merged into trunk aa02e7bf4e78 — pass: its merge-check.sh passed
mesh/delivery delivered
Milestone 5 moved 8,599 build records onto the bus under bare build ids, each carrying its module's whole manifest. Every build lookup then read the whole bucket, and a delivery plan's send did that per module per machine while it held the plans and the machine's declaration: the send of build-agent never finished, and every other act and the status waited behind it. Builds now live under <module>.build.<id> (requests under req.<id>, id.<id> names a build's module), with pointer keys newest, worked, held, commit.<sha> and source.<reuse key> that move only to a newer build and are read as hints, repaired when wrong. Manifests are kept once in the same bucket under manifest.sha256-<hex>, so no grant the running bus lacks is needed. The old keys are laid out once at start, idempotently, and left unread. The calls a plan look makes on the silent clocks while it holds the plans are bounded, since the vendored kv.Keys ignores its context.
186 lines
5.8 KiB
Go
186 lines
5.8 KiB
Go
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
"github.com/novox/mesh-controller/internal/chain"
|
|
"github.com/novox/mesh-controller/internal/store"
|
|
)
|
|
|
|
// ForTest is a fresh inventory in a database of its own, dropped when the test ends.
|
|
//
|
|
// Exported because the check that this mesh reads what a node sends lives beside the link, and a
|
|
// second copy of this would be a second thing to keep true. It takes a *testing.T, so it cannot
|
|
// be called from anything that is not a test.
|
|
func ForTest(t *testing.T) *Inventory {
|
|
t.Helper()
|
|
admin := os.Getenv("MESH_TEST_POSTGRES")
|
|
if admin == "" {
|
|
t.Skip("no MESH_TEST_POSTGRES; run `make check` to raise one")
|
|
}
|
|
name := fmt.Sprintf("inv_%d_%s", time.Now().UnixNano()%1_000_000,
|
|
strings.ToLower(strings.NewReplacer("/", "", "-", "").Replace(t.Name())))
|
|
if len(name) > 60 {
|
|
name = name[:60]
|
|
}
|
|
|
|
// Created already migrated, a copy of a template migrated once (novox/hq issue 591); the Migrate
|
|
// below then finds nothing pending, which it does only if the copy's record matches every migration.
|
|
migrations, err := Migrations()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store.CreateMigratedForTest(t, admin, name, migrations)
|
|
busPasswordCostForTest.Store(int32(bcrypt.MinCost))
|
|
|
|
cut := strings.LastIndex(admin, "/")
|
|
t.Setenv(store.Variable(Name), admin[:cut]+"/"+name+"?sslmode=disable")
|
|
|
|
inv, err := Open(t.Context())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
inv.Close()
|
|
c, err := pgx.Connect(context.Background(), admin)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer c.Close(context.Background())
|
|
_, _ = c.Exec(context.Background(), "drop database if exists "+name+" with (force)")
|
|
})
|
|
if err := inv.Ready(t.Context(), 20*time.Second); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := inv.store.Migrate(t.Context(), migrations); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The chain is on the bus in a mesh; in a test it is in memory, with every kind moved, as a mesh raised after this
|
|
// release has them (ADR 0408, issue 1089). The tests of the move itself use ForTestBefore.
|
|
c := chain.InMemory(nil)
|
|
inv.UseChain(c)
|
|
testChains.Store(os.Getenv(store.Variable(Name)), c)
|
|
t.Cleanup(func() { testChains.Delete(os.Getenv(store.Variable(Name))) })
|
|
if _, err := inv.store.Pool().Exec(t.Context(),
|
|
`insert into chain_moved (counts, largest) values ($1, $1)`, `{"builds": 0, "builds-layout": 0, "rollouts": 0, "assignments": 0}`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return inv
|
|
}
|
|
|
|
// RemoveNodeForTest removes a machine's record as a person removing it from the store would: no verb of the
|
|
// mesh removes one yet, and what hangs off it must go with it.
|
|
func (i *Inventory) RemoveNodeForTest(ctx context.Context, name string) (bool, error) {
|
|
tag, err := i.store.Pool().Exec(ctx, `delete from node where name = $1`, name)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return tag.RowsAffected() == 1, nil
|
|
}
|
|
|
|
// ForTestBefore is ForTest with the store migrated only below migration `number`, and a function that runs the
|
|
// rest: for a test of a migration that moves data, which seeds the store as the mesh holds it before the
|
|
// migration, migrates, and reads what the migration made of it (novox/hq issue 651).
|
|
func ForTestBefore(t *testing.T, number int) (*Inventory, func()) {
|
|
t.Helper()
|
|
admin := os.Getenv("MESH_TEST_POSTGRES")
|
|
if admin == "" {
|
|
t.Skip("no MESH_TEST_POSTGRES; run `make check` to raise one")
|
|
}
|
|
name := fmt.Sprintf("invb_%d_%s", time.Now().UnixNano()%1_000_000,
|
|
strings.ToLower(strings.NewReplacer("/", "", "-", "").Replace(t.Name())))
|
|
if len(name) > 60 {
|
|
name = name[:60]
|
|
}
|
|
all, err := Migrations()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var before []store.Migration
|
|
for _, m := range all {
|
|
if m.Number < number {
|
|
before = append(before, m)
|
|
}
|
|
}
|
|
store.CreateMigratedForTest(t, admin, name, before)
|
|
busPasswordCostForTest.Store(int32(bcrypt.MinCost))
|
|
cut := strings.LastIndex(admin, "/")
|
|
t.Setenv(store.Variable(Name), admin[:cut]+"/"+name+"?sslmode=disable")
|
|
inv, err := Open(t.Context())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
inv.Close()
|
|
c, err := pgx.Connect(context.Background(), admin)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer c.Close(context.Background())
|
|
_, _ = c.Exec(context.Background(), "drop database if exists "+name+" with (force)")
|
|
})
|
|
if err := inv.Ready(t.Context(), 20*time.Second); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The chain is in memory and nothing is moved: the tests of a move seed the tables and move them.
|
|
c := chain.InMemory(nil)
|
|
inv.UseChain(c)
|
|
testChains.Store(os.Getenv(store.Variable(Name)), c)
|
|
t.Cleanup(func() { testChains.Delete(os.Getenv(store.Variable(Name))) })
|
|
return inv, func() {
|
|
t.Helper()
|
|
if _, err := inv.store.Migrate(t.Context(), all); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ExecForTest runs one statement against a test's store, for a fixture no verb of the mesh writes: a row as an
|
|
// older migration left it.
|
|
func (i *Inventory) ExecForTest(t *testing.T, sql string, args ...any) {
|
|
t.Helper()
|
|
if _, err := i.store.Pool().Exec(t.Context(), sql, args...); err != nil {
|
|
t.Fatalf("%s: %v", sql, err)
|
|
}
|
|
}
|
|
|
|
// QueryForTest reads rows of a test's store, each row's columns as JSON text, for a test that checks what a
|
|
// migration left.
|
|
func (i *Inventory) QueryForTest(t *testing.T, sql string, args ...any) [][]string {
|
|
t.Helper()
|
|
rows, err := i.store.Pool().Query(t.Context(), sql, args...)
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", sql, err)
|
|
}
|
|
defer rows.Close()
|
|
var out [][]string
|
|
for rows.Next() {
|
|
values, err := rows.Values()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
row := make([]string, len(values))
|
|
for k, v := range values {
|
|
row[k] = fmt.Sprint(v)
|
|
if b, err := json.Marshal(v); err == nil {
|
|
row[k] = string(b)
|
|
}
|
|
}
|
|
out = append(out, row)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return out
|
|
}
|