| 1 | //go:build linux |
| 2 | |
| 3 | package host |
| 4 | |
| 5 | import ( |
| 6 | "os" |
| 7 | "path/filepath" |
| 8 | "reflect" |
| 9 | "testing" |
| 10 | |
| 11 | "github.com/carlosframework/platform/internal/hostbox" |
| 12 | ) |
| 13 | |
| 14 | func TestServingParkIncompleteRecoveryRechecksEnrollment(t *testing.T) { |
| 15 | for _, change := range []string{"enrollment", "binding"} { |
| 16 | t.Run(change, func(t *testing.T) { |
| 17 | f := newServingRootFixture(t, true) |
| 18 | a, err := f.park() |
| 19 | if err != nil { |
| 20 | t.Fatal(err) |
| 21 | } |
| 22 | f.tags(a.Successor.Stamp) |
| 23 | before, err := f.p.ledger.read(a.ID) |
| 24 | if err != nil || before.Complete || !before.Parked { |
| 25 | t.Fatal("expected pending completion", err) |
| 26 | } |
| 27 | actor := f.actor |
| 28 | if change == "enrollment" { |
| 29 | f.actor.Enrollment = "new-enrollment" |
| 30 | } else { |
| 31 | f.actor.Binding = "8" |
| 32 | } |
| 33 | // Reconstruct the authority object, retaining only durable journals. |
| 34 | root := &servingParkRoot{root: f.p.root, ledger: &servingParkLedger{state: f.p.ledger.state, fs: f.p.ledger.fs, journal: f.f.journal, now: f.p.ledger.now}} |
| 35 | if err := root.completeServingDatabasePark(t.Context(), f.conn, a.ID, a.Successor); err == nil { |
| 36 | t.Fatal("changed deployment identity completed old park") |
| 37 | } |
| 38 | if _, err := root.parkServingDatabase(t.Context(), f.conn, a.Successor, hostbox.ServingArtifactIdentity{}); err == nil { |
| 39 | t.Fatal("changed deployment identity replayed old park") |
| 40 | } |
| 41 | after, err := root.ledger.read(a.ID) |
| 42 | if err != nil || !reflect.DeepEqual(before, after) { |
| 43 | t.Fatal("refusal rewrote pending evidence", err) |
| 44 | } |
| 45 | // Restoring the recorded identity permits the ordinary completion. |
| 46 | // A real enrollment change needs separately established root evidence. |
| 47 | f.actor = actor |
| 48 | if err := root.completeServingDatabasePark(t.Context(), f.conn, a.ID, a.Successor); err != nil { |
| 49 | t.Fatal(err) |
| 50 | } |
| 51 | after, err = root.ledger.read(a.ID) |
| 52 | if err != nil || !after.Complete { |
| 53 | t.Fatal("completion did not recover", err) |
| 54 | } |
| 55 | after.Complete = before.Complete |
| 56 | if !reflect.DeepEqual(before, after) { |
| 57 | t.Fatal("completion changed source, receipts or retention clock") |
| 58 | } |
| 59 | for suffix, want := range map[string]string{"": "served bytes", ".gen": "G", "-wal": "WAL", "-shm": "SHM"} { |
| 60 | body, err := os.ReadFile(filepath.Join(f.f.guard.DataDir, "instances", ".serving-parked", a.ID, filepath.Base(f.db)+suffix)) |
| 61 | if err != nil || string(body) != want { |
| 62 | t.Fatalf("retained %s: %q %v", suffix, body, err) |
| 63 | } |
| 64 | } |
| 65 | }) |
| 66 | } |
| 67 | } |
| 68 | |