| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "database/sql" |
| 5 | "errors" |
| 6 | "github.com/carlosframework/platform/internal/hostclaim" |
| 7 | "github.com/carlosframework/platform/internal/hostguard" |
| 8 | "github.com/carlosframework/platform/internal/provision" |
| 9 | "testing" |
| 10 | "time" |
| 11 | ) |
| 12 | |
| 13 | func TestPlan14Fix2InventoryReceiptPruned(t *testing.T) { |
| 14 | f, root, q := plan14InstallRegistration(t, hostclaim.KindLanding, provision.LandChecking) |
| 15 | state := &plan14RegistrationCrash{rollbackDisk: &rollbackDisk{dir: t.TempDir()}} |
| 16 | root.Stop.Coordinator.Guard.Root = hostguard.NewRootAuthority(state) |
| 17 | if _, err := root.commitRootInventory(t.Context(), q); err == nil { |
| 18 | t.Fatal("missing crash") |
| 19 | } |
| 20 | db, err := sql.Open("sqlite", f.journalPath) |
| 21 | if err != nil { |
| 22 | t.Fatal(err) |
| 23 | } |
| 24 | defer db.Close() |
| 25 | if _, err = db.Exec("DELETE FROM journal_requests"); err != nil { |
| 26 | t.Fatal(err) |
| 27 | } |
| 28 | if err := root.recoverRootInventory(t.Context()); err != nil { |
| 29 | t.Fatal(err) |
| 30 | } |
| 31 | view, err := root.Stop.Coordinator.Guard.Root.View(t.Context(), q.Target.Host, hostguard.Captured{Incarnation: q.Target.Incarnation, Stamp: q.Target.Stamp}) |
| 32 | if err != nil || view.Installation.InventoryRevision != 1 { |
| 33 | t.Fatalf("root commit not recovered: %+v %v", view, err) |
| 34 | } |
| 35 | } |
| 36 | func TestPlan14Fix2OldInventoryRequestLeavesNoIntent(t *testing.T) { |
| 37 | _, root, q := plan14InstallRegistration(t, hostclaim.KindLanding, provision.LandChecking) |
| 38 | q.IssuedAt = time.Now().Add(-time.Hour).UnixNano() |
| 39 | if _, err := root.commitRootInventory(t.Context(), q); err == nil { |
| 40 | t.Fatal("old request accepted") |
| 41 | } |
| 42 | pending, err := root.Stop.Coordinator.Guard.Root.PendingInstallations() |
| 43 | if err != nil || len(pending) != 0 { |
| 44 | t.Fatal("old request fenced host", pending, err) |
| 45 | } |
| 46 | } |
| 47 | func TestPlan14Fix2CommittedInventoryAfterETagChange(t *testing.T) { |
| 48 | t.Parallel() |
| 49 | f, root, q := plan14InstallRegistration(t, hostclaim.KindLanding, provision.LandChecking) |
| 50 | state := &plan14RegistrationCrash{rollbackDisk: &rollbackDisk{dir: t.TempDir()}} |
| 51 | root.Stop.Coordinator.Guard.Root = hostguard.NewRootAuthority(state) |
| 52 | if _, err := root.commitRootInventory(t.Context(), q); err == nil { |
| 53 | t.Fatal("missing crash") |
| 54 | } |
| 55 | // Change the claim without changing its holder tuple. |
| 56 | obs := f.read() |
| 57 | obs.Claim.Op.Journal["fix2"] = "changed" |
| 58 | if _, err := hostclaim.Update(t.Context(), f.claims, f.read(), func(c *hostclaim.Claim) error { c.Op.Journal["fix2"] = "changed"; return nil }); err != nil { |
| 59 | t.Fatal(err) |
| 60 | } |
| 61 | // The committed request still completes after an ETag-only change. |
| 62 | if err := root.recoverRootInventory(t.Context()); err != nil { |
| 63 | t.Fatal(err) |
| 64 | } |
| 65 | _, err := root.Stop.Coordinator.Guard.Root.PendingInstallations() |
| 66 | if err != nil && !errors.Is(err, hostguard.ErrFenced) { |
| 67 | t.Fatal(err) |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | func TestPlan14Fix2SupersededInventoryIntentAbandoned(t *testing.T) { |
| 72 | f, root, q := plan14InstallRegistration(t, hostclaim.KindLanding, provision.LandChecking) |
| 73 | state := &rollbackDisk{dir: t.TempDir()} |
| 74 | root.Stop.Coordinator.Guard.Root = hostguard.NewRootAuthority(state) |
| 75 | state.afterWrite = func(string, []byte) { state.afterWrite = nil; _ = root.Reconcile.Registry.Close() } |
| 76 | if _, err := root.commitRootInventory(t.Context(), q); err == nil { |
| 77 | t.Fatal("closed registry accepted commit") |
| 78 | } |
| 79 | pending, err := root.Stop.Coordinator.Guard.Root.PendingInstallations() |
| 80 | if err != nil || len(pending) != 1 { |
| 81 | t.Fatal("missing pending intent", pending, err) |
| 82 | } |
| 83 | if _, err := hostclaim.Update(t.Context(), f.claims, f.read(), func(c *hostclaim.Claim) error { c.Op.Journal["fix2"] = "changed"; return nil }); err != nil { |
| 84 | t.Fatal(err) |
| 85 | } |
| 86 | if err := root.recoverRootInventory(t.Context()); err != nil { |
| 87 | t.Fatal(err) |
| 88 | } |
| 89 | pending, err = root.Stop.Coordinator.Guard.Root.PendingInstallations() |
| 90 | if err != nil || len(pending) != 0 { |
| 91 | t.Fatal("superseded intent survived", pending, err) |
| 92 | } |
| 93 | _, err = root.Stop.Coordinator.Guard.Root.View(t.Context(), q.Target.Host, hostguard.Captured{Incarnation: q.Target.Incarnation, Stamp: q.Target.Stamp}) |
| 94 | if !errors.Is(err, hostguard.ErrFenced) { |
| 95 | t.Fatal("abandon removed quarantine", err) |
| 96 | } |
| 97 | } |
| 98 | |