package host import ( "context" "encoding/json" "os" "reflect" "strings" "testing" "time" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rollout" "github.com/carlosframework/platform/internal/rootparticipation" ) type r11RecordingStore struct { release.Store reads []string } func (s *r11RecordingStore) Get(ctx context.Context, key string) ([]byte, string, error) { s.reads = append(s.reads, "get:"+key) return s.Store.Get(ctx, key) } func (s *r11RecordingStore) List(ctx context.Context, prefix string) ([]string, error) { s.reads = append(s.reads, "list:"+prefix) return s.Store.List(ctx, prefix) } func TestPlan14NoCensusProofObservation(t *testing.T) { for _, flag := range []string{"", "1"} { t.Run(flag, func(t *testing.T) { t.Setenv("CARLOS_HOST_CLAIM", flag) _, public, err := release.GenerateKey() if err != nil { t.Fatal(err) } t.Setenv("CARLOS_RELEASE_PUBKEY", public) e, rt, rec, source := plan14ProofFixture(t) store := &r11RecordingStore{Store: e.store} production := productionProofBindingSource(store, source.sys, source.optDir) if production.signedRecovery { t.Fatal("no census enabled signed recovery") } source.store = store source.signedRecovery = production.signedRecovery if production.claim != nil { rec.CreatedAt = time.Now().UTC() plan14PutRecord(t, e.store, rec) body, err := json.Marshal(rec) if err != nil { t.Fatal(err) } o, err := hostclaim.Read(t.Context(), e.store, rec.Host) if err != nil { t.Fatal(err) } o, err = hostclaim.Update(t.Context(), e.store, o, hostclaim.BeginCreate(hostclaim.NewHolder{Family: "instance", Key: provision.RecordKey(rec.Account, rec.App, rec.Host), Host: rec.Host, Account: rec.Account, App: rec.App, Region: "ie", CreatedAt: rec.CreatedAt}, body, "create", "console", rec.CreatedAt)) if err != nil { t.Fatal(err) } o, err = hostclaim.Update(t.Context(), e.store, o, hostclaim.CompleteCreate("create", "box")) if err != nil { t.Fatal(err) } if _, err = hostclaim.Update(t.Context(), e.store, o, hostclaim.AdmitWriter(hostclaim.Admission{Box: "box", Region: "ie", Mode: "cold", NewGeneration: "first"})); err != nil { t.Fatal(err) } source.claim = production.claim source.box = func(context.Context) (string, error) { return "box", nil } } source.healthy = func(string, string, string, string) *release.HealthyBuild { return nil } live, err := e.root.obs.observe(rt, 0) if err != nil { t.Fatal(err) } if live.binding.Historical || live.binding.Signed { t.Fatal("fabricated recovery authority") } baseline := append([]string(nil), store.reads...) store.reads = nil if _, _, err = provision.FindRecordByHost(t.Context(), store, rec.Host); err != nil { t.Fatal(err) } if source.claim != nil { if _, err = source.claim(t.Context(), rec.Host); err != nil { t.Fatal(err) } } // Observation binds the unit and repeats that binding after probing it. want := append(append([]string(nil), store.reads...), store.reads...) if !reflect.DeepEqual(baseline, want) { t.Fatalf("legacy observation read beyond its two canonical record/claim lookups: got %v, want %v", baseline, want) } store.reads = nil source.signedRecovery = true if _, err = e.root.obs.observe(rt, 0); err != nil { t.Fatal(err) } if reflect.DeepEqual(baseline, store.reads) { t.Fatal("negative control did not read authorization") } for _, key := range baseline { if strings.Contains(key, "manifest") || strings.Contains(key, "promotion") { t.Fatal("legacy authorization read", key) } } }) } } func TestPlan14PrelaunchCancellation(t *testing.T) { for _, mode := range []string{"reserved", "opener-failure", "open-writers", "wrong-controller", "kernel", "lost-close", "lost-retirement"} { t.Run(mode, func(t *testing.T) { f, disk, _ := generationLaunchFixture(t) if mode == "opener-failure" { f.root.generations.beginUpdate = func(context.Context, *rootGenerationSource, release.GenerationBinding) (func(string, error, func() error) error, func() error, error) { return nil, nil, os.ErrPermission } } permit, err := f.root.generations.reserve(t.Context(), f.root, f.peer, f.req) if mode == "opener-failure" { if err == nil { t.Fatal("opener succeeded") } } else { if err != nil { t.Fatal(err) } defer permit.ledger.Store.Close() } r := readGenerationLaunch(t, disk) launches, err := f.journal.GenerationLaunches(t.Context()) if err != nil { t.Fatal(err) } for _, l := range launches { if len(l.Reservations) != 0 { t.Fatal("unit intention preceded cancellation test") } } if mode != "open-writers" { if err = f.journal.CloseWriters(t.Context(), f.target); err != nil { t.Fatal(err) } } if mode == "wrong-controller" { r.Binding.ControllerPID++ } checks := 0 v := generationRetirement{guard: f.root.Guard, journal: f.journal, kernel: func(context.Context, release.GenerationReservation) (string, error) { checks++ if mode == "kernel" { return "", os.ErrPermission } if mode == "lost-retirement" { disk.fail = 1 } return strings.Repeat("a", 64), nil }} if mode == "lost-close" { disk.fail = 1 } ledger := &release.RootGenerations{Store: disk} err = v.retire(t.Context(), ledger, r) got := readGenerationLaunch(t, disk) switch mode { case "open-writers": if err != nil || got != r || checks != 0 { t.Fatal("open reservation changed", err) } return case "wrong-controller", "kernel": if err == nil || got.Retirement != "" { t.Fatal("uncertain cancellation accepted", err) } return case "lost-close", "lost-retirement": if err == nil { t.Fatal("durability failure ignored") } v.kernel = func(context.Context, release.GenerationReservation) (string, error) { return strings.Repeat("a", 64), nil } if err = v.retire(t.Context(), ledger, r); err != nil { t.Fatal("recovery", err) } got = readGenerationLaunch(t, disk) default: if err != nil { t.Fatal(err) } } if !got.Closed || got.Retirement == "" || got.Process != (release.GenerationProcess{}) { t.Fatal("reservation not cancelled", got) } if err = f.journal.IntendUnit(t.Context(), *f.req.Reservation); err == nil { t.Fatal("closed controller replayed intention") } if _, err = ledger.Reserve(r.Binding); err == nil { t.Fatal("cancelled reservation replayed") } views, err := f.root.Guard.Root.GenerationInstallations(t.Context()) if err != nil { t.Fatal(err) } launches, err = f.journal.GenerationLaunches(t.Context()) if err != nil { t.Fatal(err) } var exact []hostbox.LaunchInventory for _, l := range launches { if generationOwner(l.Target) == r.Binding.Target { exact = append(exact, l) } } snapshot := generationInventory{Installations: views, Launches: exact, Payloads: generationPayloadFixture(views)} snapshot.Census.Schema = 1 snapshot.Census.Complete = true snapshot.Census.Box = views[0].Installation.Box snapshot.Census.Enrollment = views[0].Installation.Enrollment snapshot.Census.Migration = 1 snapshot.Census.Revision = 1 snapshot.Census.LaunchRevision = 1 verifier := generationReconciler{read: func(context.Context) (generationInventory, error) { return snapshot, nil }, kernel: func(context.Context) (string, string, error) { return "boot", strings.Repeat("a", 64), nil }} if _, err = verifier.reconcile(t.Context(), []release.GenerationReservation{got}); err != nil { t.Fatal("cancelled pin failed census", err) } }) } } func TestPlan14RetiredRecordDeletion(t *testing.T) { f, _, rec := generationLaunchFixture(t) rec.Schema = 1 plan14PutRecord(t, f.root.generations.store, rec) views, err := f.root.Guard.Root.GenerationInstallations(t.Context()) if err != nil { t.Fatal(err) } in := views[0].Installation views[0].Retired = []hostguard.Captured{{Incarnation: in.Incarnation, Stamp: in.Stamp}} views[0].Installation.Empty = true snapshot := generationInventory{Installations: views, Payloads: generationPayloadFixture(views)[:1]} if err = f.root.generations.store.Delete(t.Context(), in.Record); err != nil { t.Fatal(err) } if _, err = generationRequiredInventory(t.Context(), f.root.generations.store, snapshot, nil); err != nil { t.Fatal("retired record required", err) } views[0].Retired = nil if _, err = generationRequiredInventory(t.Context(), f.root.generations.store, snapshot, nil); err == nil { t.Fatal("missing active record accepted") } } func TestPlan14SettingsSuccessorPreservesDenials(t *testing.T) { ctx := t.Context() store := release.NewDirStore(t.TempDir()) rec := provision.Record{Schema: 1, Host: "settings.example", Account: "bab", App: "shop", Channel: "stable", CreatedAt: time.Now().UTC()} if err := provision.CreateRecord(ctx, store, rec); err != nil { t.Fatal(err) } inc, err := hostclaim.Incarnation(rec.Account, rec.App, rec.Host, rec.CreatedAt) if err != nil { t.Fatal(err) } state := &rollbackMemory{files: map[string][]byte{}} book, err := rollout.InitializeRootFailures(state, "box/en/1", nil) if err != nil { t.Fatal(err) } p := rootUpdateSource{enforceFailures: true, state: state, store: store, book: book, binding: &proofBindingSource{}, data: t.TempDir()} p.source.census = func() (rootparticipation.Census, error) { return rootparticipation.Census{Schema: 1, Complete: true, Box: "box", Enrollment: "en", Migration: 1}, nil } in := hostguard.RootInstallation{Host: rec.Host, Incarnation: inc, Stamp: 1, Account: rec.Account, App: rec.App, Record: provision.RecordKey(rec.Account, rec.App, rec.Host), Box: "box", Enrollment: "en", Migration: 1, InventoryRevision: 1} for _, stamp := range []uint64{1, 2} { authority := &rollbackMemory{files: map[string][]byte{}} in.Stamp = stamp if err = hostguard.NewRootAuthority(authority).RetainMigrationDeny(ctx, in, stamp); err != nil { t.Fatal(err) } p.source.state = authority row, err := p.resolve(ctx, rec.Host) if err != nil { t.Fatal("successor settings", stamp, err) } if stamp == 1 { if err = row.Save(release.Consumed{Completed: 17}); err != nil { t.Fatal(err) } finish, err := row.Begin("v1", strings.Repeat("a", 64), "first") if err != nil { t.Fatal(err) } if err = finish("", os.ErrPermission, nil); err != nil { t.Fatal(err) } key := rootSettingsKey(rec.Host, rootHistoryInstallation(in)) raw, err := state.Read(key) if err != nil { t.Fatal(err) } if err = state.Write("settings-"+rec.Host+".json", raw); err != nil { t.Fatal(err) } if err = state.Remove(key); err != nil { t.Fatal(err) } recovered, err := p.resolve(ctx, rec.Host) if err != nil || recovered.Consumed == nil || recovered.Consumed.Completed != 17 { t.Fatal("same-installation legacy settings lost", err) } } else { if row.Consumed != nil || row.Build.State != nil { t.Fatal("foreign completion reused") } if !row.Failed("v1") { t.Fatal("successor lost required denial") } if row.Route.Socket != provision.NestedSocket(rec.Host) || row.Route.DBPath != provision.NestedDBPath(p.data, rec.Host) { t.Fatal("claim readiness used legacy paths", row.Route) } } } }