| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "testing" |
| 8 | |
| 9 | "github.com/carlosframework/platform/internal/hostbox" |
| 10 | "github.com/carlosframework/platform/internal/registry" |
| 11 | ) |
| 12 | |
| 13 | func TestRootRetirementJournalsBeforeAcknowledgementAndPreservesSuccessor(t *testing.T) { |
| 14 | coordinator, req, _ := claimStopFixture(t) |
| 15 | result, err := coordinator.FenceAndStop(t.Context(), req) |
| 16 | if err != nil { |
| 17 | t.Fatal(err) |
| 18 | } |
| 19 | base := t.TempDir() |
| 20 | inventory, err := openLegacyInventory(t, filepath.Join(base, "inventory.db")) |
| 21 | if err != nil { |
| 22 | t.Fatal(err) |
| 23 | } |
| 24 | defer inventory.Close() |
| 25 | reg, err := registry.Open(filepath.Join(base, "registry.db")) |
| 26 | if err != nil { |
| 27 | t.Fatal(err) |
| 28 | } |
| 29 | defer reg.Close() |
| 30 | owner := registry.ClaimOwnership{ClaimIncarnation: req.Incarnation, ClaimStamp: req.InstallationStamp, ClaimOwner: "installation", ClaimRowID: "route-1"} |
| 31 | row := registry.Route{Host: req.Host, ClaimOwnership: owner} |
| 32 | if err = reg.Upsert(row); err != nil { |
| 33 | t.Fatal(err) |
| 34 | } |
| 35 | in := hostbox.StopInventory{Complete: true, Host: req.Host, Incarnation: req.Incarnation, InstallationStamp: req.InstallationStamp, Box: req.Box, Enrollment: req.Enrollment, DirectoryRevision: req.ExpectedDirectoryRevision, Entries: []hostbox.StopInventoryEntry{{Kind: "route", ArtifactID: owner.ClaimRowID, ManagedRoot: "registry", RelativePath: req.Host, RowIdentity: owner.ClaimRowID, Ownership: owner.ClaimOwner, Materialization: "materialized"}}} |
| 36 | if err = inventory.WriteStopInventory(t.Context(), in); err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | journal := filepath.Join(base, "retire") |
| 40 | root := ClaimRootHTTP{Stop: ClaimStopHTTP{Coordinator: coordinator}, Roots: map[string]string{"data": base}, Reconcile: hostbox.ReconcileAdapter{Inventory: inventory, Registry: reg, Retirement: hostbox.TypedRetirement{JournalDir: journal}}} |
| 41 | // The fixture deliberately has no claim in the evidence store. Root must |
| 42 | // retain completed local evidence and retry acknowledgement separately. |
| 43 | if err = root.retire(t.Context(), result.Target); err == nil { |
| 44 | t.Fatal("missing acknowledgement claim accepted") |
| 45 | } |
| 46 | if got, e := reg.Get(req.Host); e != nil || got != nil { |
| 47 | t.Fatalf("root did not retire owned row: %v", e) |
| 48 | } |
| 49 | if _, err = os.Stat(filepath.Join(journal, result.Target.FenceID+".json")); err != nil { |
| 50 | t.Fatal(err) |
| 51 | } |
| 52 | row.ClaimStamp++ |
| 53 | row.ClaimOwner = "successor" |
| 54 | row.ClaimRowID = "route-2" |
| 55 | if err = reg.Upsert(row); err != nil { |
| 56 | t.Fatal(err) |
| 57 | } |
| 58 | if err = root.retire(context.Background(), result.Target); err == nil { |
| 59 | t.Fatal("missing acknowledgement claim accepted on retry") |
| 60 | } |
| 61 | got, err := reg.Get(req.Host) |
| 62 | if err != nil || got == nil || got.ClaimRowID != "route-2" { |
| 63 | t.Fatalf("root replay removed successor: %v", err) |
| 64 | } |
| 65 | } |
| 66 | |