carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package rollout
2
3import (
4 "context"
5 "testing"
6
7 "github.com/carlosframework/platform/internal/activator"
8 "github.com/carlosframework/platform/internal/appconfig"
9 "github.com/carlosframework/platform/internal/status"
10)
11
12// Final review F4: every plan carries a token of its own, never zero, and
13// the delivered config revision at the moment it was planned.
14func TestEveryPlanCarriesItsOwnTokenAndConfigRevision(t *testing.T) {
15 ctx := context.Background()
16 h := newHarness(t, "a")
17 h.stage("v2")
18 h.registerThenInstall("v2", "a")
19 h.wakeHost("a")
20 h.m.ConfigDelivered("a", "sha256:c1")
21 p1 := h.m.Plan(ctx, h.rows.get("a"))
22 h.m.ConfigDelivered("a", "sha256:c2")
23 p2 := h.m.Plan(ctx, h.rows.get("a"))
24 if p1.Token == 0 || p2.Token == 0 || p1.Token == p2.Token {
25 t.Fatalf("tokens %d and %d: want two distinct, nonzero tokens", p1.Token, p2.Token)
26 }
27 if p1.ConfigRev != "sha256:c1" || p2.ConfigRev != "sha256:c2" {
28 t.Fatalf("config revisions %q and %q: want each plan's delivered revision", p1.ConfigRev, p2.ConfigRev)
29 }
30}
31
32// Final review F4, the reviewer's sequence: an old first wake's observer
33// delivers its GenCancelled late, after the host's new first wake of B has
34// registered. A sibling then fails B, and root moves the box's routes on to
35// C. The late event belongs to the old wake: it must not clear the new
36// wake's registration, so the Pass keeps B's record and the new wake's
37// check refuses B. Keyed by host alone, the old event deleted the newer
38// entry, the Pass pruned the record, and the check let the failed B boot.
39func TestAStaleOutcomeLeavesANewerWakesRegistration(t *testing.T) {
40 ctx := context.Background()
41 sibling := FailureRecord{Account: "bab", App: "blog", Build: "v2", Host: "b", At: t0}
42 h := newHarness(t, "a")
43 h.stage("v2", "v3")
44 h.install("v2", "a")
45 h.wakeHost("a")
46 old := h.m.Plan(ctx, h.rows.get("a"))
47 g := activator.Gen{N: 7, Build: "v2", Digest: "sha-v2"}
48 h.m.OnGen(activator.GenEvent{Kind: activator.GenHealthy, Route: h.rows.get("a"), Gen: g, FirstWake: true,
49 Token: old.Token, ConfigRev: old.ConfigRev})
50
51 // The instance stopped inside its observation and woke again: a new first
52 // wake of v2, planned while the old observer's outcome is still on its way.
53 h.wakeHost("a")
54 cur := h.m.Plan(ctx, h.rows.get("a"))
55 if !cur.FirstWake || cur.Boot.Version != "v2" || cur.Check == nil {
56 t.Fatalf("plan %+v: want a new first wake onto v2 with its check", cur)
57 }
58 h.m.OnGen(activator.GenEvent{Kind: activator.GenCancelled, Route: h.rows.get("a"), Gen: g, FirstWake: true,
59 Token: old.Token, ConfigRev: old.ConfigRev})
60
61 if _, err := h.book.Record(sibling); err != nil {
62 t.Fatal(err)
63 }
64 h.install("v3", "a") // no route on the box installs v2 now
65 h.pass()
66 if _, ok := h.book.Lookup("bab", "blog", "v2"); !ok {
67 t.Fatal("the Pass pruned v2's record: the old wake's late event cleared the new wake's registration")
68 }
69 if err := cur.Check("v2"); err == nil {
70 t.Fatal("the new wake's check let v2 boot after it failed on a sibling")
71 }
72
73 // The new wake's own outcome does let the record go.
74 h.m.OnGen(activator.GenEvent{Kind: activator.GenCancelled, Route: h.rows.get("a"), FirstWake: true,
75 Token: cur.Token, ConfigRev: cur.ConfigRev})
76 h.pass()
77 if _, ok := h.book.Lookup("bab", "blog", "v2"); ok {
78 t.Fatal("the new wake reported its outcome, and the next Pass still kept v2's record")
79 }
80}
81
82// Final review F4: an event of an older wake does nothing to a newer
83// wake's state. A late GenHealthy of the older wake neither opens an
84// observation nor activates a config revision: with the revision read from
85// the host (the newest plan's snapshot), it acknowledged c2 while the old
86// wake's process ran c1. A late GenCancelled of it does not close the newer
87// wake's observation. The newer wake's own events activate the revision
88// they carry.
89func TestAStaleOutcomeLeavesANewerWakesObservationAndConfig(t *testing.T) {
90 ctx := context.Background()
91 h := newHarness(t, "a")
92 h.stage("v2")
93 h.registerThenInstall("v2", "a")
94 h.wakeHost("a")
95 var acks []appconfig.ConfigAck
96 ack := h.m.DeferAck(func(_ context.Context, a appconfig.ConfigAck) { acks = append(acks, a) })
97
98 h.m.ConfigDelivered("a", "sha256:c1")
99 old := h.m.Plan(ctx, h.rows.get("a"))
100 // The older wake's plan was replaced (a replan's first token never gets
101 // an event) after config sync delivered c2.
102 h.m.ConfigDelivered("a", "sha256:c2")
103 ack(appconfig.ConfigAck{Host: "a"})
104 cur := h.m.Plan(ctx, h.rows.get("a"))
105
106 g := activator.Gen{N: 7, Build: "v2", Digest: "sha-v2"}
107 h.m.OnGen(activator.GenEvent{Kind: activator.GenHealthy, Route: h.rows.get("a"), Gen: g, FirstWake: true,
108 Token: old.Token, ConfigRev: old.ConfigRev})
109 h.flushAcks()
110 if len(acks) != 0 {
111 t.Fatalf("acks %+v: an older wake's late event acknowledged a revision", acks)
112 }
113 if got := h.m.Status().Hosts["a"]; got.Attempt == status.AttemptSettling || !got.ConfigPending {
114 t.Fatalf("a = %+v: an older wake's late event opened an observation or activated a config", got)
115 }
116
117 h.m.OnGen(activator.GenEvent{Kind: activator.GenHealthy, Route: h.rows.get("a"), Gen: g, FirstWake: true,
118 Token: cur.Token, ConfigRev: cur.ConfigRev})
119 h.flushAcks()
120 if len(acks) != 1 {
121 t.Fatalf("acks %+v after the current wake's healthy event, want c2's", acks)
122 }
123 if got := h.m.Status().Hosts["a"]; got.Attempt != status.AttemptSettling || got.ConfigPending {
124 t.Fatalf("a = %+v: want settling with c2 active", got)
125 }
126
127 h.m.OnGen(activator.GenEvent{Kind: activator.GenCancelled, Route: h.rows.get("a"), Gen: g, FirstWake: true,
128 Token: old.Token, ConfigRev: old.ConfigRev})
129 if got := h.m.Status().Hosts["a"]; got.Attempt != status.AttemptSettling {
130 t.Fatalf("a = %+v: an older wake's late GenCancelled closed the newer wake's observation", got)
131 }
132 h.m.OnGen(activator.GenEvent{Kind: activator.GenCancelled, Route: h.rows.get("a"), Gen: g, FirstWake: true,
133 Token: cur.Token, ConfigRev: cur.ConfigRev})
134 if got := h.m.Status().Hosts["a"]; got.Attempt == status.AttemptSettling {
135 t.Fatalf("a = %+v: the wake's own GenCancelled left it settling", got)
136 }
137}
138