//go:build linux package host import ( "context" "errors" "path/filepath" "testing" "github.com/carlosframework/platform/internal/activator" "github.com/carlosframework/platform/internal/hostclaim" ) func TestClaimRestoreOwnWriteMaterializes(t *testing.T) { for _, foreign := range []string{"", "before-retag", "after-restore"} { t.Run(foreign, func(t *testing.T) { t.Setenv("CARLOS_HOST_CLAIM", "1") t.Setenv("CARLOS_HOST_CLAIM_ORIGINS", "1") d := plan9D4Landing(t) c := d.controller("boot-1", "controller-1", 4101, "start-1", true) proofs := 0 d.f.materialize = func(ctx context.Context, o hostclaim.Observed) error { h := o.Claim.Holder tuple := hostclaim.OriginTuple{Incarnation: h.Incarnation, Location: h.Location, Region: "ie", Generation: h.Generation} proven, err := hostclaim.AdmittedOrigins(ctx, d.f.claims, o, []hostclaim.OriginTuple{tuple}) if err != nil { return err } if !proven[tuple] { t.Fatal("admission origin not proven") } proofs++ return nil } materialize := func(o hostclaim.Observed, s activator.ClaimStart) error { if err := s.Reserve(t.Context(), 0); err != nil { return err } return s.Materialize(t.Context()) } first, err := d.start(t, c, plan9D4Probe, false, materialize) if err != nil { t.Fatal("first start", err) } writeForeign := func() { d.step(t, "foreign inventory write", func(claim *hostclaim.Claim) error { claim.Inventory["box-1"].Paths[recoveryHost+".env"] = claim.Holder.Stamp return nil }) } if foreign == "before-retag" { retire := c.wake.RetireArtifacts c.wake.RetireArtifacts = func(ctx context.Context, o hostclaim.Observed, in ClaimLaunchInputs, generation string) error { writeForeign() return retire(ctx, o, in, generation) } } second, err := d.start(t, c, plan9D4Probe, true, func(o hostclaim.Observed, s activator.ClaimStart) error { after := d.f.read() if foreign != "before-retag" && after.Claim.Gen != o.Claim.Gen+1 { t.Fatalf("Restore did not perform exactly its retag CAS: %d -> %d", o.Claim.Gen, after.Claim.Gen) } h := o.Claim.Holder _, stale := hostclaim.AdmittedOrigins(t.Context(), d.f.claims, o, []hostclaim.OriginTuple{{Incarnation: h.Incarnation, Location: h.Location, Region: "ie", Generation: h.Generation}}) if !errors.Is(stale, hostclaim.ErrConflict) { t.Fatalf("Prepare's read was not stale after retirement: %v", stale) } if foreign == "after-restore" { writeForeign() } return materialize(o, s) }) if foreign != "" { if !errors.Is(err, hostclaim.ErrConflict) { t.Fatalf("foreign claim write: %v", err) } if proofs != 1 { t.Fatal("foreign writer reached C8", proofs) } return } if err != nil { t.Fatal("retiring cold start", err) } d.requireRetired(t, first, second, false) if proofs != 2 { t.Fatal("both starts must prove C8 origins", proofs) } }) } } func TestClaimRestoreWriteOnlyRetagsPair(t *testing.T) { for _, foreign := range []string{"", "op", "holder", "session", "stamp", "admission", "other-path", "before-etag", "generation"} { t.Run(foreign, func(t *testing.T) { f := newRecoveryFixture(t) c := f.controller("boot", "controller", 4101, "start") before := c.admit(t, f) current, err := servingWriterIdentity(before, "box-1", "ie") if err != nil { t.Fatal(err) } after, err := hostclaim.Update(t.Context(), f.claims, before, servingInventoryTransition(current, "state/data.db", 0)) if err != nil { t.Fatal(err) } r := &claimRestoreObservation{observed: before, box: "box-1", dataDir: f.guard.DataDir, db: filepath.Join(f.guard.DataDir, "state", "data.db")} switch foreign { case "op": after.Claim.Op.Exec = "foreign" case "holder": after.Claim.Holder.App = "foreign" case "session": after.Claim.Holder.Session++ case "stamp": after.Claim.Holder.Stamp++ case "admission": after.Claim.Holder.Admissions[0].Generation = "foreign" case "other-path": after.Claim.Inventory["box-1"].Paths["foreign"] = current.Stamp case "before-etag": before.ETag = "foreign" case "generation": after.Claim.Gen++ } err = r.retag(before, after) if (err == nil) != (foreign == "") { t.Fatalf("foreign %q: %v", foreign, err) } }) } }