| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "os" |
| 6 | "strings" |
| 7 | "testing" |
| 8 | |
| 9 | "github.com/carlosframework/platform/internal/hostclaim" |
| 10 | "github.com/carlosframework/platform/internal/hostguard" |
| 11 | "github.com/carlosframework/platform/internal/release" |
| 12 | ) |
| 13 | |
| 14 | type retiredReceiptLockStore struct { |
| 15 | *rollbackDisk |
| 16 | held bool |
| 17 | } |
| 18 | |
| 19 | func (s *retiredReceiptLockStore) Lock(context.Context, string) (func(), error) { |
| 20 | if s.held { |
| 21 | return nil, os.ErrPermission |
| 22 | } |
| 23 | s.held = true |
| 24 | return func() { s.held = false }, nil |
| 25 | } |
| 26 | |
| 27 | func TestPlan14GenerationRetirementReceiptLockOrder(t *testing.T) { |
| 28 | f, disk, _ := generationLaunchFixture(t) |
| 29 | in, err := f.root.Guard.Root.Installation(t.Context(), f.target.Host, hostguard.Captured{Incarnation: f.target.Incarnation, Stamp: f.target.Stamp}) |
| 30 | if err != nil { |
| 31 | t.Fatal(err) |
| 32 | } |
| 33 | state := &retiredReceiptLockStore{rollbackDisk: &rollbackDisk{dir: t.TempDir()}} |
| 34 | f.root.Guard.Root = hostguard.NewRootAuthority(state) |
| 35 | if err := f.root.Guard.EstablishRoot(t.Context(), in, func(*hostclaim.Claim) error { return nil }); err != nil { |
| 36 | t.Fatal(err) |
| 37 | } |
| 38 | if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil { |
| 39 | t.Fatal(err) |
| 40 | } |
| 41 | r := readGenerationLaunch(t, disk) |
| 42 | if err := f.root.stop(t.Context(), f.peer, *f.req.Reservation); err != nil { |
| 43 | t.Fatal(err) |
| 44 | } |
| 45 | if err := f.journal.CloseWriters(t.Context(), f.target); err != nil { |
| 46 | t.Fatal(err) |
| 47 | } |
| 48 | calls := 0 |
| 49 | v := generationRetirement{guard: f.root.Guard, journal: f.journal, kernel: func(context.Context, release.GenerationReservation) (string, error) { return "", os.ErrPermission }, retiredKernel: func(_ context.Context, _ release.GenerationReservation, view hostguard.FenceView) (string, error) { |
| 50 | if !state.held || view.Host != f.target.Host { |
| 51 | t.Fatal("retirement verification escaped exclusion") |
| 52 | } |
| 53 | calls++ |
| 54 | return strings.Repeat("a", 64), nil |
| 55 | }} |
| 56 | if err := v.retire(t.Context(), &release.RootGenerations{Store: disk}, r); err != nil { |
| 57 | t.Fatal(err) |
| 58 | } |
| 59 | if calls != 1 || state.held { |
| 60 | t.Fatalf("verification calls=%d lock=%v", calls, state.held) |
| 61 | } |
| 62 | } |
| 63 | |