carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package dbreads
2
3import (
4 "context"
5 "errors"
6 "testing"
7 "time"
8)
9
10type unitsSeam struct {
11 n int
12 err error
13}
14
15func (u *unitsSeam) count(context.Context) (int, error) { return u.n, u.err }
16
17func gib(n uint64) uint64 { return n << 30 }
18
19func TestAdmissionTwoReadsThenBusy(t *testing.T) {
20 t.Parallel()
21 u := &unitsSeam{}
22 a := newAdmission(u.count, func() (uint64, uint64, error) { return gib(100), gib(200), nil })
23 ctx := context.Background()
24 for _, id := range []string{"r1", "r2"} {
25 if err := a.admitRead(ctx, id); err != nil {
26 t.Fatalf("%s: %v", id, err)
27 }
28 }
29 if err := a.admitRead(ctx, "r3"); !errors.Is(err, errBusy) {
30 t.Fatalf("a third read: %v", err)
31 }
32 // The probe still has its connection (spec §7.1, codex 9).
33 if !a.opened() || !a.opened() || !a.admitProbe(ctx) || !a.opened() {
34 t.Fatal("two reads and a probe do not fit three connections")
35 }
36 if a.opened() {
37 t.Fatal("a fourth connection was allowed")
38 }
39}
40
41// T12, unit half: free space that fits one read admits one.
42func TestAdmissionDiskFitsOneOfTwo(t *testing.T) {
43 t.Parallel()
44 a := newAdmission((&unitsSeam{}).count, func() (uint64, uint64, error) { return gib(12), gib(20), nil })
45 ctx := context.Background()
46 if err := a.admitRead(ctx, "r1"); err != nil {
47 t.Fatal(err)
48 }
49 if err := a.admitRead(ctx, "r2"); !errors.Is(err, errNoRoom) {
50 t.Fatalf("a second read on a box with room for one: %v", err)
51 }
52 if err := a.grow("r1", int64(gib(8))); !errors.Is(err, errNoRoom) {
53 t.Fatalf("growing past the floor and 10%%: %v", err)
54 }
55 if err := a.grow("r1", int64(gib(6))); err != nil {
56 t.Fatalf("growing within room: %v", err)
57 }
58}
59
60// An orphan (a unit this agent is not running) blocks every admission until
61// it is gone, and no size is guessed for it (spec §5.7, codex 8).
62func TestAdmissionRefusesBesideAnOrphan(t *testing.T) {
63 t.Parallel()
64 u := &unitsSeam{n: 1}
65 a := newAdmission(u.count, func() (uint64, uint64, error) { return gib(100), gib(200), nil })
66 ctx := context.Background()
67 if err := a.admitRead(ctx, "r1"); !errors.Is(err, errOrphans) {
68 t.Fatalf("beside an orphan: %v", err)
69 }
70 if a.admitProbe(ctx) {
71 t.Fatal("a probe beside an orphan")
72 }
73 u.n = 0
74 if err := a.admitRead(ctx, "r1"); err != nil {
75 t.Fatalf("once the orphan is gone: %v", err)
76 }
77}
78
79// A finished read's reservation is held until no db-read unit is left, since
80// systemd removes the scratch only at unit stop.
81func TestAdmissionHoldsAReservationUntilItsUnitIsGone(t *testing.T) {
82 t.Parallel()
83 u := &unitsSeam{}
84 // 12 GiB free of 20: r1 grown to 6 leaves room for r2's floor only once
85 // r1's reservation is released.
86 a := newAdmission(u.count, func() (uint64, uint64, error) { return gib(12), gib(20), nil })
87 ctx := context.Background()
88 if err := a.admitRead(ctx, "r1"); err != nil {
89 t.Fatal(err)
90 }
91 a.opened()
92 if err := a.grow("r1", int64(gib(6))); err != nil {
93 t.Fatal(err)
94 }
95 u.n = 1
96 a.closed()
97 a.finish("r1")
98 // The unit is still deactivating: one unit, no connection. Refused.
99 if err := a.admitRead(ctx, "r2"); !errors.Is(err, errOrphans) {
100 t.Fatalf("while the unit deactivates: %v", err)
101 }
102 u.n = 0
103 if err := a.admitRead(ctx, "r2"); err != nil {
104 t.Fatalf("after the unit is gone the reservation is free: %v", err)
105 }
106}
107
108// Review Focus 5: a unit listing that fails admits nothing and skips the
109// probe rather than guessing.
110func TestAdmissionFailsClosedWhenUnitsCannotBeListed(t *testing.T) {
111 t.Parallel()
112 u := &unitsSeam{err: errors.New("systemctl: not found")}
113 a := newAdmission(u.count, func() (uint64, uint64, error) { return gib(100), gib(200), nil })
114 if err := a.admitRead(context.Background(), "r1"); err == nil {
115 t.Fatal("admitted with no unit listing")
116 }
117 if a.admitProbe(context.Background()) {
118 t.Fatal("probed with no unit listing")
119 }
120}
121
122// A read admitted and then lost in the election started no worker: its
123// floor goes at once, even while another read's unit is running.
124func TestAdmissionAbandonFreesAtOnce(t *testing.T) {
125 t.Parallel()
126 u := &unitsSeam{}
127 // 17 GiB of 20: room for two floors (17-10 = 7), not three.
128 a := newAdmission(u.count, func() (uint64, uint64, error) { return gib(17), gib(20), nil })
129 ctx := context.Background()
130 if err := a.admitRead(ctx, "running"); err != nil {
131 t.Fatal(err)
132 }
133 a.opened()
134 u.n = 1 // its worker unit is up, so finished reservations stay held
135 if err := a.admitRead(ctx, "lost"); err != nil {
136 t.Fatal(err)
137 }
138 a.abandon("lost") // it lost the election; no worker started
139 if err := a.admitRead(ctx, "next"); err != nil {
140 t.Fatalf("the lost read's floor is still held: %v", err)
141 }
142}
143
144// Plan review finding 10: a retried read keeps its first attempt's
145// reservation while that worker's unit is still stopping, and settle waits
146// for the unit. Mutation: retry leaves held[id] in place instead of moving
147// it to a retired key; the grow below then fits and this test is red.
148func TestAdmissionRetryHoldsTheOldReservation(t *testing.T) {
149 t.Parallel()
150 u := &unitsSeam{}
151 a := newAdmission(u.count, func() (uint64, uint64, error) { return gib(12), gib(20), nil })
152 ctx := context.Background()
153 if err := a.admitRead(ctx, "r1"); err != nil {
154 t.Fatal(err)
155 }
156 if err := a.grow("r1", int64(gib(6))); err != nil {
157 t.Fatal(err)
158 }
159 u.n = 1 // the first worker's connection is closed; its unit is stopping
160 a.retry("r1", 1)
161 if err := a.grow("r1", int64(gib(5))); !errors.Is(err, errNoRoom) {
162 t.Fatalf("the stopping worker's scratch was not held: %v", err)
163 }
164 short, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
165 defer cancel()
166 if err := a.settle(short, 10*time.Millisecond); !errors.Is(err, context.DeadlineExceeded) {
167 t.Fatalf("settle returned beside a live unit: %v", err)
168 }
169 u.n = 0
170 if err := a.settle(ctx, 10*time.Millisecond); err != nil {
171 t.Fatal(err)
172 }
173 if err := a.grow("r1", int64(gib(5))); err != nil {
174 t.Fatalf("after the unit stopped: %v", err)
175 }
176}
177
178func TestAdmissionFailsClosedWhenTheDiskCannotBeRead(t *testing.T) {
179 t.Parallel()
180 a := newAdmission((&unitsSeam{}).count, func() (uint64, uint64, error) { return 0, 0, errors.New("statfs") })
181 if err := a.admitRead(context.Background(), "r1"); !errors.Is(err, errNoRoom) {
182 t.Fatalf("admitted with no disk reading: %v", err)
183 }
184}
185