blob: 96e8dbe0abac18bfec268fd63c6e7856a04a34fc [file] [log] [blame]
Serge Bazanski68ca3702022-11-02 17:30:44 +01001package bmdb
2
3import (
4 "context"
5 "fmt"
6 "testing"
7 "time"
8
Serge Bazanskic59c5152023-05-11 16:15:30 +02009 "github.com/google/uuid"
10
Serge Bazanski68ca3702022-11-02 17:30:44 +010011 "source.monogon.dev/cloud/bmaas/bmdb/model"
12)
13
14// TestAgentStart exercises GetMachinesForAgentStart.
15func TestAgentStart(t *testing.T) {
16 b := dut()
17 conn, err := b.Open(true)
18 if err != nil {
19 t.Fatalf("Open failed: %v", err)
20 }
21
22 ctx, ctxC := context.WithCancel(context.Background())
23 defer ctxC()
24
25 session, err := conn.StartSession(ctx)
26 if err != nil {
27 t.Fatalf("Starting session failed: %v", err)
28 }
29
30 // Create a test machine.
31 var machine model.Machine
32 err = session.Transact(ctx, func(q *model.Queries) error {
33 machine, err = q.NewMachine(ctx)
34 return err
35 })
36 if err != nil {
37 t.Fatalf("Creating machine failed: %v", err)
38 }
39
40 // It should be, by default, not a candidate for agent start as it's not yet
41 // provided by any provider.
Serge Bazanskic59c5152023-05-11 16:15:30 +020042 expectCandidates := func(want int) {
43 t.Helper()
Serge Bazanski68ca3702022-11-02 17:30:44 +010044 if err := session.Transact(ctx, func(q *model.Queries) error {
45 candidates, err := q.GetMachinesForAgentStart(ctx, 1)
46 if err != nil {
47 t.Fatalf("Could not retrieve machines for agent start: %v", err)
48 }
Serge Bazanskic59c5152023-05-11 16:15:30 +020049 if got := len(candidates); want != got {
Serge Bazanski68ca3702022-11-02 17:30:44 +010050 t.Fatalf("Wanted %d machines for agent start, got %+v", want, candidates)
51 }
52 return nil
53 }); err != nil {
54 t.Fatal(err)
55 }
56 }
Serge Bazanski68ca3702022-11-02 17:30:44 +010057
58 // Provide machine, and check it is now a candidate.
59 if err := session.Transact(ctx, func(q *model.Queries) error {
60 return q.MachineAddProvided(ctx, model.MachineAddProvidedParams{
61 MachineID: machine.MachineID,
62 Provider: model.ProviderEquinix,
63 ProviderID: "123",
64 })
65 }); err != nil {
66 t.Fatalf("could not add provided tag to machine: %v", err)
67 }
Serge Bazanskic59c5152023-05-11 16:15:30 +020068 expectCandidates(1)
Serge Bazanski68ca3702022-11-02 17:30:44 +010069
70 // Add a start tag. Machine shouldn't be a candidate anymore.
71 if err := session.Transact(ctx, func(q *model.Queries) error {
72 return q.MachineSetAgentStarted(ctx, model.MachineSetAgentStartedParams{
73 MachineID: machine.MachineID,
74 AgentStartedAt: time.Now(),
75 AgentPublicKey: []byte("fakefakefakefake"),
76 })
77 }); err != nil {
78 t.Fatalf("could not add provided tag to machine: %v", err)
79 }
Serge Bazanskic59c5152023-05-11 16:15:30 +020080 expectCandidates(0)
81
82 // Add a new machine which has an unfulfilled installation request. It should be
83 // a candidate.
84 if err = session.Transact(ctx, func(q *model.Queries) error {
85 machine, err = q.NewMachine(ctx)
86 if err != nil {
87 return err
88 }
89 if err := q.MachineAddProvided(ctx, model.MachineAddProvidedParams{
90 MachineID: machine.MachineID,
91 Provider: model.ProviderEquinix,
92 ProviderID: "234",
93 }); err != nil {
94 return err
95 }
96 if err := q.MachineSetOSInstallationRequest(ctx, model.MachineSetOSInstallationRequestParams{
97 MachineID: machine.MachineID,
98 Generation: 10,
99 }); err != nil {
100 return err
101 }
102 return nil
103 }); err != nil {
104 t.Fatalf("could not add new machine with installation request: %v", err)
105 }
106 expectCandidates(1)
107
108 // Fulfill installation request on machine with an older generation. it should
109 // remain a candidate.
110 if err = session.Transact(ctx, func(q *model.Queries) error {
111 if err := q.MachineSetOSInstallationReport(ctx, model.MachineSetOSInstallationReportParams{
Tim Windelschmidt53087302023-06-27 16:36:31 +0200112 MachineID: machine.MachineID,
113 Generation: 9,
114 OsInstallationResult: model.MachineOsInstallationResultSuccess,
Serge Bazanskic59c5152023-05-11 16:15:30 +0200115 }); err != nil {
116 return err
117 }
118 return nil
119 }); err != nil {
120 t.Fatalf("could not fulfill installation request with older generation: %v", err)
121 }
122 expectCandidates(1)
123
124 // Fulfill installation request with correct generation. The machine should not
125 // be a candidate anymore.
126 if err = session.Transact(ctx, func(q *model.Queries) error {
127 if err := q.MachineSetOSInstallationReport(ctx, model.MachineSetOSInstallationReportParams{
Tim Windelschmidt53087302023-06-27 16:36:31 +0200128 MachineID: machine.MachineID,
129 Generation: 10,
130 OsInstallationResult: model.MachineOsInstallationResultSuccess,
Serge Bazanskic59c5152023-05-11 16:15:30 +0200131 }); err != nil {
132 return err
133 }
134 return nil
135 }); err != nil {
136 t.Fatalf("could not fulfill installation request with current generation: %v", err)
137 }
138 expectCandidates(0)
Serge Bazanski68ca3702022-11-02 17:30:44 +0100139}
140
141// TestAgentRecovery exercises GetMachinesForAgentRecovery though a few
142// different scenarios in which a test machine is present with different tags
143// set.
144func TestAgentRecovery(t *testing.T) {
145 b := dut()
146 conn, err := b.Open(true)
147 if err != nil {
148 t.Fatalf("Open failed: %v", err)
149 }
150
151 ctx, ctxC := context.WithCancel(context.Background())
152 defer ctxC()
153
154 session, err := conn.StartSession(ctx)
155 if err != nil {
156 t.Fatalf("Starting session failed: %v", err)
157 }
158
159 for i, scenario := range []struct {
160 // Whether recovery is expected to run.
161 wantRun bool
162 // started will add a AgentStarted tag for a given time, if set.
163 started time.Time
164 // heartbeat will add a AgentHeartbeat tag for a given time, if set.
165 heartbeat time.Time
Serge Bazanskic59c5152023-05-11 16:15:30 +0200166 // requestGeneration will populate a OSInstallationRequest if not zero.
167 requestGeneration int64
168 // requestGeneration will populate a OSInstallationResponse if not zero.
169 reportGeneration int64
Serge Bazanski68ca3702022-11-02 17:30:44 +0100170 }{
171 // No start, no heartbeat -> no recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200172 {false, time.Time{}, time.Time{}, 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100173 // Started recently, no heartbeat -> no recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200174 {false, time.Now(), time.Time{}, 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100175 // Started a while ago, heartbeat active -> no recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200176 {false, time.Now().Add(-40 * time.Minute), time.Now(), 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100177
178 // Started a while ago, no heartbeat -> recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200179 {true, time.Now().Add(-40 * time.Minute), time.Time{}, 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100180 // Started a while ago, no recent heartbeat -> recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200181 {true, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 0, 0},
182
183 // Installation request without report -> recovery expected.
184 {true, time.Now().Add(-40 * time.Minute), time.Time{}, 10, 0},
185 {true, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 10, 0},
186 // Installation request mismatching report -> recovery expected.
187 {true, time.Now().Add(-40 * time.Minute), time.Time{}, 10, 9},
188 {true, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 10, 9},
189 // Installation request matching report -> no recovery expected.
190 {false, time.Now().Add(-40 * time.Minute), time.Time{}, 10, 10},
191 {false, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 10, 10},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100192 } {
Serge Bazanskic59c5152023-05-11 16:15:30 +0200193 var machineID uuid.UUID
Serge Bazanski68ca3702022-11-02 17:30:44 +0100194 if err := session.Transact(ctx, func(q *model.Queries) error {
195 machine, err := q.NewMachine(ctx)
196 if err != nil {
197 return fmt.Errorf("NewMachine: %w", err)
198 }
Serge Bazanskic59c5152023-05-11 16:15:30 +0200199 machineID = machine.MachineID
Serge Bazanski68ca3702022-11-02 17:30:44 +0100200 if err := q.MachineAddProvided(ctx, model.MachineAddProvidedParams{
201 MachineID: machine.MachineID,
202 Provider: model.ProviderEquinix,
203 ProviderID: fmt.Sprintf("test-%d", i),
204 }); err != nil {
205 return fmt.Errorf("MachineAddProvided: %w", err)
206 }
207 if !scenario.started.IsZero() {
208 if err := q.MachineSetAgentStarted(ctx, model.MachineSetAgentStartedParams{
209 MachineID: machine.MachineID,
210 AgentStartedAt: scenario.started,
211 AgentPublicKey: []byte("fake"),
212 }); err != nil {
213 return fmt.Errorf("MachineSetAgentStarted: %w", err)
214 }
215 }
216 if !scenario.heartbeat.IsZero() {
217 if err := q.MachineSetAgentHeartbeat(ctx, model.MachineSetAgentHeartbeatParams{
218 MachineID: machine.MachineID,
219 AgentHeartbeatAt: scenario.heartbeat,
220 }); err != nil {
221 return fmt.Errorf("MachineSetAgentHeartbeat: %w", err)
222 }
223 }
Serge Bazanskic59c5152023-05-11 16:15:30 +0200224 if scenario.requestGeneration != 0 {
225 if err := q.MachineSetOSInstallationRequest(ctx, model.MachineSetOSInstallationRequestParams{
226 MachineID: machine.MachineID,
227 Generation: scenario.requestGeneration,
228 }); err != nil {
229 return fmt.Errorf("MachineSetOSInstallationRequest: %w", err)
230 }
231 }
232 if scenario.reportGeneration != 0 {
233 if err := q.MachineSetOSInstallationReport(ctx, model.MachineSetOSInstallationReportParams{
Tim Windelschmidt53087302023-06-27 16:36:31 +0200234 MachineID: machine.MachineID,
235 Generation: scenario.reportGeneration,
236 OsInstallationResult: model.MachineOsInstallationResultSuccess,
Serge Bazanskic59c5152023-05-11 16:15:30 +0200237 }); err != nil {
238 return fmt.Errorf("MachineSetOSInstallationReport: %w", err)
239 }
240 }
Serge Bazanski68ca3702022-11-02 17:30:44 +0100241 return nil
242 }); err != nil {
243 t.Errorf("%d: setup failed: %v", i, err)
244 continue
245 }
246
Serge Bazanskic59c5152023-05-11 16:15:30 +0200247 found := false
Serge Bazanski68ca3702022-11-02 17:30:44 +0100248 if err := session.Transact(ctx, func(q *model.Queries) error {
Serge Bazanskic59c5152023-05-11 16:15:30 +0200249 candidates, err := q.GetMachineForAgentRecovery(ctx, 100)
Serge Bazanski68ca3702022-11-02 17:30:44 +0100250 if err != nil {
251 return fmt.Errorf("GetMachinesForAgentRecovery: %w", err)
252 }
Serge Bazanskic59c5152023-05-11 16:15:30 +0200253 for _, c := range candidates {
254 if c.MachineID == machineID {
255 found = true
256 break
257 }
Serge Bazanski68ca3702022-11-02 17:30:44 +0100258 }
259 return nil
260 }); err != nil {
Serge Bazanskic59c5152023-05-11 16:15:30 +0200261 t.Errorf("%d: failed to retrieve candidates: %v", i, err)
262 }
263 if scenario.wantRun && !found {
264 t.Errorf("%d: expected recovery but not scheduled", i)
265 }
266 if !scenario.wantRun && found {
267 t.Errorf("%d: expected no recovery but is scheduled", i)
Serge Bazanski68ca3702022-11-02 17:30:44 +0100268 }
269 }
270}