carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package activator
2
3import (
4 "context"
5 "errors"
6 "sync"
7 "testing"
8 "time"
9)
10
11// Final review wave 2, N2: a planned first wake that ran its build and was
12// ended with no verdict tells the scheduler the build ran (GenCancelled,
13// Executed, Gen.Build naming it), so a hold build's run holds the instance
14// off its good build. Two ways it happens: an intentional stop cancels the
15// observation of a healthy first wake, or the wake's lifecycle ends it after
16// the exec (here Shutdown during the health check). The second, which
17// reported nothing before, is reported for a hold first wake only, under the
18// host's outcome fence, so no new wake plans before the scheduler has it.
19// Since Task 5 every planned wake with a token ends with a terminal event:
20// one that ran nothing is cancelled, not Executed, and an overlap wake ended
21// the same way is cancelled Executed. Neither records anything, so neither
22// is delivered under the fence.
23func TestAFirstWakeEndedAfterItsExecReportsThatItRan(t *testing.T) {
24 const host = "one.example.com"
25 endInHealth := func(t *testing.T, mode SwapMode, refuse bool) (*Service, *genLog, *planGenBackend, chan bool) {
26 dir := t.TempDir()
27 good, next := stagedID(t, dir, "v1"), stagedID(t, dir, "v2")
28 plan := WakePlan{Boot: next, Mode: mode, FirstWake: true, Token: 41, ConfigRev: "sha256:c1"}
29 if mode == ModeOverlap {
30 plan.Fallback = &good
31 }
32 if refuse {
33 plan.Check = func(string) error { return errors.New("held") }
34 }
35 a, b, evs := planActivator(t, plan)
36 fenced := make(chan bool, 4)
37 a.OnGen = func(ev GenEvent) {
38 if ev.Kind == GenCancelled {
39 a.mu.Lock()
40 _, held := a.outcomeFences[host]
41 a.mu.Unlock()
42 fenced <- held
43 }
44 evs.add(ev)
45 }
46 entered := make(chan struct{})
47 var once sync.Once
48 a.genHealth = func(ctx context.Context, _ Gen) error {
49 once.Do(func() { close(entered) })
50 <-ctx.Done()
51 return ctx.Err()
52 }
53 go a.beginRequest(context.Background(), testRoute(host)) //nolint:errcheck
54 if !refuse {
55 <-entered
56 } else {
57 time.Sleep(100 * time.Millisecond)
58 }
59 a.Shutdown(context.Background())
60 return a, evs, b, fenced
61 }
62
63 t.Run("hold, ended in its health check", func(t *testing.T) {
64 _, evs, _, fenced := endInHealth(t, ModeHold, false)
65 ev := evs.wait(t, GenCancelled)
66 if !ev.Executed || ev.Gen.Build != "v2" || !ev.FirstWake || ev.Mode != ModeHold || ev.Token != 41 {
67 t.Fatalf("event %+v: want GenCancelled, Executed, naming v2, with the plan's token", ev)
68 }
69 if !<-fenced {
70 t.Fatal("the host's outcome fence was not held while the event was delivered")
71 }
72 })
73 t.Run("overlap, ended in its health check", func(t *testing.T) {
74 _, evs, _, fenced := endInHealth(t, ModeOverlap, false)
75 ev := evs.wait(t, GenCancelled)
76 if !ev.Executed || ev.Gen.Build != "v2" || ev.Mode != ModeOverlap || ev.Token != 41 {
77 t.Fatalf("event %+v: want GenCancelled, Executed, naming v2, in overlap mode, with the plan's token", ev)
78 }
79 if <-fenced {
80 t.Fatal("an overlap wake's cancellation records nothing, yet it held the outcome fence")
81 }
82 time.Sleep(100 * time.Millisecond)
83 if k := evs.kinds(); len(k) != 1 {
84 t.Fatalf("events %v: want the one cancellation", k)
85 }
86 })
87 t.Run("hold, refused before its exec", func(t *testing.T) {
88 _, evs, b, fenced := endInHealth(t, ModeHold, true)
89 ev := evs.wait(t, GenCancelled)
90 if ev.Executed || ev.Token != 41 || len(b.started()) != 0 {
91 t.Fatalf("event %+v, started %+v: a wake that ran nothing is cancelled, not Executed", ev, b.started())
92 }
93 if <-fenced {
94 t.Fatal("a cancellation with nothing run held the outcome fence")
95 }
96 time.Sleep(100 * time.Millisecond)
97 if k := evs.kinds(); len(k) != 1 {
98 t.Fatalf("events %v: want the one cancellation", k)
99 }
100 })
101 t.Run("hold, its observation cancelled", func(t *testing.T) {
102 shrinkFirstWake(t, 55*time.Second, 25*time.Second, 2*time.Second)
103 next := stagedID(t, t.TempDir(), "v2")
104 a, _, evs := planActivator(t, WakePlan{Boot: next, Mode: ModeHold, FirstWake: true, Token: 42})
105 mustServe(t, a)
106 evs.wait(t, GenHealthy)
107 a.BounceFor(host, CauseConfig)
108 ev := evs.wait(t, GenCancelled)
109 if !ev.Executed || ev.Gen.Build != "v2" || ev.Mode != ModeHold || ev.Token != 42 {
110 t.Fatalf("event %+v: want the cancelled observation reported as executed, naming v2", ev)
111 }
112 })
113}
114