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 "crypto/sha256"
6 "encoding/hex"
7 "encoding/json"
8 "fmt"
9 "os"
10 "strings"
11 "testing"
12 "time"
13
14 "github.com/carlosframework/platform/internal/registry"
15 "github.com/carlosframework/platform/internal/release"
16)
17
18func seedHostRootHistory(t *testing.T, rt registry.Route) *release.RootHistory {
19 t.Helper()
20 b := release.HistoryBinding{Host: rt.Host, Account: rt.Account, App: rt.App}
21 store := &rollbackMemory{files: map[string][]byte{}}
22 h := &release.RootHistory{Store: store}
23 id := sha256.Sum256([]byte(b.Account + "\x00" + b.App + "\x00" + b.Host))
24 rawBinding, _ := json.Marshal(b)
25 raw := fmt.Sprintf(`{"schema":1,"binding":%s,"revision":1,"programs":{"revision":1,"complete":true,"values":null},"refusals":{"revision":1,"complete":true,"values":null}}`, rawBinding)
26 if err := store.Write(hex.EncodeToString(id[:])+".json", []byte(raw)); err != nil {
27 t.Fatal(err)
28 }
29 at := time.Now().UTC().Add(-time.Hour).Format(time.RFC3339Nano)
30 raw = fmt.Sprintf(`{"schema":1,"revision":1,"complete":{%q:1},"observed":%q,"clock":{"jitter":0.1,"channels":{}}}`, b.Account+"|"+b.App, at)
31 if err := store.Write("bake.json", []byte(raw)); err != nil {
32 t.Fatal(err)
33 }
34 id = sha256.Sum256([]byte(b.Account + "\x00" + b.App))
35 raw = fmt.Sprintf(`{"schema":1,"account":%q,"app":%q,"revision":1,"complete":true,"hosts":[%q]}`, b.Account, b.App, b.Host)
36 if err := store.Write(hex.EncodeToString(id[:])+".app", []byte(raw)); err != nil {
37 t.Fatal(err)
38 }
39 return h
40}
41
42func TestPlan14RootHistoryProductionPass(t *testing.T) {
43 t.Parallel()
44 for _, attack := range []string{"positive", "legacy-deleted", "legacy-recovered", "failed-ready", "record-change"} {
45 t.Run(attack, func(t *testing.T) {
46 e, rt, u, c, pub := plan14AdoptionFixture(t)
47 h := seedHostRootHistory(t, rt)
48 b := release.HistoryBinding{Host: rt.Host, Account: rt.Account, App: rt.App}
49 if attack == "legacy-deleted" || attack == "legacy-recovered" {
50 ptr, err := release.Channel(context.Background(), e.store, pub, rt.Account, rt.App, rt.Channel)
51 if err != nil {
52 t.Fatal(err)
53 }
54 ref := release.Refusal{Host: rt.Host, App: rt.App, Version: ptr.Version, PointerPromotedAt: ptr.PromotedAt, RefusedAt: time.Now(), Reason: "failed"}
55 if err := h.AddRefusal(b, ref); err != nil {
56 t.Fatal(err)
57 }
58 if attack == "legacy-recovered" {
59 ref.Recovered = true
60 ref.Version = "v1"
61 if err := release.WriteRefusal(e.bake, ref); err != nil {
62 t.Fatal(err)
63 }
64 }
65 }
66 if attack == "failed-ready" {
67 c.ready = func(registry.Route) func() error { return func() error { return os.ErrPermission } }
68 }
69 if attack == "record-change" {
70 rt.Account = "bap"
71 if err := e.root.reg.Upsert(rt); err != nil {
72 t.Fatal(err)
73 }
74 }
75 deps := boundRootHistory(h, c.resolve, e.root.obs)
76 forward := rootRestartRouteWith(e.root.reg, u, u.opt, func(string) string { return "5s" }, e.root.obs.recordSettled(e.bake), c)
77 err := release.Pass(context.Background(), e.root.reg, e.store, pub, u.opt, forward,
78 release.BakeDir(e.bake), release.WithRootHistory(deps), release.WithRollbackRestart(rollbackUnitRestart(e.root.reg, u, u.opt, func(string) string { return "5s" }, c)))
79 if attack == "positive" {
80 if err != nil || len(u.effects) != 1 || u.exe != release.VersionPath(u.opt, "bab", "v2", "console") {
81 t.Fatalf("root positive: %v, %+v", err, u.effects)
82 }
83 } else if attack == "failed-ready" {
84 if err == nil {
85 t.Fatal("failed readiness accepted")
86 }
87 if _, err := h.Refusals(b); err == nil {
88 t.Fatal("failed update lost its pending obligation")
89 }
90 } else if len(u.effects) != 0 {
91 t.Fatalf("%s restarted: %v", attack, u.effects)
92 }
93 })
94 }
95}
96
97func TestPlan14RootHistoryMintCensus(t *testing.T) {
98 t.Parallel()
99 for _, state := range []string{"complete", "refused", "unknown", "census-error", "missing-probe", "no-census-complete"} {
100 t.Run(state, func(t *testing.T) {
101 e, rt, _, _ := plan14ProofFixture(t)
102 h := seedHostRootHistory(t, rt)
103 e.root.refusals = h.MintRefusals
104 e.root.mintCensus = func() (bool, error) { return true, nil }
105 switch state {
106 case "refused":
107 if err := h.AddRefusal(release.HistoryBinding{Host: rt.Host, Account: rt.Account, App: rt.App}, release.Refusal{Host: rt.Host, App: rt.App, Version: "v1", SHA256: e.sha, RefusedAt: time.Now(), Recovered: true}); err != nil {
108 t.Fatal(err)
109 }
110 case "unknown":
111 h.Store = &rollbackMemory{files: map[string][]byte{}}
112 case "census-error":
113 e.root.mintCensus = func() (bool, error) { return true, os.ErrPermission }
114 case "missing-probe":
115 e.root.mintCensus = nil
116 case "no-census-complete":
117 e.root.mintCensus = func() (bool, error) { return false, nil }
118 }
119 e.root.run(context.Background())
120 if (e.proof(t) != nil) != (state == "complete") {
121 t.Fatalf("mint %s: %v", state, e.logs)
122 }
123 })
124 }
125}
126
127func TestPlan14ProgramFailureStillRecordsHealth(t *testing.T) {
128 t.Parallel()
129 for _, census := range []bool{false, true} {
130 t.Run(fmt.Sprint(census), func(t *testing.T) {
131 e, _, _, _ := plan14ProofFixture(t)
132 e.root.mintCensus = func() (bool, error) { return census, nil }
133 e.root.recordProgram = func(liveBuild) error { return os.ErrPermission }
134 e.root.run(context.Background())
135 healthy := release.ReadHealthy(e.root.historyDir(), "bab", "console")
136 if len(healthy) != 1 || healthy[0].SHA256 != e.sha {
137 t.Fatalf("program error suppressed healthy record: %+v", healthy)
138 }
139 if e.proof(t) != nil {
140 t.Fatal("mint ignored program error")
141 }
142 if !strings.Contains(strings.Join(e.logs, "\n"), "recording the installed program") {
143 t.Fatalf("missing diagnostic: %v", e.logs)
144 }
145 })
146 }
147}
148
149func TestPlan14RootHistoryCanonicalBinding(t *testing.T) {
150 t.Parallel()
151 for _, field := range []string{"unchanged", "account", "app", "unit", "upstream", "readiness"} {
152 t.Run(field, func(t *testing.T) {
153 e, rt, _, source := plan14ProofFixture(t)
154 deps := boundRootHistory(&release.RootHistory{}, source.resolve, e.root.obs)
155 candidate := rt
156 switch field {
157 case "account":
158 candidate.Account = "bap"
159 case "app":
160 candidate.App = "other"
161 case "unit":
162 candidate.Unit = "other.service"
163 case "upstream":
164 candidate.Addr = ""
165 case "readiness":
166 candidate.Health = ""
167 }
168 b, revision, err := deps.Resolve(context.Background(), candidate)
169 if field == "unchanged" {
170 if err != nil || revision == "" || b.Account != "bab" || b.App != "console" || b.Host != rt.Host {
171 t.Fatalf("canonical binding: %+v %q %v", b, revision, err)
172 }
173 } else if err == nil {
174 t.Fatalf("forged %s accepted", field)
175 }
176 })
177 }
178}
179
180func TestPlan14RootHistoryInstalledRequiresObservation(t *testing.T) {
181 t.Parallel()
182 for _, attack := range []string{"unchanged", "not-ready", "wrong-version", "changed-record"} {
183 t.Run(attack, func(t *testing.T) {
184 e, rt, _, source := plan14ProofFixture(t)
185 version := "v1"
186 if attack == "wrong-version" {
187 version = "v2"
188 }
189 if attack == "not-ready" {
190 e.root.obs.ready = func(registry.Route) func() error { return nil }
191 }
192 deps := boundRootHistory(&release.RootHistory{}, source.resolve, e.root.obs)
193 if attack == "changed-record" {
194 rt.Account = "bap"
195 }
196 _, err := deps.Installed(context.Background(), rt, version)
197 if attack == "unchanged" && err != nil {
198 t.Fatal(err)
199 }
200 if attack != "unchanged" && err == nil {
201 t.Fatalf("%s granted installed history", attack)
202 }
203 })
204 }
205}
206