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 "sync"
5 "testing"
6 "time"
7
8 "github.com/carlosframework/platform/internal/registry"
9)
10
11// stopEventGate is an EventSink that holds a terminal stop at its first
12// line ("instance stopping"), before it reads the sidecar join, until the
13// test lets it go: the stop's read of the join can then be placed after
14// anything the test arranges.
15type stopEventGate struct {
16 once sync.Once
17 entered chan struct{}
18 release chan struct{}
19}
20
21func newStopEventGate() *stopEventGate {
22 return &stopEventGate{entered: make(chan struct{}), release: make(chan struct{})}
23}
24
25func (g *stopEventGate) App(string, string, string, string) {}
26
27func (g *stopEventGate) Event(_, _, _, _, msg string) {
28 if msg != "instance stopping" {
29 return
30 }
31 first := false
32 g.once.Do(func() { first = true })
33 if !first {
34 return
35 }
36 close(g.entered)
37 <-g.release
38}
39
40// shrinkSidecarJoinWait shortens a flag-off stop's bounded wait on an
41// in-flight sidecar start for one test.
42func shrinkSidecarJoinWait(t *testing.T) {
43 t.Helper()
44 prev := sidecarStartJoinWait
45 sidecarStartJoinWait = 50 * time.Millisecond
46 t.Cleanup(func() { sidecarStartJoinWait = prev })
47}
48
49// Fix wave 1, item 1 (Astra final review 1, finding 1): the missed-join
50// window. A sidecar restart returns while its entry is stopping, and the
51// stop reads the join only after the restart's critical section has run
52// but while its cleanup is still stopping the sidecar it brought back. The
53// stop must still see the join, and the lease goes only once that sidecar
54// is stopped.
55func TestAStopThatReadsTheJoinDuringTheRestartsCleanupWaitsForIt(t *testing.T) {
56 shrinkSidecarJoinWait(t)
57 a, b, _, _ := testActivator(t, time.Hour)
58 h := &hostEmptyFake{fakeBackend: b}
59 a.backend = h
60 g := newStopEventGate()
61 a.Logs = g
62 b.sidecarStaged = true
63 host := "missed-join.example.com"
64
65 arrangeAwake(t, a, testRoute(host))
66 h.reset()
67 b.lastSidecar().exit() // the sweeper now wants a restart
68
69 startGate, entered := make(chan struct{}), make(chan struct{})
70 scStopGate := make(chan struct{})
71 b.mu.Lock()
72 b.sidecarGate, b.sidecarStartEntered = startGate, entered
73 b.sidecarStopGate = scStopGate // the returned sidecar's stop blocks
74 b.mu.Unlock()
75 a.sweepOnce()
76 <-entered
77
78 rep := b.lastRep()
79 if !a.Bounce(host) {
80 t.Fatal("Bounce did not find the awake entry")
81 }
82 <-g.entered // the stop is running, and has not read the join yet
83
84 close(startGate) // the restart returns onto a stopping entry
85 waitFor(t, "the restart to bring back its sidecar", func() bool { return b.sidecarStartCount() == 2 })
86 sc := b.lastSidecar()
87 rep.setOnClose(func() {
88 if !sc.isStopped() {
89 t.Error("the lease was released while the sidecar the restart brought back was still being stopped")
90 }
91 })
92 waitFor(t, "the restart's cleanup to begin stopping it", func() bool { return sc.stopEntered() >= 1 })
93 close(g.release) // now the stop reads the join
94
95 time.Sleep(4 * sidecarStartJoinWait)
96 for range 3 {
97 a.sweepOnce()
98 }
99 if rep.isClosed() {
100 t.Fatal("the lease went while the returned sidecar's stop was still in flight")
101 }
102
103 close(scStopGate)
104 waitFor(t, "the stop to finish once the returned sidecar is stopped", func() bool {
105 a.sweepOnce()
106 return rep.isClosed() && a.entryCount() == 0
107 })
108}
109
110// Fix wave 1, item 1: a nested instance's restart returns a sidecar whose
111// stop cannot prove its exit (ErrStillRunning). Nested rows skip the
112// host-cgroup proof, so the sidecar's own stop proof is the only one: the
113// stop stays pending with the lease, the sweep re-stops the sidecar, and
114// the lease goes only after it has exited.
115func TestANestedStopKeepsTheLeaseOverAReturnedSidecarItCannotProve(t *testing.T) {
116 shrinkSidecarJoinWait(t)
117 a, b, _, _ := testActivator(t, time.Hour)
118 h := &hostEmptyFake{fakeBackend: b}
119 a.backend = h
120 b.layoutFn = func(registry.Route) (Layout, string) { return LayoutNested, "" }
121 b.sidecarStaged = true
122 host := "nested-stuck.example.com"
123
124 arrangeAwake(t, a, testRoute(host))
125 h.reset()
126 b.lastSidecar().exit()
127
128 startGate, entered := make(chan struct{}), make(chan struct{})
129 b.mu.Lock()
130 b.sidecarGate, b.sidecarStartEntered = startGate, entered
131 b.sidecarStuck = true // the returned sidecar's stop answers ErrStillRunning
132 b.mu.Unlock()
133 a.sweepOnce()
134 <-entered
135
136 rep, inst := b.lastRep(), b.lastProc()
137 if !a.Bounce(host) {
138 t.Fatal("Bounce did not find the awake entry")
139 }
140 waitFor(t, "the instance to stop", inst.isStopped)
141 close(startGate)
142 waitFor(t, "the restart to bring back its sidecar", func() bool { return b.sidecarStartCount() == 2 })
143 sc := b.lastSidecar()
144 rep.setOnClose(func() {
145 if !sc.Exited() {
146 t.Error("the lease was released while the returned sidecar had not exited")
147 }
148 })
149 waitFor(t, "the restart's stop of it", func() bool { return sc.stopCalls() >= 1 })
150 time.Sleep(4 * sidecarStartJoinWait)
151 for range 3 {
152 a.sweepOnce()
153 }
154 if rep.isClosed() || a.entryCount() != 1 {
155 t.Fatalf("closed=%v entries=%d: the lease went over a sidecar whose stop was unproven", rep.isClosed(), a.entryCount())
156 }
157 waitFor(t, "the sweep to re-stop the unproven sidecar", func() bool {
158 a.sweepOnce()
159 return sc.stopCalls() >= 2
160 })
161
162 sc.exit()
163 waitFor(t, "the stop to finish once the sidecar exited", func() bool {
164 a.sweepOnce()
165 return rep.isClosed() && a.entryCount() == 0
166 })
167 if n := h.count(); n != 0 {
168 t.Fatalf("host proofs = %d: a nested stop has no host-cgroup proof", n)
169 }
170}
171
172// Fix wave 1, item 1: the co-stop's own sidecar, flag off and nested, whose
173// stop cannot prove its exit, is held to the same rule: the lease is kept
174// until it has exited.
175func TestANestedStopKeepsTheLeaseOverItsCoStoppedSidecarUntilItExits(t *testing.T) {
176 t.Parallel()
177 a, b, _, _ := testActivator(t, time.Hour)
178 h := &hostEmptyFake{fakeBackend: b}
179 a.backend = h
180 b.layoutFn = func(registry.Route) (Layout, string) { return LayoutNested, "" }
181 b.sidecarStaged = true
182 b.sidecarStuck = true
183 host := "nested-costop.example.com"
184
185 arrangeAwake(t, a, testRoute(host))
186 sc := b.lastSidecar()
187 rep, inst := b.lastRep(), b.lastProc()
188 rep.setOnClose(func() {
189 if !sc.Exited() {
190 t.Error("the lease was released while the co-stopped sidecar had not exited")
191 }
192 })
193 if !a.Bounce(host) {
194 t.Fatal("Bounce did not find the awake entry")
195 }
196 waitFor(t, "the instance to stop", inst.isStopped)
197 for range 3 {
198 a.sweepOnce()
199 }
200 if rep.isClosed() || a.entryCount() != 1 {
201 t.Fatalf("closed=%v entries=%d: the lease went over a sidecar whose stop was unproven", rep.isClosed(), a.entryCount())
202 }
203 sc.exit()
204 waitFor(t, "the stop to finish once the sidecar exited", func() bool {
205 a.sweepOnce()
206 return rep.isClosed() && a.entryCount() == 0
207 })
208}
209