carlos / platform Public

host: the offline move world moves into e2e_bridge.go, an untagged file

The two-box world the offline move tests share (offWorld and offBox, the
seal and land worlds' types and arming, the agent environments, the fake
wake backend, shipVersion and mustJSON) moves out of the _test.go files
into internal/host/e2e_bridge.go, unchanged but for testing.TB in place of
*testing.T, so a test in another package can build on it. Test-only
methods stay where they were.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev ae84e1a3bcc45deddf5050825f75cf05ed7400f8 parent 6a93125
8 files changed, +664 −616
  • internal/host/e2e_bridge.go +660 −0
  • internal/host/host_test.go +0 −14
  • internal/host/movesoffline_e2e_test.go +0 −77
  • internal/host/movesoffline_land_test.go +2 −111
  • internal/host/movesoffline_seal_test.go +0 −150
  • internal/host/movesoffline_test.go +2 −144
  • internal/host/movesprobeowners_test.go +0 −9
  • internal/host/schedule_wake_test.go +0 −111
diff --git a/internal/host/e2e_bridge.go b/internal/host/e2e_bridge.go
new file mode 100644
index 000000000..b4e684992
--- /dev/null
+++ b/internal/host/e2e_bridge.go
@@ -0,0 +1,660 @@
+package host
+
+// The two-box offline move world, exported for the end-to-end witnesses
+// (self-serve moves plan 2 Task 10: internal/e2e/selfserve, under the e2e
+// build tag) and shared with this package's own offline move tests.
+//
+// It lives in a non-test file so that a test in another package can import
+// it, and in package host so that it reaches movesWorker, moveCfg,
+// newMovesWorker, armOffline and reconcileSerializedWith directly: nothing
+// private moves out of the package and no import cycle arises. It is dead
+// code in a production binary (stated and accepted by the plan): nothing
+// outside tests calls it.
+//
+// The world is the offline move tests' own (movesoffline_test.go and the
+// seal, land and end-to-end tests), moved here unchanged but for testing.TB
+// in place of testing.TB: offWorld and offBox, sealWorld, landWorld, the
+// fake wake backend, shipVersion and mustJSON.
+
+import (
+ "context"
+ "crypto/ecdh"
+ "crypto/ed25519"
+ "database/sql"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/carlosframework/platform/internal/activator"
+ "github.com/carlosframework/platform/internal/appconfig"
+ "github.com/carlosframework/platform/internal/cgroup"
+ "github.com/carlosframework/platform/internal/hostwrite"
+ "github.com/carlosframework/platform/internal/instancekey"
+ "github.com/carlosframework/platform/internal/lease"
+ "github.com/carlosframework/platform/internal/lease/leasetest"
+ "github.com/carlosframework/platform/internal/moves"
+ "github.com/carlosframework/platform/internal/provision"
+ "github.com/carlosframework/platform/internal/registry"
+ "github.com/carlosframework/platform/internal/release"
+)
+
+// offHost is the one instance every offline world moves.
+const offHost = "shop.example.com"
+
+type offWorld struct {
+ t testing.TB
+ s release.Store
+ acct string
+ mu sync.Mutex
+ now time.Time
+ // judge, when set, is every box's lifetime judge, wired as the agent
+ // wires newBoxJudge's (the moves worker, its offline view and the
+ // reconcile pass). Nil: the boxes judge nothing, as before.
+ judge *provision.LifetimeJudge
+}
+
+func (wd *offWorld) clock() time.Time {
+ wd.mu.Lock()
+ defer wd.mu.Unlock()
+ return wd.now
+}
+
+func (wd *offWorld) advance(d time.Duration) {
+ wd.mu.Lock()
+ wd.now = wd.now.Add(d)
+ wd.mu.Unlock()
+}
+
+func (wd *offWorld) create(host string) {
+ wd.t.Helper()
+ rec := provision.Record{Schema: 1, Host: host, Slug: strings.SplitN(host, ".", 2)[0], Account: wd.acct, App: "app", Region: "", Channel: "stable", CreatedAt: wd.now}
+ if err := provision.CreateRecord(context.Background(), wd.s, rec); err != nil {
+ wd.t.Fatal(err)
+ }
+}
+
+// offBox is one box: its own data directory, registry, activator and moves
+// worker, armed as armMovesPass arms it (armOffline).
+type offBox struct {
+ wd *offWorld
+ name, region string
+ dd, opt, cg string
+ env map[string]string
+ reg *registry.Registry
+ svc *activator.Service
+ w *movesWorker
+ nested bool
+ last []byte
+}
+
+func (wd *offWorld) box(name, region string, env map[string]string) *offBox {
+ wd.t.Helper()
+ b := &offBox{wd: wd, name: name, region: region, dd: wd.t.TempDir(), opt: wd.t.TempDir(), cg: wd.t.TempDir(), env: env}
+ reg, err := registry.Open(filepath.Join(b.dd, "registry.db"))
+ if err != nil {
+ wd.t.Fatal(err)
+ }
+ wd.t.Cleanup(func() { reg.Close() })
+ b.reg = reg
+ b.restart()
+ return b
+}
+
+func (b *offBox) getenv(k string) string {
+ if v, ok := b.env[k]; ok {
+ return v
+ }
+ if k == "CARLOS_HIBERNATE_BUCKET" {
+ return "data"
+ }
+ return ""
+}
+
+// restart is an agent restart: a fresh activator (no fences) and a fresh
+// worker over the same registry and data directory.
+func (b *offBox) restart() {
+ b.svc = activator.New(activator.Config{Bucket: "data", Idle: time.Hour}, &fakeWakeBackend{}, b.reg)
+ cfg := moveCfg{Judge: b.wd.judge, Region: b.region, Box: b.name, DataDir: b.dd, OptDir: b.opt, Quiesced: b.svc.Quiesced, Route: b.reg.Get, Now: b.wd.clock}
+ armOffline(&cfg, b.dd, b.reg, b.svc, nil, hostwrite.Config{}, b.getenv)
+ // The stage-requested marker is root's business, and its throttled
+ // timer would outlive the test's directories.
+ cfg.Offline = newOfflineView(cfg, nil)
+ cfg.ReleaseHold = releaseHeld(b.reg, nil)
+ cfg.ReleaseHoldSeq = releaseHeldSeq(b.reg, nil)
+ cfg.Procs = func(host string) (int, error) { return cgroup.HostProcs(b.cg, host, "") }
+ b.w = newMovesWorker(cfg)
+}
+
+func (b *offBox) reconcile() {
+ b.wd.t.Helper()
+ cfg := provision.ReconcileConfig{Region: b.region, DataDir: b.dd, Judge: b.wd.judge}
+ if b.nested {
+ cfg.Layout = func(context.Context, release.Store) (string, error) { return release.LayoutModeNested, nil }
+ } else {
+ cfg.Layout = func(context.Context, release.Store) (string, error) { return release.LayoutModeFlat, nil }
+ }
+ if err := reconcileSerializedWith(context.Background(), b.wd.s, b.reg, cfg, b.w); err != nil {
+ b.wd.t.Fatal(err)
+ }
+}
+
+func (b *offBox) edges() {
+ b.wd.t.Helper()
+ if err := edgesFileWrite(context.Background(), b.wd.s, b.reg, edgesFile{Box: b.name, Placement: b.region}, "", &b.last, false); err != nil {
+ b.wd.t.Fatal(err)
+ }
+}
+
+func (b *offBox) pass() {
+ b.wd.t.Helper()
+ if err := movesPass(context.Background(), b.wd.s, b.w); err != nil {
+ b.wd.t.Fatal(err)
+ }
+}
+
+// tick is the agent's order on a serial bump: reconcile, the edges file,
+// the moves pass.
+func (b *offBox) tick() {
+ b.wd.t.Helper()
+ b.reconcile()
+ b.edges()
+ b.pass()
+}
+
+func (b *offBox) row(host string) *registry.Route {
+ b.wd.t.Helper()
+ rt, err := b.reg.Get(host)
+ if err != nil {
+ b.wd.t.Fatal(err)
+ }
+ return rt
+}
+
+func writeFile(t testing.TB, p, body string) {
+ t.Helper()
+ if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
+ t.Fatal(err)
+ }
+}
+
+const (
+ sealIE = "data-ie"
+ sealUK = "data-uk"
+ sealIERegion = "eu-west-1"
+ sealUKRegion = "eu-west-2"
+ sealPrefix = "instances"
+ sealVersion = "v42"
+)
+
+type sealWorld struct {
+ *offWorld
+ fake *leasetest.VersionedBucket
+ keyB64 string
+ priv *ecdh.PrivateKey
+ ie, uk *offBox
+ pages []string
+ bodies []string // each page's body, beside pages
+}
+
+// sourceBox is a home box armed as armMovesPass arms one: armOffline, then
+// the hibernate coordinates, the S3 mirror, the production checkpoint and
+// the box's exec backend for the launch environment.
+func (sw *sealWorld) sourceBox(name string) *offBox {
+ b := sw.box(name, "", map[string]string{"CARLOS_HIBERNATE_BUCKET": sealIE, "CARLOS_SECRETS_KEY": sw.keyB64})
+ sw.arm(b)
+ return b
+}
+
+func (sw *sealWorld) arm(b *offBox) {
+ c := &b.w.cfg
+ c.OwnBucket, c.OwnBucketRegion, c.OwnPrefix = sealIE, sealIERegion, sealPrefix
+ c.Mirror = func(ctx context.Context, dst string) (lease.MirrorStore, error) {
+ return lease.NewS3Mirror(ctx, sealIERegion, dst)
+ }
+ // armMovesPass's Checkpoint, as it is wired there.
+ c.Checkpoint = func(ctx context.Context, dbPath string) error {
+ host := strings.TrimSuffix(filepath.Base(dbPath), ".db")
+ rep, err := lease.ReplicateWithLease(ctx, dbPath, sealIE, activator.HibernatePrefix(sealPrefix, host), sealIERegion)
+ if err != nil {
+ return err
+ }
+ defer rep.Close(ctx)
+ return rep.SyncAndWait(ctx)
+ }
+ c.CarryRequest = func(ctx context.Context, host, bucket, prefix string) error {
+ sw.fake.Put(bucket, prefix+"/build-state.json", []byte(`{"request":"carried"}`))
+ return nil
+ }
+ c.LaunchLayers = (&activator.ExecBackend{DataDir: b.dd}).LaunchLayers
+ c.Page = func(_ context.Context, level, subject, body string) error {
+ sw.pages = append(sw.pages, level+": "+subject)
+ sw.bodies = append(sw.bodies, body)
+ return nil
+ }
+}
+
+func (sw *sealWorld) dbPath(b *offBox) string {
+ sw.t.Helper()
+ rt := b.row(offHost)
+ if rt == nil {
+ sw.t.Fatal("no row")
+ }
+ return rt.DBPath
+}
+
+// instance gives b a live instance of offHost, as a box that has served it
+// has: the row (minted ready by reconcile), its replication rung, next due
+// and timers, a database written through lease.ReplicateWithLease into
+// data-ie, the files directory, <host>.env with an instance key, the
+// rendered configuration and a live build.
+func (sw *sealWorld) instance(b *offBox) {
+ t := sw.t
+ t.Helper()
+ b.tick()
+ rt := b.row(offHost)
+ if rt == nil || rt.HoldOp != "" {
+ t.Fatalf("fixture: row %+v", rt)
+ }
+ if err := b.reg.SetReplication(offHost, registry.ReplicationBatched); err != nil {
+ t.Fatal(err)
+ }
+ if err := b.reg.SetNextDue(offHost, 1_800_000_000); err != nil {
+ t.Fatal(err)
+ }
+ for _, tm := range []registry.Timer{
+ {Host: offHost, Name: "nightly", Kind: "cron", Spec: "0 3 * * *", Path: "/jobs/nightly", NextAt: 1_800_000_100},
+ {Host: offHost, Name: "reminder", Kind: "once", Path: "/jobs/remind", NextAt: 1_800_000_200},
+ {Host: offHost, Name: "fired", Kind: "once", Path: "/jobs/fired", LastAt: 1_700_000_000, LastStatus: "200"},
+ } {
+ if err := b.reg.UpsertTimer(tm); err != nil {
+ t.Fatal(err)
+ }
+ }
+ writeFile(t, filepath.Join(b.dd, offHost, "instance.key"), "app key file")
+ writeFile(t, filepath.Join(b.dd, offHost, "uploads", "a.png"), "png bytes")
+ writeFile(t, filepath.Join(b.dd, offHost+".env"), "CARLOS_INSTANCE_KEY=the-instance-key\nCARLOS_ADMIN_TOKEN=token\n")
+ writeFile(t, filepath.Join(b.dd, offHost+".config.env"), "APP_COLOUR=green\n")
+ sw.writeRows(b, rt.DBPath, 0, 20, true)
+ versions := filepath.Join(b.opt, "versions", sealVersion, sw.acct, "app")
+ if err := os.MkdirAll(versions, 0o755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.MkdirAll(filepath.Dir(release.LiveLink(b.opt, offHost)), 0o755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Symlink(versions, release.LiveLink(b.opt, offHost)); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// writeRows inserts rows [from, to) into the database at dbPath, then, with
+// replicate, syncs it to data-ie under the lease as a wake's stop does.
+func (sw *sealWorld) writeRows(b *offBox, dbPath string, from, to int, replicate bool) {
+ t := sw.t
+ t.Helper()
+ db, err := sql.Open("sqlite", dbPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, q := range []string{`PRAGMA journal_mode=WAL`, `CREATE TABLE IF NOT EXISTS t (id INTEGER PRIMARY KEY, body TEXT)`} {
+ if _, err := db.Exec(q); err != nil {
+ t.Fatal(err)
+ }
+ }
+ for i := from; i < to; i++ {
+ if _, err := db.Exec(`INSERT INTO t (id, body) VALUES (?, ?)`, i, fmt.Sprintf("row-%d", i)); err != nil {
+ t.Fatal(err)
+ }
+ }
+ if err := db.Close(); err != nil {
+ t.Fatal(err)
+ }
+ if !replicate {
+ return
+ }
+ ctx := context.Background()
+ rep, err := lease.ReplicateWithLease(ctx, dbPath, sealIE, activator.HibernatePrefix(sealPrefix, offHost), sealIERegion)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := rep.SyncAndWait(ctx); err != nil {
+ t.Fatal(err)
+ }
+ if err := rep.Close(ctx); err != nil {
+ t.Fatal(err)
+ }
+}
+
+const (
+ landKeyValue = "the-instance-key" // the key sealWorld.instance writes
+ landStore = "shop-store-ie"
+ landSecret = "store-secret-value"
+)
+
+// landS3 reads the data buckets' model directly.
+type landS3 struct{ f *leasetest.VersionedBucket }
+
+// landOpts shapes the source: configKey sets the instance key in the app's
+// configuration (and none in <host>.env), noFiles gives the host no files
+// directory, nested makes it a nested row with the launch descriptors a real
+// nested host keeps.
+type landOpts struct {
+ configKey string
+ noFiles bool
+ nested bool
+ // judged arms every box with a lifetime judge, as a claims-off agent
+ // arms newBoxJudge's (the end-to-end test's claims-off variant).
+ judged bool
+ // lifetime is judged with the record created through its lifetime: the
+ // host's incarnation slot at 1, live, then the record at incarnation 1
+ // (the create protocol's order, self-serve moves plan 1 Task 3).
+ lifetime bool
+ // incarnation creates the record at that incarnation with no slot, as
+ // a claims-on box (no judge) sees one; only without judged or lifetime.
+ incarnation uint64
+}
+
+// landWorld is offHost at home (ie-a, data-ie) moving to uk (uk-box,
+// data-uk), with a real database replica, a files directory, an env file
+// carrying the instance key, an app config bundle carrying the store
+// binding, timers, a NextDue, a replication rung and a promoted release,
+// sealed by the real sealer.
+type landWorld struct {
+ t testing.TB
+ sw *sealWorld
+ wd *offWorld
+ s3 *landS3
+ ie *offBox
+ uk *offBox
+ ukEB *activator.ExecBackend
+
+ relPriv ed25519.PrivateKey
+
+ seal moves.OfflineReport
+ sealRaw []byte
+ src provision.MoveSource
+
+ opts landOpts
+ // The source's files directory, env file and config file.
+ srcFiles, srcEnv, srcConfig string
+}
+
+// arm wires the target box as armMovesPass would, over its own data bucket.
+func (lw *landWorld) arm(b *offBox) {
+ bucket, region := b.getenv("CARLOS_HIBERNATE_BUCKET"), b.getenv("CARLOS_HIBERNATE_REGION")
+ b.svc = activator.New(activator.Config{Bucket: bucket, Idle: time.Hour}, &fakeWakeBackend{}, b.reg)
+ cfg := moveCfg{Judge: b.wd.judge, Region: b.region, Box: b.name, DataDir: b.dd, OptDir: b.opt, Quiesced: b.svc.Quiesced, Route: b.reg.Get, Now: b.wd.clock,
+ OwnBucket: bucket, OwnBucketRegion: region, OwnPrefix: sealPrefix,
+ Mirror: func(ctx context.Context, dst string) (lease.MirrorStore, error) {
+ return lease.NewS3Mirror(ctx, region, dst)
+ }}
+ armOffline(&cfg, b.dd, b.reg, b.svc, nil, hostwrite.Config{}, b.getenv)
+ cfg.Offline = newOfflineView(cfg, nil)
+ cfg.ReleaseHold = releaseHeld(b.reg, nil)
+ cfg.ReleaseHoldSeq = releaseHeldSeq(b.reg, nil)
+ cfg.Procs = func(host string) (int, error) { return cgroup.HostProcs(b.cg, host, "") }
+ cfg.LaunchLayers = lw.ukEB.LaunchLayers
+ b.w = newMovesWorker(cfg)
+}
+
+// restart is an agent restart on the target: a fresh activator and worker
+// over the same registry, data directory and buckets.
+func (lw *landWorld) restart() { lw.arm(lw.uk) }
+
+func (lw *landWorld) promote(v string) {
+ lw.t.Helper()
+ if err := release.Promote(context.Background(), lw.wd.s, lw.relPriv, lw.wd.acct, "app", "stable", v); err != nil {
+ lw.t.Fatal(err)
+ }
+}
+
+// bundle writes the app's config: the store binding, its secret and a
+// setting, and the instance key when the world's configuration sets it.
+func (lw *landWorld) bundle(store string) {
+ lw.t.Helper()
+ env, err := appconfig.SealBound(lw.sw.priv.PublicKey(), appconfig.Binding{Account: lw.wd.acct, App: "app"}, []byte(landSecret))
+ if err != nil {
+ lw.t.Fatal(err)
+ }
+ m := appconfig.Manifest{Schema: 1, App: "app", Vars: map[string]appconfig.Var{
+ "STORE_BUCKET": {Kind: "env", Value: store},
+ "STORE_ENDPOINT": {Kind: "env", Value: "https://store.example.com"},
+ "STORE_SECRET_KEY": {Kind: "secret", Envelope: env},
+ "FEATURE": {Kind: "env", Value: "on"},
+ }}
+ if lw.opts.configKey != "" {
+ m.Vars[instancekey.Var] = appconfig.Var{Kind: "env", Value: lw.opts.configKey}
+ }
+ if err := moves.PutReplace(context.Background(), lw.wd.s, appconfig.Key(lw.wd.acct, "app"), mustJSON(lw.t, m)); err != nil {
+ lw.t.Fatal(err)
+ }
+}
+
+// The agent environments of the two boxes: the variables measured on dev
+// and flagship that differ from box to box (97f541f2). Each box's launch
+// composer sees its own.
+var (
+ e2eIEAgentEnv = map[string]string{
+ "SYSTEMD_EXEC_PID": "1101",
+ "MEMORY_PRESSURE_WATCH": "/sys/fs/cgroup/system.slice/carlos-edge.service/memory.pressure",
+ "INVOCATION_ID": "0f0e0d0c0b0a09080706050403020100",
+ "AWS_REGION": sealIERegion,
+ "STATUS_BOX_REGION": sealIERegion,
+ "PLATFORM_EDGE_IPS": "192.0.2.10",
+ }
+ e2eUKAgentEnv = map[string]string{
+ "SYSTEMD_EXEC_PID": "2202",
+ "MEMORY_PRESSURE_WATCH": "/sys/fs/cgroup/system.slice/carlos-edge.service/memory.pressure.uk",
+ "INVOCATION_ID": "00010203040506070809",
+ "AWS_REGION": sealUKRegion,
+ "STATUS_BOX_REGION": sealUKRegion,
+ "PLATFORM_EDGE_IPS": "198.51.100.20,198.51.100.21",
+ }
+)
+
+// envSwapMu serializes the agent-environment swap: the composer is called
+// from one goroutine at a time in this test, and the lock keeps it so.
+var envSwapMu sync.Mutex
+
+// withAgentEnv runs compose with env as the process's agent environment,
+// restored after the call.
+func withAgentEnv(env map[string]string, compose func(registry.Route) (map[string]string, map[string]bool, []string, error)) func(registry.Route) (map[string]string, map[string]bool, []string, error) {
+ return func(rt registry.Route) (map[string]string, map[string]bool, []string, error) {
+ envSwapMu.Lock()
+ defer envSwapMu.Unlock()
+ type prev struct {
+ v string
+ ok bool
+ }
+ old := map[string]prev{}
+ for k, v := range env {
+ pv, ok := os.LookupEnv(k)
+ old[k] = prev{pv, ok}
+ os.Setenv(k, v)
+ }
+ defer func() {
+ for k, p := range old {
+ if p.ok {
+ os.Setenv(k, p.v)
+ } else {
+ os.Unsetenv(k)
+ }
+ }
+ }()
+ return compose(rt)
+ }
+}
+
+// createLifetime creates host's record through its lifetime, in the create
+// protocol's order: the host incarnation slot at inc, live, then the record
+// carrying incarnation inc.
+func (wd *offWorld) createLifetime(host string, inc uint64) {
+ wd.t.Helper()
+ ctx := context.Background()
+ if _, err := provision.WriteHostIncarnation(ctx, wd.s, host,
+ provision.HostIncarnation{Account: wd.acct, App: "app", Incarnation: inc, State: provision.SlotLive, Intent: "create-e2e"}, ""); err != nil {
+ wd.t.Fatal(err)
+ }
+ wd.createAt(host, inc)
+}
+
+// createAt creates host's record carrying incarnation inc, with no slot.
+func (wd *offWorld) createAt(host string, inc uint64) {
+ wd.t.Helper()
+ rec := provision.Record{Schema: 1, Host: host, Slug: strings.SplitN(host, ".", 2)[0], Account: wd.acct, App: "app", Region: "", Channel: "stable", CreatedAt: wd.now, Incarnation: inc}
+ if err := provision.CreateRecord(context.Background(), wd.s, rec); err != nil {
+ wd.t.Fatal(err)
+ }
+}
+
+// fakeInstanceProc is the spawned child: stopping it takes the socket away,
+// which is what "hibernated" means to anything downstream.
+type fakeInstanceProc struct {
+ inst *fakeInstance
+}
+
+func (p *fakeInstanceProc) Stop(ctx context.Context) error { return p.inst.stop(ctx) }
+
+func (p *fakeInstanceProc) Exited() bool { return !p.inst.running() }
+
+// fakeInstance is an app listening on a unix socket, started and stopped by
+// the backend below. It records every request it served.
+type fakeInstance struct {
+ socket string
+ handler http.Handler
+
+ mu sync.Mutex
+ srv *http.Server
+ starts int
+ conns int // accepted connections — the transport-reuse assertion
+ requests []*http.Request
+}
+
+func (f *fakeInstance) running() bool {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+ return f.srv != nil
+}
+
+func (f *fakeInstance) start() error {
+ ln, err := net.Listen("unix", f.socket)
+ if err != nil {
+ return err
+ }
+ srv := &http.Server{
+ Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ f.mu.Lock()
+ f.requests = append(f.requests, r.Clone(context.Background()))
+ f.mu.Unlock()
+ f.handler.ServeHTTP(w, r)
+ }),
+ ConnState: func(_ net.Conn, st http.ConnState) {
+ if st == http.StateNew {
+ f.mu.Lock()
+ f.conns++
+ f.mu.Unlock()
+ }
+ },
+ }
+ f.mu.Lock()
+ f.srv = srv
+ f.starts++
+ f.mu.Unlock()
+ go srv.Serve(ln)
+ return nil
+}
+
+func (f *fakeInstance) stop(ctx context.Context) error {
+ f.mu.Lock()
+ srv := f.srv
+ f.srv = nil
+ f.mu.Unlock()
+ if srv == nil {
+ return nil
+ }
+ return srv.Close()
+}
+
+// fakeWakeBackend is activator.Backend over one fakeInstance: no S3, no
+// processes, no replication — just the spawn/stop the wake path drives.
+type fakeWakeBackend struct{ inst *fakeInstance }
+
+type nopLease struct{}
+
+func (nopLease) Replicate(context.Context, string, time.Duration) (activator.Rep, error) {
+ return nopRep{}, nil
+}
+
+func (nopLease) Release(context.Context) error { return nil }
+
+type nopRep struct{}
+
+func (nopRep) Err() error { return nil }
+
+func (nopRep) Close(context.Context) error { return nil }
+
+func (b *fakeWakeBackend) Acquire(context.Context, registry.Route) (activator.Lease, error) {
+ return nopLease{}, nil
+}
+
+func (b *fakeWakeBackend) Restore(context.Context, registry.Route) (bool, error) { return false, nil }
+
+func (b *fakeWakeBackend) Seed(context.Context, registry.Route) error { return nil }
+
+func (b *fakeWakeBackend) Start(_ context.Context, _ registry.Route) (activator.Proc, string, error) {
+ if err := b.inst.start(); err != nil {
+ return nil, "", err
+ }
+ return &fakeInstanceProc{inst: b.inst}, "", nil
+}
+
+func (b *fakeWakeBackend) NextDue(context.Context, registry.Route) (int64, error) { return 0, nil }
+
+func (b *fakeWakeBackend) HasSidecar(registry.Route) bool { return false }
+
+func (b *fakeWakeBackend) StartSidecar(context.Context, registry.Route) (activator.Proc, error) {
+ return nil, nil
+}
+
+// StartWorker: this fixture drives the INSTANCE wake path, so nothing here
+// ever asks for a worker. Refusing rather than returning a nil Proc is the
+// safer stub — a nil Proc would be dereferenced by the caller and read as a
+// crash inside the wake, which is a confusing way to learn a test wandered
+// into the wrong door.
+func (b *fakeWakeBackend) StartWorker(context.Context, registry.Route, map[string]string) (activator.Proc, string, error) {
+ return nil, "", errors.New("fakeWakeBackend: no worker path in this fixture")
+}
+
+// shipVersion ships a minimal one-artifact release so Promote has something
+// real to point at — release.Promote refuses to point a channel at a
+// version nothing shipped.
+func shipVersion(t testing.TB, s release.Store, acct, app, version string) {
+ t.Helper()
+ m := release.Manifest{
+ Schema: 1, App: app, Version: version, Kind: "binary",
+ Artifacts: map[string]release.Artifact{"linux-amd64": {File: app}},
+ }
+ if err := release.Ship(context.Background(), s, acct, m, map[string][]byte{"linux-amd64": []byte("bin")}); err != nil {
+ t.Fatalf("Ship %s/%s: %v", app, version, err)
+ }
+}
+
+func mustJSON(t testing.TB, v any) []byte {
+ t.Helper()
+ b, err := json.Marshal(v)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return b
+}
diff --git a/internal/host/host_test.go b/internal/host/host_test.go
index c269f4fee..6d02eef08 100644
--- a/internal/host/host_test.go
+++ b/internal/host/host_test.go
@@ -1601,20 +1601,6 @@ func TestCountExpiredCanariesReadsEachChannelOnce(t *testing.T) {
}
}
-// shipVersion ships a minimal one-artifact release so Promote has something
-// real to point at — release.Promote refuses to point a channel at a
-// version nothing shipped.
-func shipVersion(t *testing.T, s release.Store, acct, app, version string) {
- t.Helper()
- m := release.Manifest{
- Schema: 1, App: app, Version: version, Kind: "binary",
- Artifacts: map[string]release.Artifact{"linux-amd64": {File: app}},
- }
- if err := release.Ship(context.Background(), s, acct, m, map[string][]byte{"linux-amd64": []byte("bin")}); err != nil {
- t.Fatalf("Ship %s/%s: %v", app, version, err)
- }
-}
-
// Both restore drills must write their restored copies to DISK, never to the
// PrivateTmp tmpfs. On tmpfs the restored database is charged to the unit's
// own cgroup as memory, so the drill's ceiling has to exceed the fleet's
diff --git a/internal/host/movesoffline_e2e_test.go b/internal/host/movesoffline_e2e_test.go
index 10f2327ab..06f0d724a 100644
--- a/internal/host/movesoffline_e2e_test.go
+++ b/internal/host/movesoffline_e2e_test.go
@@ -79,61 +79,6 @@ import (
const e2eAddr = "ops@example.com"
-// The agent environments of the two boxes: the variables measured on dev
-// and flagship that differ from box to box (97f541f2). Each box's launch
-// composer sees its own.
-var (
- e2eIEAgentEnv = map[string]string{
- "SYSTEMD_EXEC_PID": "1101",
- "MEMORY_PRESSURE_WATCH": "/sys/fs/cgroup/system.slice/carlos-edge.service/memory.pressure",
- "INVOCATION_ID": "0f0e0d0c0b0a09080706050403020100",
- "AWS_REGION": sealIERegion,
- "STATUS_BOX_REGION": sealIERegion,
- "PLATFORM_EDGE_IPS": "192.0.2.10",
- }
- e2eUKAgentEnv = map[string]string{
- "SYSTEMD_EXEC_PID": "2202",
- "MEMORY_PRESSURE_WATCH": "/sys/fs/cgroup/system.slice/carlos-edge.service/memory.pressure.uk",
- "INVOCATION_ID": "00010203040506070809",
- "AWS_REGION": sealUKRegion,
- "STATUS_BOX_REGION": sealUKRegion,
- "PLATFORM_EDGE_IPS": "198.51.100.20,198.51.100.21",
- }
-)
-
-// envSwapMu serializes the agent-environment swap: the composer is called
-// from one goroutine at a time in this test, and the lock keeps it so.
-var envSwapMu sync.Mutex
-
-// withAgentEnv runs compose with env as the process's agent environment,
-// restored after the call.
-func withAgentEnv(env map[string]string, compose func(registry.Route) (map[string]string, map[string]bool, []string, error)) func(registry.Route) (map[string]string, map[string]bool, []string, error) {
- return func(rt registry.Route) (map[string]string, map[string]bool, []string, error) {
- envSwapMu.Lock()
- defer envSwapMu.Unlock()
- type prev struct {
- v string
- ok bool
- }
- old := map[string]prev{}
- for k, v := range env {
- pv, ok := os.LookupEnv(k)
- old[k] = prev{pv, ok}
- os.Setenv(k, v)
- }
- defer func() {
- for k, p := range old {
- if p.ok {
- os.Setenv(k, p.v)
- } else {
- os.Unsetenv(k)
- }
- }
- }()
- return compose(rt)
- }
-}
-
// recordingStore is the deployment store every party uses (the console and
// both boxes), logging each write in order.
type recordingStore struct {
@@ -1566,28 +1511,6 @@ func (w *e2eWorld) parkedTarget() map[string]bool {
return out
}
-// createLifetime creates host's record through its lifetime, in the create
-// protocol's order: the host incarnation slot at inc, live, then the record
-// carrying incarnation inc.
-func (wd *offWorld) createLifetime(host string, inc uint64) {
- wd.t.Helper()
- ctx := context.Background()
- if _, err := provision.WriteHostIncarnation(ctx, wd.s, host,
- provision.HostIncarnation{Account: wd.acct, App: "app", Incarnation: inc, State: provision.SlotLive, Intent: "create-e2e"}, ""); err != nil {
- wd.t.Fatal(err)
- }
- wd.createAt(host, inc)
-}
-
-// createAt creates host's record carrying incarnation inc, with no slot.
-func (wd *offWorld) createAt(host string, inc uint64) {
- wd.t.Helper()
- rec := provision.Record{Schema: 1, Host: host, Slug: strings.SplitN(host, ".", 2)[0], Account: wd.acct, App: "app", Region: "", Channel: "stable", CreatedAt: wd.now, Incarnation: inc}
- if err := provision.CreateRecord(context.Background(), wd.s, rec); err != nil {
- wd.t.Fatal(err)
- }
-}
-
// reincarnate rewrites the record at incarnation inc, the move and every
// other field kept, through the record writer: the record a claims-on box
// reads after its hostname was recreated under rows it already holds.
diff --git a/internal/host/movesoffline_land_test.go b/internal/host/movesoffline_land_test.go
index 5953ea24a..f4cfe0889 100644
--- a/internal/host/movesoffline_land_test.go
+++ b/internal/host/movesoffline_land_test.go
@@ -5,7 +5,6 @@ package host
import (
"bytes"
"context"
- "crypto/ed25519"
"encoding/json"
"errors"
"io/fs"
@@ -24,11 +23,8 @@ import (
"github.com/carlosframework/platform/internal/activator"
"github.com/carlosframework/platform/internal/appconfig"
- "github.com/carlosframework/platform/internal/cgroup"
- "github.com/carlosframework/platform/internal/hostwrite"
"github.com/carlosframework/platform/internal/instancekey"
"github.com/carlosframework/platform/internal/lease"
- "github.com/carlosframework/platform/internal/lease/leasetest"
"github.com/carlosframework/platform/internal/moves"
"github.com/carlosframework/platform/internal/provision"
"github.com/carlosframework/platform/internal/registry"
@@ -49,17 +45,8 @@ import (
// staged replica, the archive, its manifest, the timers snapshot, the env
// report and seal.json are exactly what the sealer writes.
-const (
- landKeyValue = "the-instance-key" // the key sealWorld.instance writes
- landStore = "shop-store-ie"
- landSecret = "store-secret-value"
-)
-
-// landS3 reads the data buckets' model directly.
-type landS3 struct{ f *leasetest.VersionedBucket }
-
// current is the current objects under prefix in bucket.
-func (s *landS3) current(t *testing.T, bucket, prefix string) map[string][]byte {
+func (s *landS3) current(t testing.TB, bucket, prefix string) map[string][]byte {
t.Helper()
vers, err := s.f.ListVersions(context.Background(), bucket, prefix)
if err != nil {
@@ -75,51 +62,6 @@ func (s *landS3) current(t *testing.T, bucket, prefix string) map[string][]byte
return out
}
-// landOpts shapes the source: configKey sets the instance key in the app's
-// configuration (and none in <host>.env), noFiles gives the host no files
-// directory, nested makes it a nested row with the launch descriptors a real
-// nested host keeps.
-type landOpts struct {
- configKey string
- noFiles bool
- nested bool
- // judged arms every box with a lifetime judge, as a claims-off agent
- // arms newBoxJudge's (the end-to-end test's claims-off variant).
- judged bool
- // lifetime is judged with the record created through its lifetime: the
- // host's incarnation slot at 1, live, then the record at incarnation 1
- // (the create protocol's order, self-serve moves plan 1 Task 3).
- lifetime bool
- // incarnation creates the record at that incarnation with no slot, as
- // a claims-on box (no judge) sees one; only without judged or lifetime.
- incarnation uint64
-}
-
-// landWorld is offHost at home (ie-a, data-ie) moving to uk (uk-box,
-// data-uk), with a real database replica, a files directory, an env file
-// carrying the instance key, an app config bundle carrying the store
-// binding, timers, a NextDue, a replication rung and a promoted release,
-// sealed by the real sealer.
-type landWorld struct {
- t *testing.T
- sw *sealWorld
- wd *offWorld
- s3 *landS3
- ie *offBox
- uk *offBox
- ukEB *activator.ExecBackend
-
- relPriv ed25519.PrivateKey
-
- seal moves.OfflineReport
- sealRaw []byte
- src provision.MoveSource
-
- opts landOpts
- // The source's files directory, env file and config file.
- srcFiles, srcEnv, srcConfig string
-}
-
func newLandWorld(t *testing.T) *landWorld { return newLandWorldWith(t, landOpts{}) }
func newLandWorldWith(t *testing.T, opts landOpts) *landWorld {
@@ -202,57 +144,6 @@ func newLandWorldWith(t *testing.T, opts landOpts) *landWorld {
return lw
}
-// arm wires the target box as armMovesPass would, over its own data bucket.
-func (lw *landWorld) arm(b *offBox) {
- bucket, region := b.getenv("CARLOS_HIBERNATE_BUCKET"), b.getenv("CARLOS_HIBERNATE_REGION")
- b.svc = activator.New(activator.Config{Bucket: bucket, Idle: time.Hour}, &fakeWakeBackend{}, b.reg)
- cfg := moveCfg{Judge: b.wd.judge, Region: b.region, Box: b.name, DataDir: b.dd, OptDir: b.opt, Quiesced: b.svc.Quiesced, Route: b.reg.Get, Now: b.wd.clock,
- OwnBucket: bucket, OwnBucketRegion: region, OwnPrefix: sealPrefix,
- Mirror: func(ctx context.Context, dst string) (lease.MirrorStore, error) {
- return lease.NewS3Mirror(ctx, region, dst)
- }}
- armOffline(&cfg, b.dd, b.reg, b.svc, nil, hostwrite.Config{}, b.getenv)
- cfg.Offline = newOfflineView(cfg, nil)
- cfg.ReleaseHold = releaseHeld(b.reg, nil)
- cfg.ReleaseHoldSeq = releaseHeldSeq(b.reg, nil)
- cfg.Procs = func(host string) (int, error) { return cgroup.HostProcs(b.cg, host, "") }
- cfg.LaunchLayers = lw.ukEB.LaunchLayers
- b.w = newMovesWorker(cfg)
-}
-
-// restart is an agent restart on the target: a fresh activator and worker
-// over the same registry, data directory and buckets.
-func (lw *landWorld) restart() { lw.arm(lw.uk) }
-
-func (lw *landWorld) promote(v string) {
- lw.t.Helper()
- if err := release.Promote(context.Background(), lw.wd.s, lw.relPriv, lw.wd.acct, "app", "stable", v); err != nil {
- lw.t.Fatal(err)
- }
-}
-
-// bundle writes the app's config: the store binding, its secret and a
-// setting, and the instance key when the world's configuration sets it.
-func (lw *landWorld) bundle(store string) {
- lw.t.Helper()
- env, err := appconfig.SealBound(lw.sw.priv.PublicKey(), appconfig.Binding{Account: lw.wd.acct, App: "app"}, []byte(landSecret))
- if err != nil {
- lw.t.Fatal(err)
- }
- m := appconfig.Manifest{Schema: 1, App: "app", Vars: map[string]appconfig.Var{
- "STORE_BUCKET": {Kind: "env", Value: store},
- "STORE_ENDPOINT": {Kind: "env", Value: "https://store.example.com"},
- "STORE_SECRET_KEY": {Kind: "secret", Envelope: env},
- "FEATURE": {Kind: "env", Value: "on"},
- }}
- if lw.opts.configKey != "" {
- m.Vars[instancekey.Var] = appconfig.Var{Kind: "env", Value: lw.opts.configKey}
- }
- if err := moves.PutReplace(context.Background(), lw.wd.s, appconfig.Key(lw.wd.acct, "app"), mustJSON(lw.t, m)); err != nil {
- lw.t.Fatal(err)
- }
-}
-
// flipAndLand flips the record and runs the target box's ticks.
func (lw *landWorld) flipAndLand(ticks int) {
lw.wd.flip()
@@ -334,7 +225,7 @@ func keysOf[V any](m map[string]V) []string {
// tree is every regular file and symlink under dir with its content's
// sha256, every directory with its mode: what a pass may not change. The
// registry is left out (its file changes on every reconcile).
-func tree(t *testing.T, dir string) map[string]string {
+func tree(t testing.TB, dir string) map[string]string {
t.Helper()
out := map[string]string{}
err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error {
diff --git a/internal/host/movesoffline_seal_test.go b/internal/host/movesoffline_seal_test.go
index 4868f8588..9c4a713b5 100644
--- a/internal/host/movesoffline_seal_test.go
+++ b/internal/host/movesoffline_seal_test.go
@@ -5,7 +5,6 @@ package host
import (
"bytes"
"context"
- "crypto/ecdh"
"crypto/sha256"
"database/sql"
"encoding/hex"
@@ -33,7 +32,6 @@ import (
"github.com/carlosframework/platform/internal/moves/archive"
"github.com/carlosframework/platform/internal/provision"
"github.com/carlosframework/platform/internal/registry"
- "github.com/carlosframework/platform/internal/release"
)
// The sealer (Task 4 of docs/superpowers/plans/2026-10-20-offline-operator-
@@ -44,25 +42,6 @@ import (
// activator per box, and the production edges-file writer for the target's
// coordinates. The deployment store is T3's DirStore world.
-const (
- sealIE = "data-ie"
- sealUK = "data-uk"
- sealIERegion = "eu-west-1"
- sealUKRegion = "eu-west-2"
- sealPrefix = "instances"
- sealVersion = "v42"
-)
-
-type sealWorld struct {
- *offWorld
- fake *leasetest.VersionedBucket
- keyB64 string
- priv *ecdh.PrivateKey
- ie, uk *offBox
- pages []string
- bodies []string // each page's body, beside pages
-}
-
func newSealWorld(t *testing.T) *sealWorld {
t.Helper()
fake := leasetest.NewVersionedBucket()
@@ -96,43 +75,6 @@ func newSealWorld(t *testing.T) *sealWorld {
return sw
}
-// sourceBox is a home box armed as armMovesPass arms one: armOffline, then
-// the hibernate coordinates, the S3 mirror, the production checkpoint and
-// the box's exec backend for the launch environment.
-func (sw *sealWorld) sourceBox(name string) *offBox {
- b := sw.box(name, "", map[string]string{"CARLOS_HIBERNATE_BUCKET": sealIE, "CARLOS_SECRETS_KEY": sw.keyB64})
- sw.arm(b)
- return b
-}
-
-func (sw *sealWorld) arm(b *offBox) {
- c := &b.w.cfg
- c.OwnBucket, c.OwnBucketRegion, c.OwnPrefix = sealIE, sealIERegion, sealPrefix
- c.Mirror = func(ctx context.Context, dst string) (lease.MirrorStore, error) {
- return lease.NewS3Mirror(ctx, sealIERegion, dst)
- }
- // armMovesPass's Checkpoint, as it is wired there.
- c.Checkpoint = func(ctx context.Context, dbPath string) error {
- host := strings.TrimSuffix(filepath.Base(dbPath), ".db")
- rep, err := lease.ReplicateWithLease(ctx, dbPath, sealIE, activator.HibernatePrefix(sealPrefix, host), sealIERegion)
- if err != nil {
- return err
- }
- defer rep.Close(ctx)
- return rep.SyncAndWait(ctx)
- }
- c.CarryRequest = func(ctx context.Context, host, bucket, prefix string) error {
- sw.fake.Put(bucket, prefix+"/build-state.json", []byte(`{"request":"carried"}`))
- return nil
- }
- c.LaunchLayers = (&activator.ExecBackend{DataDir: b.dd}).LaunchLayers
- c.Page = func(_ context.Context, level, subject, body string) error {
- sw.pages = append(sw.pages, level+": "+subject)
- sw.bodies = append(sw.bodies, body)
- return nil
- }
-}
-
// pass is a tick in the agent's order (reconcile, the edges file, the moves
// pass) whose moves pass is movesPass's offline part: the keep view's
// refresh from the record list, then offlineRound, returning the round's
@@ -153,98 +95,6 @@ func (sw *sealWorld) mustPass(b *offBox) {
}
}
-func (sw *sealWorld) dbPath(b *offBox) string {
- sw.t.Helper()
- rt := b.row(offHost)
- if rt == nil {
- sw.t.Fatal("no row")
- }
- return rt.DBPath
-}
-
-// instance gives b a live instance of offHost, as a box that has served it
-// has: the row (minted ready by reconcile), its replication rung, next due
-// and timers, a database written through lease.ReplicateWithLease into
-// data-ie, the files directory, <host>.env with an instance key, the
-// rendered configuration and a live build.
-func (sw *sealWorld) instance(b *offBox) {
- t := sw.t
- t.Helper()
- b.tick()
- rt := b.row(offHost)
- if rt == nil || rt.HoldOp != "" {
- t.Fatalf("fixture: row %+v", rt)
- }
- if err := b.reg.SetReplication(offHost, registry.ReplicationBatched); err != nil {
- t.Fatal(err)
- }
- if err := b.reg.SetNextDue(offHost, 1_800_000_000); err != nil {
- t.Fatal(err)
- }
- for _, tm := range []registry.Timer{
- {Host: offHost, Name: "nightly", Kind: "cron", Spec: "0 3 * * *", Path: "/jobs/nightly", NextAt: 1_800_000_100},
- {Host: offHost, Name: "reminder", Kind: "once", Path: "/jobs/remind", NextAt: 1_800_000_200},
- {Host: offHost, Name: "fired", Kind: "once", Path: "/jobs/fired", LastAt: 1_700_000_000, LastStatus: "200"},
- } {
- if err := b.reg.UpsertTimer(tm); err != nil {
- t.Fatal(err)
- }
- }
- writeFile(t, filepath.Join(b.dd, offHost, "instance.key"), "app key file")
- writeFile(t, filepath.Join(b.dd, offHost, "uploads", "a.png"), "png bytes")
- writeFile(t, filepath.Join(b.dd, offHost+".env"), "CARLOS_INSTANCE_KEY=the-instance-key\nCARLOS_ADMIN_TOKEN=token\n")
- writeFile(t, filepath.Join(b.dd, offHost+".config.env"), "APP_COLOUR=green\n")
- sw.writeRows(b, rt.DBPath, 0, 20, true)
- versions := filepath.Join(b.opt, "versions", sealVersion, sw.acct, "app")
- if err := os.MkdirAll(versions, 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.MkdirAll(filepath.Dir(release.LiveLink(b.opt, offHost)), 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.Symlink(versions, release.LiveLink(b.opt, offHost)); err != nil {
- t.Fatal(err)
- }
-}
-
-// writeRows inserts rows [from, to) into the database at dbPath, then, with
-// replicate, syncs it to data-ie under the lease as a wake's stop does.
-func (sw *sealWorld) writeRows(b *offBox, dbPath string, from, to int, replicate bool) {
- t := sw.t
- t.Helper()
- db, err := sql.Open("sqlite", dbPath)
- if err != nil {
- t.Fatal(err)
- }
- for _, q := range []string{`PRAGMA journal_mode=WAL`, `CREATE TABLE IF NOT EXISTS t (id INTEGER PRIMARY KEY, body TEXT)`} {
- if _, err := db.Exec(q); err != nil {
- t.Fatal(err)
- }
- }
- for i := from; i < to; i++ {
- if _, err := db.Exec(`INSERT INTO t (id, body) VALUES (?, ?)`, i, fmt.Sprintf("row-%d", i)); err != nil {
- t.Fatal(err)
- }
- }
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- if !replicate {
- return
- }
- ctx := context.Background()
- rep, err := lease.ReplicateWithLease(ctx, dbPath, sealIE, activator.HibernatePrefix(sealPrefix, offHost), sealIERegion)
- if err != nil {
- t.Fatal(err)
- }
- if err := rep.SyncAndWait(ctx); err != nil {
- t.Fatal(err)
- }
- if err := rep.Close(ctx); err != nil {
- t.Fatal(err)
- }
-}
-
func countRows(t *testing.T, dbPath string) int {
t.Helper()
db, err := sql.Open("sqlite", dbPath)
diff --git a/internal/host/movesoffline_test.go b/internal/host/movesoffline_test.go
index f6c443814..24e93bac6 100644
--- a/internal/host/movesoffline_test.go
+++ b/internal/host/movesoffline_test.go
@@ -14,10 +14,8 @@ import (
"testing"
"time"
- "github.com/carlosframework/platform/internal/activator"
"github.com/carlosframework/platform/internal/cgroup"
"github.com/carlosframework/platform/internal/domains"
- "github.com/carlosframework/platform/internal/hostwrite"
"github.com/carlosframework/platform/internal/instancectl"
"github.com/carlosframework/platform/internal/layout"
"github.com/carlosframework/platform/internal/moves"
@@ -34,35 +32,10 @@ import (
// package's fake wake backend, which only the fence needs).
const (
- offHost = "shop.example.com"
- offID = "mv_zyxwvutsrqponmlkjihgfedcba"
- offID2 = "mv_abcdefghijklmnopqrstuvwxy2"
+ offID = "mv_zyxwvutsrqponmlkjihgfedcba"
+ offID2 = "mv_abcdefghijklmnopqrstuvwxy2"
)
-type offWorld struct {
- t *testing.T
- s release.Store
- acct string
- mu sync.Mutex
- now time.Time
- // judge, when set, is every box's lifetime judge, wired as the agent
- // wires newBoxJudge's (the moves worker, its offline view and the
- // reconcile pass). Nil: the boxes judge nothing, as before.
- judge *provision.LifetimeJudge
-}
-
-func (wd *offWorld) clock() time.Time {
- wd.mu.Lock()
- defer wd.mu.Unlock()
- return wd.now
-}
-
-func (wd *offWorld) advance(d time.Duration) {
- wd.mu.Lock()
- wd.now = wd.now.Add(d)
- wd.mu.Unlock()
-}
-
// newOffWorld is a deployment with one account, one app and one instance
// record for offHost at the home placement ("").
func newOffWorld(t *testing.T) *offWorld {
@@ -84,14 +57,6 @@ func newOffWorld(t *testing.T) *offWorld {
return wd
}
-func (wd *offWorld) create(host string) {
- wd.t.Helper()
- rec := provision.Record{Schema: 1, Host: host, Slug: strings.SplitN(host, ".", 2)[0], Account: wd.acct, App: "app", Region: "", Channel: "stable", CreatedAt: wd.now}
- if err := provision.CreateRecord(context.Background(), wd.s, rec); err != nil {
- wd.t.Fatal(err)
- }
-}
-
// proposed is a stamped transition whose stamp is proposed from the record
// the write reads at the world's clock (provision.ProposeStamp), as every
// writer proposes it: the transitions store the stamp they are given.
@@ -173,103 +138,6 @@ func (wd *offWorld) reportIf(role, box string) (moves.OfflineReport, bool) {
return r, true
}
-// offBox is one box: its own data directory, registry, activator and moves
-// worker, armed as armMovesPass arms it (armOffline).
-type offBox struct {
- wd *offWorld
- name, region string
- dd, opt, cg string
- env map[string]string
- reg *registry.Registry
- svc *activator.Service
- w *movesWorker
- nested bool
- last []byte
-}
-
-func (wd *offWorld) box(name, region string, env map[string]string) *offBox {
- wd.t.Helper()
- b := &offBox{wd: wd, name: name, region: region, dd: wd.t.TempDir(), opt: wd.t.TempDir(), cg: wd.t.TempDir(), env: env}
- reg, err := registry.Open(filepath.Join(b.dd, "registry.db"))
- if err != nil {
- wd.t.Fatal(err)
- }
- wd.t.Cleanup(func() { reg.Close() })
- b.reg = reg
- b.restart()
- return b
-}
-
-func (b *offBox) getenv(k string) string {
- if v, ok := b.env[k]; ok {
- return v
- }
- if k == "CARLOS_HIBERNATE_BUCKET" {
- return "data"
- }
- return ""
-}
-
-// restart is an agent restart: a fresh activator (no fences) and a fresh
-// worker over the same registry and data directory.
-func (b *offBox) restart() {
- b.svc = activator.New(activator.Config{Bucket: "data", Idle: time.Hour}, &fakeWakeBackend{}, b.reg)
- cfg := moveCfg{Judge: b.wd.judge, Region: b.region, Box: b.name, DataDir: b.dd, OptDir: b.opt, Quiesced: b.svc.Quiesced, Route: b.reg.Get, Now: b.wd.clock}
- armOffline(&cfg, b.dd, b.reg, b.svc, nil, hostwrite.Config{}, b.getenv)
- // The stage-requested marker is root's business, and its throttled
- // timer would outlive the test's directories.
- cfg.Offline = newOfflineView(cfg, nil)
- cfg.ReleaseHold = releaseHeld(b.reg, nil)
- cfg.ReleaseHoldSeq = releaseHeldSeq(b.reg, nil)
- cfg.Procs = func(host string) (int, error) { return cgroup.HostProcs(b.cg, host, "") }
- b.w = newMovesWorker(cfg)
-}
-
-func (b *offBox) reconcile() {
- b.wd.t.Helper()
- cfg := provision.ReconcileConfig{Region: b.region, DataDir: b.dd, Judge: b.wd.judge}
- if b.nested {
- cfg.Layout = func(context.Context, release.Store) (string, error) { return release.LayoutModeNested, nil }
- } else {
- cfg.Layout = func(context.Context, release.Store) (string, error) { return release.LayoutModeFlat, nil }
- }
- if err := reconcileSerializedWith(context.Background(), b.wd.s, b.reg, cfg, b.w); err != nil {
- b.wd.t.Fatal(err)
- }
-}
-
-func (b *offBox) edges() {
- b.wd.t.Helper()
- if err := edgesFileWrite(context.Background(), b.wd.s, b.reg, edgesFile{Box: b.name, Placement: b.region}, "", &b.last, false); err != nil {
- b.wd.t.Fatal(err)
- }
-}
-
-func (b *offBox) pass() {
- b.wd.t.Helper()
- if err := movesPass(context.Background(), b.wd.s, b.w); err != nil {
- b.wd.t.Fatal(err)
- }
-}
-
-// tick is the agent's order on a serial bump: reconcile, the edges file,
-// the moves pass.
-func (b *offBox) tick() {
- b.wd.t.Helper()
- b.reconcile()
- b.edges()
- b.pass()
-}
-
-func (b *offBox) row(host string) *registry.Route {
- b.wd.t.Helper()
- rt, err := b.reg.Get(host)
- if err != nil {
- b.wd.t.Fatal(err)
- }
- return rt
-}
-
// lists reports whether this box's published edges file lists host.
func (b *offBox) lists(host string) bool {
b.wd.t.Helper()
@@ -285,16 +153,6 @@ func (b *offBox) lists(host string) bool {
return ok
}
-func writeFile(t *testing.T, p, body string) {
- t.Helper()
- if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
- t.Fatal(err)
- }
-}
-
// --- the hold ---------------------------------------------------------------
// offlineHold's table: a pure function of the record and the box's
diff --git a/internal/host/movesprobeowners_test.go b/internal/host/movesprobeowners_test.go
index 5fbe31165..913646017 100644
--- a/internal/host/movesprobeowners_test.go
+++ b/internal/host/movesprobeowners_test.go
@@ -743,15 +743,6 @@ func TestFirstProbeFromTheSourceGenerationIsAdmitted(t *testing.T) {
}
}
-func mustJSON(t *testing.T, v any) []byte {
- t.Helper()
- b, err := json.Marshal(v)
- if err != nil {
- t.Fatal(err)
- }
- return b
-}
-
// The prepared marker (moves §7.14 marker row, R10-3): the first probe's
// admission publishes generation.json naming the move id with the build's
// pin, superseding an earlier stay's marker by its exact ETag, before
diff --git a/internal/host/schedule_wake_test.go b/internal/host/schedule_wake_test.go
index 3c14e09d1..4c9bb1fab 100644
--- a/internal/host/schedule_wake_test.go
+++ b/internal/host/schedule_wake_test.go
@@ -14,13 +14,11 @@ package host
import (
"context"
"errors"
- "net"
"net/http"
"net/http/httptest"
"net/http/httputil"
"path/filepath"
"strconv"
- "sync"
"testing"
"time"
@@ -33,73 +31,6 @@ import (
// --- the fake instance and its backend ---------------------------------------
-// fakeInstanceProc is the spawned child: stopping it takes the socket away,
-// which is what "hibernated" means to anything downstream.
-type fakeInstanceProc struct {
- inst *fakeInstance
-}
-
-func (p *fakeInstanceProc) Stop(ctx context.Context) error { return p.inst.stop(ctx) }
-func (p *fakeInstanceProc) Exited() bool { return !p.inst.running() }
-
-// fakeInstance is an app listening on a unix socket, started and stopped by
-// the backend below. It records every request it served.
-type fakeInstance struct {
- socket string
- handler http.Handler
-
- mu sync.Mutex
- srv *http.Server
- starts int
- conns int // accepted connections — the transport-reuse assertion
- requests []*http.Request
-}
-
-func (f *fakeInstance) running() bool {
- f.mu.Lock()
- defer f.mu.Unlock()
- return f.srv != nil
-}
-
-func (f *fakeInstance) start() error {
- ln, err := net.Listen("unix", f.socket)
- if err != nil {
- return err
- }
- srv := &http.Server{
- Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- f.mu.Lock()
- f.requests = append(f.requests, r.Clone(context.Background()))
- f.mu.Unlock()
- f.handler.ServeHTTP(w, r)
- }),
- ConnState: func(_ net.Conn, st http.ConnState) {
- if st == http.StateNew {
- f.mu.Lock()
- f.conns++
- f.mu.Unlock()
- }
- },
- }
- f.mu.Lock()
- f.srv = srv
- f.starts++
- f.mu.Unlock()
- go srv.Serve(ln)
- return nil
-}
-
-func (f *fakeInstance) stop(ctx context.Context) error {
- f.mu.Lock()
- srv := f.srv
- f.srv = nil
- f.mu.Unlock()
- if srv == nil {
- return nil
- }
- return srv.Close()
-}
-
func (f *fakeInstance) served() []*http.Request {
f.mu.Lock()
defer f.mu.Unlock()
@@ -118,48 +49,6 @@ func (f *fakeInstance) connCount() int {
return f.conns
}
-// fakeWakeBackend is activator.Backend over one fakeInstance: no S3, no
-// processes, no replication — just the spawn/stop the wake path drives.
-type fakeWakeBackend struct{ inst *fakeInstance }
-
-type nopLease struct{}
-
-func (nopLease) Replicate(context.Context, string, time.Duration) (activator.Rep, error) {
- return nopRep{}, nil
-}
-func (nopLease) Release(context.Context) error { return nil }
-
-type nopRep struct{}
-
-func (nopRep) Err() error { return nil }
-func (nopRep) Close(context.Context) error { return nil }
-
-func (b *fakeWakeBackend) Acquire(context.Context, registry.Route) (activator.Lease, error) {
- return nopLease{}, nil
-}
-func (b *fakeWakeBackend) Restore(context.Context, registry.Route) (bool, error) { return false, nil }
-func (b *fakeWakeBackend) Seed(context.Context, registry.Route) error { return nil }
-func (b *fakeWakeBackend) Start(_ context.Context, _ registry.Route) (activator.Proc, string, error) {
- if err := b.inst.start(); err != nil {
- return nil, "", err
- }
- return &fakeInstanceProc{inst: b.inst}, "", nil
-}
-func (b *fakeWakeBackend) NextDue(context.Context, registry.Route) (int64, error) { return 0, nil }
-func (b *fakeWakeBackend) HasSidecar(registry.Route) bool { return false }
-func (b *fakeWakeBackend) StartSidecar(context.Context, registry.Route) (activator.Proc, error) {
- return nil, nil
-}
-
-// StartWorker: this fixture drives the INSTANCE wake path, so nothing here
-// ever asks for a worker. Refusing rather than returning a nil Proc is the
-// safer stub — a nil Proc would be dereferenced by the caller and read as a
-// crash inside the wake, which is a confusing way to learn a test wandered
-// into the wrong door.
-func (b *fakeWakeBackend) StartWorker(context.Context, registry.Route, map[string]string) (activator.Proc, string, error) {
- return nil, "", errors.New("fakeWakeBackend: no worker path in this fixture")
-}
-
// wakeFixture builds a sleeping instance: a registry with one hibernate-
// flagged exec route, a fakeInstance that listens only while it is "running",
// and an activator over it with its sweeper started. dd is the agent's data