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 "errors"
5 "fmt"
6 "testing"
7 "time"
8)
9
10// TestFenceAndStopAsMeetsOnlyItsOwnFence: an owned fence is the caller's
11// again only when it holds at the same generation with the same owner.
12// Any other fence (a plain one at a lower, equal or higher generation, or
13// another owner's at the same generation) refuses with ErrFenceHeld and
14// stays exactly as it was, with nothing stopped.
15func TestFenceAndStopAsMeetsOnlyItsOwnFence(t *testing.T) {
16 t.Parallel()
17 t.Run("own fence, re-collected", func(t *testing.T) {
18 a, e, _, _, _ := maintenanceAwake(t)
19 proof, err := a.FenceAndStopAs(maintenanceContext(t, time.Second), e.host, 12, "owner-1")
20 if err != nil || !proof.Matches(e.host, 12) {
21 t.Fatalf("first install: %v", err)
22 }
23 again, err := a.FenceAndStopAs(maintenanceContext(t, time.Second), e.host, 12, "owner-1")
24 if err != nil || !again.Matches(e.host, 12) {
25 t.Fatalf("the owner's repeat: %v", err)
26 }
27 if g := a.FenceGeneration(e.host); g != 12 {
28 t.Fatalf("generation %d, want 12", g)
29 }
30 })
31 type held struct {
32 gen uint64
33 owner string
34 }
35 for _, h := range []held{{5, ""}, {12, ""}, {20, ""}, {12, "owner-2"}, {5, "owner-1"}} {
36 t.Run(fmt.Sprintf("held %d by %q", h.gen, h.owner), func(t *testing.T) {
37 a, e, main, _, _ := maintenanceAwake(t)
38 a.mu.Lock()
39 a.fences = map[string]uint64{e.host: h.gen}
40 if h.owner != "" {
41 a.fenceOwners = map[string]string{e.host: h.owner}
42 }
43 a.mu.Unlock()
44 if _, err := a.FenceAndStopAs(maintenanceContext(t, time.Second), e.host, 12, "owner-1"); !errors.Is(err, ErrFenceHeld) {
45 t.Fatalf("err = %v, want ErrFenceHeld", err)
46 }
47 a.mu.Lock()
48 gen, owner := a.fences[e.host], a.fenceOwners[e.host]
49 a.mu.Unlock()
50 if gen != h.gen || owner != h.owner {
51 t.Fatalf("held fence is %d by %q, want %d by %q", gen, owner, h.gen, h.owner)
52 }
53 if main.calls.Load() != 0 || !a.Owns(e.host) {
54 t.Fatal("a refused owned fence stopped the host")
55 }
56 })
57 }
58 t.Run("an empty owner", func(t *testing.T) {
59 a, e, _, _, _ := maintenanceAwake(t)
60 if _, err := a.FenceAndStopAs(maintenanceContext(t, time.Second), e.host, 12, ""); !errors.Is(err, ErrFenceGeneration) || a.Fenced(e.host) {
61 t.Fatalf("err = %v, fenced %v", err, a.Fenced(e.host))
62 }
63 })
64}
65
66// TestAPlainFenceTakesOwnershipAway: FenceAndStop at the same generation
67// over an owned fence leaves a fence that is no longer the owner's, so
68// the owner can neither re-collect under it nor lift it.
69func TestAPlainFenceTakesOwnershipAway(t *testing.T) {
70 a, e, _, _, _ := maintenanceAwake(t)
71 if _, err := a.FenceAndStopAs(maintenanceContext(t, time.Second), e.host, 12, "owner-1"); err != nil {
72 t.Fatal(err)
73 }
74 if _, err := a.FenceAndStop(maintenanceContext(t, time.Second), e.host, 12); !errors.Is(err, ErrStopUnproven) && err != nil {
75 t.Fatal(err)
76 }
77 if _, err := a.FenceAndStopAs(maintenanceContext(t, time.Second), e.host, 12, "owner-1"); !errors.Is(err, ErrFenceHeld) {
78 t.Fatalf("err = %v, want ErrFenceHeld", err)
79 }
80 if a.LiftFenceAs(e.host, 12, "owner-1") {
81 t.Fatal("the owner lifted a fence it no longer owns")
82 }
83 if !a.Fenced(e.host) {
84 t.Fatal("the plain fence was lifted")
85 }
86}
87
88// TestLiftFenceAsLiftsOnlyTheOwnersFence: the lift names the generation
89// and the owner, and a plain LiftFence also clears the owner.
90func TestLiftFenceAsLiftsOnlyTheOwnersFence(t *testing.T) {
91 a, e, _, _, _ := maintenanceAwake(t)
92 if _, err := a.FenceAndStopAs(maintenanceContext(t, time.Second), e.host, 12, "owner-1"); err != nil {
93 t.Fatal(err)
94 }
95 if a.LiftFenceAs(e.host, 12, "owner-2") || a.LiftFenceAs(e.host, 11, "owner-1") || a.LiftFenceAs(e.host, 12, "") {
96 t.Fatal("lifted with the wrong owner or generation")
97 }
98 if !a.LiftFenceAs(e.host, 12, "owner-1") || a.Fenced(e.host) {
99 t.Fatal("the owner's lift did not lift its fence")
100 }
101 if _, err := a.FenceAndStopAs(maintenanceContext(t, time.Second), e.host, 13, "owner-1"); err != nil && !errors.Is(err, ErrStopUnproven) {
102 t.Fatal(err)
103 }
104 if !a.LiftFence(e.host, 13) {
105 t.Fatal("plain lift")
106 }
107 a.mu.Lock()
108 _, left := a.fenceOwners[e.host]
109 a.mu.Unlock()
110 if left {
111 t.Fatal("a plain lift left the owner behind")
112 }
113}
114
115// TestAnOwnedCallNeverTakesBackAFenceReplacedWhileItWaited (fix round 1,
116// M1; ruling 75): FenceAndStopAs installs its owned fence and waits for
117// the stop. Meanwhile a plain FenceAndStop installs at the same generation,
118// or a plain LiftFence lifts it. When the stop completes the owned call
119// answers ErrStopUnproven and leaves the fence as the other caller left
120// it: still plain and unowned, or still lifted, never the owner's again.
121func TestAnOwnedCallNeverTakesBackAFenceReplacedWhileItWaited(t *testing.T) {
122 t.Parallel()
123 for _, meanwhile := range []string{"a plain fence", "a plain lift"} {
124 t.Run(meanwhile, func(t *testing.T) {
125 a, e, _, _, _ := maintenanceAwake(t)
126 stopped := make(chan struct{})
127 a.mu.Lock()
128 e.state, e.stopped = actStopping, stopped
129 a.mu.Unlock()
130 type answer struct {
131 proof *StopEvidence
132 err error
133 }
134 done := make(chan answer, 1)
135 go func() {
136 proof, err := a.FenceAndStopAs(maintenanceContext(t, 30*time.Second), e.host, 12, "owner-1")
137 done <- answer{proof, err}
138 }()
139 for deadline := time.Now().Add(5 * time.Second); ; {
140 a.mu.Lock()
141 installed := a.fences[e.host] == 12 && a.fenceOwners[e.host] == "owner-1"
142 a.mu.Unlock()
143 if installed {
144 break
145 }
146 if time.Now().After(deadline) {
147 t.Fatal("the owned call never installed its fence")
148 }
149 time.Sleep(time.Millisecond)
150 }
151 switch meanwhile {
152 case "a plain fence":
153 // The stop is still in progress, so the plain call installs and
154 // then waits out its own deadline: ErrStopUnproven.
155 if _, err := a.FenceAndStop(maintenanceContext(t, 500*time.Millisecond), e.host, 12); !errors.Is(err, ErrStopUnproven) {
156 t.Fatalf("the plain fence: %v", err)
157 }
158 case "a plain lift":
159 if !a.LiftFence(e.host, 12) {
160 t.Fatal("the plain lift lifted nothing")
161 }
162 }
163 a.mu.Lock()
164 delete(a.entries, e.host)
165 a.mu.Unlock()
166 close(stopped)
167 var got answer
168 select {
169 case got = <-done:
170 case <-time.After(30 * time.Second):
171 t.Fatal("the owned call never answered after the stop completed")
172 }
173 if !errors.Is(got.err, ErrStopUnproven) || got.proof != nil {
174 t.Fatalf("the owned call after its fence was replaced: %v %v", got.proof, got.err)
175 }
176 a.mu.Lock()
177 gen := a.fences[e.host]
178 owner, owned := a.fenceOwners[e.host]
179 a.mu.Unlock()
180 want := uint64(12)
181 if meanwhile == "a plain lift" {
182 want = 0
183 }
184 if gen != want || owned {
185 t.Fatalf("the fence is %d owned by %q (%v), want %d unowned", gen, owner, owned, want)
186 }
187 })
188 }
189}
190