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 "strings"
6 "sync"
7 "testing"
8 "time"
9
10 "github.com/carlosframework/platform/internal/instancectl"
11)
12
13// queuingControl is root's channel during an edge restart (#292): a start
14// whose job systemd has queued blocks until the client gives up, and the
15// job outlives that. Only a stop of the unit replaces it.
16type queuingControl struct {
17 *fakeControl
18 mu sync.Mutex
19 queued bool
20}
21
22func (q *queuingControl) Start(ctx context.Context, host string, role instancectl.Role, max int64) (string, error) {
23 q.rec("start " + host + " " + string(role))
24 q.mu.Lock()
25 q.queued = true
26 q.mu.Unlock()
27 <-ctx.Done()
28 return "", ctx.Err()
29}
30
31func (q *queuingControl) Stop(ctx context.Context, host string, role instancectl.Role) error {
32 q.mu.Lock()
33 q.queued = false
34 q.mu.Unlock()
35 return q.fakeControl.Stop(ctx, host, role)
36}
37
38func (q *queuingControl) stillQueued() bool {
39 q.mu.Lock()
40 defer q.mu.Unlock()
41 return q.queued
42}
43
44// A legacy start that is cancelled while root's start job is queued comes
45// back marked, so the wake's cleanup knows the job may still run; the
46// proof stops the unit, which clears the job, and only then passes.
47func TestACancelledLegacyStartIsMarkedAndItsStopClearsTheQueuedJob(t *testing.T) {
48 q := &queuingControl{fakeControl: &fakeControl{}}
49 unit := &fakeUnit{}
50 unit.set("inactive", "", false, false)
51 b, rt := nestedBackend(t, &fakeControl{}, unit)
52 b.Control = q
53
54 ctx, cancel := context.WithCancel(context.Background())
55 go func() {
56 for !q.stillQueued() {
57 time.Sleep(5 * time.Millisecond)
58 }
59 cancel()
60 }()
61 p, _, err := b.Start(ctx, rt)
62 if err == nil || p != nil {
63 t.Fatalf("Start = %v, %v; want no process and an error", p, err)
64 }
65 if !legacyStartMayRun(err) {
66 t.Fatalf("a cancelled legacy start is not marked as one whose job may run: %v", err)
67 }
68 if !q.stillQueued() {
69 t.Fatal("the fake lost its queued job on cancellation; it must model a job that survives")
70 }
71
72 pctx, pcancel := context.WithTimeout(context.Background(), 2*time.Second)
73 defer pcancel()
74 if err := b.LegacyRunGone(pctx, rt); err != nil {
75 t.Fatalf("LegacyRunGone: %v", err)
76 }
77 if q.stillQueued() {
78 t.Fatal("the proof passed with the start job still queued")
79 }
80 if !strings.Contains(q.joined(), "stop "+rt.Host+" run") {
81 t.Fatalf("the run unit was not stopped through instance-control; calls:\n%s", q.joined())
82 }
83}
84
85// The proof fails, and so keeps the lease, while the unit is not stopped
86// or its cgroup still holds a process.
87func TestLegacyRunGoneRefusesWhileTheUnitRuns(t *testing.T) {
88 t.Parallel()
89 for _, tc := range []struct {
90 name string
91 active string
92 populated bool
93 }{
94 {"active", "active", true},
95 {"deactivating", "deactivating", true},
96 {"inactive with processes left", "inactive", true},
97 } {
98 t.Run(tc.name, func(t *testing.T) {
99 unit := &fakeUnit{}
100 unit.set(tc.active, "inv", true, tc.populated)
101 b, rt := nestedBackend(t, &fakeControl{}, unit)
102 ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
103 defer cancel()
104 if err := b.LegacyRunGone(ctx, rt); err == nil {
105 t.Fatal("LegacyRunGone proved a unit that still runs")
106 }
107 })
108 }
109}
110