Retire the networking bundle and what the control plane stops shipping (hq ADR 0226)
networking required mesh-wireguard and nothing else; machines are assigned the network directly. module forget refuses a provided module, so a retired one is removed at start once no machine has it. Guard route-proxy's public account directory against a reissue.
This commit is contained in:
@@ -284,6 +284,92 @@ func (i *Inventory) Provide(ctx context.Context, m catalogue.Manifest) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// RetireUnshipped removes every module the control plane recorded as its own and no longer ships.
|
||||
//
|
||||
// **`module forget` refuses a provided module**, rightly: the next start would put it back. So a
|
||||
// module the control plane stops shipping — `networking`, retired by novox/hq ADR 0226 — could be
|
||||
// removed by nothing at all, and would sit in the catalogue for ever, assignable and pointing at a
|
||||
// manifest no release carries. This is the other half of Provide: what this release ships is
|
||||
// recorded, and what it does not is taken away.
|
||||
//
|
||||
// **Never from under a machine.** A retired module still assigned somewhere is kept, and named in
|
||||
// `kept` with the machines it is on, so the caller can say "unassign it, and it goes at the next
|
||||
// start". Taking it while assigned would drop whatever it pulled in — for `networking`, the
|
||||
// private network itself — at the next push, with nobody having asked for that. Its settings,
|
||||
// secrets and ports are kept the same way: a provided module that holds any is kept and named,
|
||||
// because discarding them is a person's decision (novox/hq 04-ISSUES/017).
|
||||
func (i *Inventory) RetireUnshipped(ctx context.Context, shipped []string) (retired []string, kept map[string]string, err error) {
|
||||
keep := map[string]bool{}
|
||||
for _, s := range shipped {
|
||||
keep[s] = true
|
||||
}
|
||||
rows, err := i.store.Pool().Query(ctx,
|
||||
`select name from module where source = 'the control plane' order by name`)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var gone []string
|
||||
for rows.Next() {
|
||||
var name string
|
||||
if err := rows.Scan(&name); err != nil {
|
||||
rows.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
if !keep[name] {
|
||||
gone = append(gone, name)
|
||||
}
|
||||
}
|
||||
rows.Close()
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
kept = map[string]string{}
|
||||
for _, name := range gone {
|
||||
on, err := i.assignedOn(ctx, name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(on) > 0 {
|
||||
kept[name] = "still assigned on " + strings.Join(on, ", ") + "; unassign it and it goes at the next start"
|
||||
continue
|
||||
}
|
||||
held, err := i.HeldFor(ctx, name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if held.Any() {
|
||||
kept[name] = "the mesh still holds things for it:\n" + strings.Join(held.Lines(), "\n")
|
||||
continue
|
||||
}
|
||||
if err := i.discard(ctx, name); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
retired = append(retired, name)
|
||||
}
|
||||
return retired, kept, nil
|
||||
}
|
||||
|
||||
// assignedOn is the machines a module is assigned to, sorted.
|
||||
func (i *Inventory) assignedOn(ctx context.Context, name string) ([]string, error) {
|
||||
rows, err := i.store.Pool().Query(ctx,
|
||||
`select n.name from assignment a join node n on n.id = a.node where a.module = $1
|
||||
order by n.name`, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var on []string
|
||||
for rows.Next() {
|
||||
var node string
|
||||
if err := rows.Scan(&node); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
on = append(on, node)
|
||||
}
|
||||
return on, rows.Err()
|
||||
}
|
||||
|
||||
// Provided reports whether a module came with the control plane rather than from a repository.
|
||||
func (i *Inventory) Provided(ctx context.Context, name string) (bool, error) {
|
||||
var source *string
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
// What a release stops shipping is retired at the next start, and never from under a machine
|
||||
// (novox/hq ADR 0226). `module forget` refuses a provided module, so without this a module the
|
||||
// control plane no longer carries could be removed by nothing.
|
||||
|
||||
func TestAModuleTheControlPlaneNoLongerShipsIsRetired(t *testing.T) {
|
||||
inv := fresh(t)
|
||||
ctx := t.Context()
|
||||
for _, m := range []string{"mesh-wireguard", "networking"} {
|
||||
if err := inv.Provide(ctx, manifest(m, nil, nil)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
retired, kept, err := inv.RetireUnshipped(ctx, []string{"mesh-wireguard"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Join(retired, ",") != "networking" || len(kept) != 0 {
|
||||
t.Fatalf("retired %v, kept %v", retired, kept)
|
||||
}
|
||||
if _, err := inv.Provided(ctx, "networking"); !errors.Is(err, pgx.ErrNoRows) {
|
||||
t.Fatalf("networking is still in the catalogue: %v", err)
|
||||
}
|
||||
if provided, err := inv.Provided(ctx, "mesh-wireguard"); err != nil || !provided {
|
||||
t.Fatalf("what this release ships went too: %v %v", provided, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestARetiredModuleStillAssignedIsKeptAndSaidSo(t *testing.T) {
|
||||
inv := fresh(t)
|
||||
ctx := t.Context()
|
||||
if _, err := inv.AddNode(ctx, "anchor"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := inv.Provide(ctx, manifest("networking", nil, nil)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := inv.Assign(ctx, "anchor", "networking"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
retired, kept, err := inv.RetireUnshipped(ctx, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(retired) != 0 {
|
||||
// Taking it now would drop whatever it pulled in at the next push — for the bundle, the
|
||||
// private network.
|
||||
t.Fatalf("a module assigned on a machine was retired: %v", retired)
|
||||
}
|
||||
if !strings.Contains(kept["networking"], "anchor") {
|
||||
t.Fatalf("keeping it does not say where it is still assigned: %v", kept)
|
||||
}
|
||||
|
||||
// Unassigned, the next start takes it.
|
||||
if err := inv.Unassign(ctx, "anchor", "networking"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
retired, _, err = inv.RetireUnshipped(ctx, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Join(retired, ",") != "networking" {
|
||||
t.Fatalf("unassigned, it was still not retired: %v", retired)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAModuleFromARepositoryIsNeverRetiredByThis(t *testing.T) {
|
||||
// Only what the control plane recorded as its own. A module somebody registered is theirs to
|
||||
// forget.
|
||||
inv := fresh(t)
|
||||
ctx := t.Context()
|
||||
if err := inv.RegisterModule(ctx, manifest("public-acme", nil, nil), Source{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
retired, kept, err := inv.RetireUnshipped(ctx, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(retired) != 0 || len(kept) != 0 {
|
||||
t.Fatalf("a registered module was considered: retired %v, kept %v", retired, kept)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user