carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "os"
6 "path/filepath"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/carlosframework/platform/internal/registry"
12 "github.com/carlosframework/platform/internal/release"
13 "github.com/carlosframework/platform/internal/rootstate"
14)
15
16func TestPlan14GenerationRetirementReleasesExactPin(t *testing.T) {
17 f, disk, _ := generationLaunchFixture(t)
18 permit, err := f.root.generations.reserve(t.Context(), f.root, f.peer, f.req)
19 if err != nil {
20 t.Fatal(err)
21 }
22 r := permit.reservation
23 opt := f.root.generations.optDir
24 stageSweepVersion(t, opt, "bap", "v2", "other", 48*time.Hour)
25 stageSweepVersion(t, opt, "bap", "v3", "pending", 48*time.Hour)
26 pending := r.Binding
27 pending.Attempt, pending.Invocation = "pending-v3", "pending-v3"
28 pending.Binding.Account, pending.Binding.App, pending.Version = "bap", "pending", "v3"
29 pending.Path = release.VersionPath(opt, "bap", "v3", "pending")
30 pending.Tree = filepath.Dir(pending.Path)
31 for path, dst := range map[string]*rootstate.FileIdentity{pending.Path: &pending.Executable, pending.Tree: &pending.TreeIdentity} {
32 file, err := os.Open(path)
33 if err != nil {
34 t.Fatal(err)
35 }
36 *dst, err = rootstate.Identity(file)
37 file.Close()
38 if err != nil {
39 t.Fatal(err)
40 }
41 }
42 p, err := permit.ledger.Reserve(pending)
43 if err != nil {
44 t.Fatal(err)
45 }
46 when := time.Now().Add(-48 * time.Hour)
47 if err := os.Chtimes(r.Binding.Path, when, when); err != nil {
48 t.Fatal(err)
49 }
50 source := generationPinSource{open: func() (rootstate.Store, error) { return disk, nil }, verify: func(context.Context, []release.GenerationReservation) error { return nil }}
51 // The injected verifier exercises the destructive consumer contract; this
52 // fixture does not establish production installation/kernel completeness.
53 rows := fakeLister{routes: []registry.Route{{Host: "other.example", Account: "bap", App: "other", Version: "v2"}}}
54 gen := noGenerations(t)
55 sweepStagedVersions(rows, opt, gen, 6*time.Hour, time.Now(), source)
56 if !staged(t, opt, "v1") {
57 t.Fatal("pending generation lost pin")
58 }
59 if err := permit.ledger.Retire(t.Context(), r, func(context.Context, release.GenerationReservation) error { return nil }, func(context.Context, release.GenerationReservation) (string, error) { return "", os.ErrPermission }); err == nil {
60 t.Fatal("retired without kernel witness")
61 }
62 sweepStagedVersions(rows, opt, gen, 6*time.Hour, time.Now(), source)
63 if !staged(t, opt, "v1") {
64 t.Fatal("incomplete retirement lost pin")
65 }
66 if err := permit.ledger.Retire(t.Context(), r, func(context.Context, release.GenerationReservation) error { return nil }, func(context.Context, release.GenerationReservation) (string, error) {
67 return strings.Repeat("a", 64), nil
68 }); err != nil {
69 t.Fatal(err)
70 }
71 sweepStagedVersions(rows, opt, gen, 72*time.Hour, time.Now(), source)
72 if !staged(t, opt, "v1") {
73 t.Fatal("retirement bypassed sweep grace")
74 }
75 sweepStagedVersions(rows, opt, gen, 6*time.Hour, time.Now(), source)
76 if staged(t, opt, "v1") || !staged(t, opt, "v2") || !staged(t, opt, "v3") {
77 t.Fatal("sweep did not release only the retired version")
78 }
79 if got, err := permit.ledger.Read(p.Binding); err != nil || got != p {
80 t.Fatal("pending V3 changed", err)
81 }
82 // Once swept, the retirement tombstone must still read without the old
83 // executable or tree, and must never permit the original launch again.
84 if _, err := source.StagedPins(t.Context(), opt); err != nil {
85 t.Fatal(err)
86 }
87 if _, err := permit.ledger.Reserve(r.Binding); err == nil {
88 t.Fatal("swept reservation replayed")
89 }
90}
91
92func TestPlan14InstalledPinClosure(t *testing.T) {
93 for _, state := range []string{"running", "controller-open", "closed"} {
94 t.Run(state, func(t *testing.T) {
95 f, disk, _ := generationLaunchFixture(t)
96 if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil {
97 t.Fatal(err)
98 }
99 r := readGenerationLaunch(t, disk)
100 if state != "running" {
101 if err := f.root.stop(t.Context(), f.peer, *f.req.Reservation); err != nil {
102 t.Fatal(err)
103 }
104 }
105 if state == "closed" {
106 if err := f.journal.CloseWriters(t.Context(), f.target); err != nil {
107 t.Fatal(err)
108 }
109 }
110 calls := 0
111 retire := generationRetirement{guard: f.root.Guard, journal: f.journal, kernel: func(context.Context, release.GenerationReservation) (string, error) {
112 calls++
113 return strings.Repeat("b", 64), nil
114 }}
115 ledger := &release.RootGenerations{Store: disk}
116 if err := retire.retire(t.Context(), ledger, r); err != nil {
117 t.Fatal(err)
118 }
119 got, err := ledger.Read(r.Binding)
120 if err != nil {
121 t.Fatal(err)
122 }
123 if (got.Retirement != "") != (state == "closed") || (calls > 0) != (state == "closed") {
124 t.Fatal("pin crossed incomplete closure", state, got, calls)
125 }
126 })
127 }
128}
129