blob: 917033f6ad12e296573ea94ac30245c21693160a [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Serge Bazanski68ca3702022-11-02 17:30:44 +01004package bmdb
5
6import (
7 "context"
8 "fmt"
9 "testing"
10 "time"
11
Serge Bazanskic59c5152023-05-11 16:15:30 +020012 "github.com/google/uuid"
13
Serge Bazanski68ca3702022-11-02 17:30:44 +010014 "source.monogon.dev/cloud/bmaas/bmdb/model"
15)
16
17// TestAgentStart exercises GetMachinesForAgentStart.
18func TestAgentStart(t *testing.T) {
19 b := dut()
20 conn, err := b.Open(true)
21 if err != nil {
22 t.Fatalf("Open failed: %v", err)
23 }
24
25 ctx, ctxC := context.WithCancel(context.Background())
26 defer ctxC()
27
28 session, err := conn.StartSession(ctx)
29 if err != nil {
30 t.Fatalf("Starting session failed: %v", err)
31 }
32
33 // Create a test machine.
34 var machine model.Machine
35 err = session.Transact(ctx, func(q *model.Queries) error {
36 machine, err = q.NewMachine(ctx)
37 return err
38 })
39 if err != nil {
40 t.Fatalf("Creating machine failed: %v", err)
41 }
42
43 // It should be, by default, not a candidate for agent start as it's not yet
44 // provided by any provider.
Serge Bazanskic59c5152023-05-11 16:15:30 +020045 expectCandidates := func(want int) {
46 t.Helper()
Serge Bazanski68ca3702022-11-02 17:30:44 +010047 if err := session.Transact(ctx, func(q *model.Queries) error {
Tim Windelschmidt0e749612023-08-07 17:42:59 +000048 candidates, err := q.GetMachinesForAgentStart(ctx, model.GetMachinesForAgentStartParams{
49 Limit: 1,
50 Provider: model.ProviderEquinix,
51 })
Serge Bazanski68ca3702022-11-02 17:30:44 +010052 if err != nil {
53 t.Fatalf("Could not retrieve machines for agent start: %v", err)
54 }
Serge Bazanskic59c5152023-05-11 16:15:30 +020055 if got := len(candidates); want != got {
Serge Bazanski68ca3702022-11-02 17:30:44 +010056 t.Fatalf("Wanted %d machines for agent start, got %+v", want, candidates)
57 }
58 return nil
59 }); err != nil {
60 t.Fatal(err)
61 }
62 }
Serge Bazanski68ca3702022-11-02 17:30:44 +010063
64 // Provide machine, and check it is now a candidate.
65 if err := session.Transact(ctx, func(q *model.Queries) error {
66 return q.MachineAddProvided(ctx, model.MachineAddProvidedParams{
67 MachineID: machine.MachineID,
68 Provider: model.ProviderEquinix,
69 ProviderID: "123",
70 })
71 }); err != nil {
72 t.Fatalf("could not add provided tag to machine: %v", err)
73 }
Serge Bazanskic59c5152023-05-11 16:15:30 +020074 expectCandidates(1)
Serge Bazanski68ca3702022-11-02 17:30:44 +010075
76 // Add a start tag. Machine shouldn't be a candidate anymore.
77 if err := session.Transact(ctx, func(q *model.Queries) error {
78 return q.MachineSetAgentStarted(ctx, model.MachineSetAgentStartedParams{
79 MachineID: machine.MachineID,
80 AgentStartedAt: time.Now(),
81 AgentPublicKey: []byte("fakefakefakefake"),
82 })
83 }); err != nil {
84 t.Fatalf("could not add provided tag to machine: %v", err)
85 }
Serge Bazanskic59c5152023-05-11 16:15:30 +020086 expectCandidates(0)
87
88 // Add a new machine which has an unfulfilled installation request. It should be
89 // a candidate.
90 if err = session.Transact(ctx, func(q *model.Queries) error {
91 machine, err = q.NewMachine(ctx)
92 if err != nil {
93 return err
94 }
95 if err := q.MachineAddProvided(ctx, model.MachineAddProvidedParams{
96 MachineID: machine.MachineID,
97 Provider: model.ProviderEquinix,
98 ProviderID: "234",
99 }); err != nil {
100 return err
101 }
102 if err := q.MachineSetOSInstallationRequest(ctx, model.MachineSetOSInstallationRequestParams{
103 MachineID: machine.MachineID,
104 Generation: 10,
105 }); err != nil {
106 return err
107 }
108 return nil
109 }); err != nil {
110 t.Fatalf("could not add new machine with installation request: %v", err)
111 }
112 expectCandidates(1)
113
114 // Fulfill installation request on machine with an older generation. it should
115 // remain a candidate.
116 if err = session.Transact(ctx, func(q *model.Queries) error {
117 if err := q.MachineSetOSInstallationReport(ctx, model.MachineSetOSInstallationReportParams{
Tim Windelschmidt53087302023-06-27 16:36:31 +0200118 MachineID: machine.MachineID,
119 Generation: 9,
120 OsInstallationResult: model.MachineOsInstallationResultSuccess,
Serge Bazanskic59c5152023-05-11 16:15:30 +0200121 }); err != nil {
122 return err
123 }
124 return nil
125 }); err != nil {
126 t.Fatalf("could not fulfill installation request with older generation: %v", err)
127 }
128 expectCandidates(1)
129
130 // Fulfill installation request with correct generation. The machine should not
131 // be a candidate anymore.
132 if err = session.Transact(ctx, func(q *model.Queries) error {
133 if err := q.MachineSetOSInstallationReport(ctx, model.MachineSetOSInstallationReportParams{
Tim Windelschmidt53087302023-06-27 16:36:31 +0200134 MachineID: machine.MachineID,
135 Generation: 10,
136 OsInstallationResult: model.MachineOsInstallationResultSuccess,
Serge Bazanskic59c5152023-05-11 16:15:30 +0200137 }); err != nil {
138 return err
139 }
140 return nil
141 }); err != nil {
142 t.Fatalf("could not fulfill installation request with current generation: %v", err)
143 }
144 expectCandidates(0)
Serge Bazanski68ca3702022-11-02 17:30:44 +0100145}
146
147// TestAgentRecovery exercises GetMachinesForAgentRecovery though a few
148// different scenarios in which a test machine is present with different tags
149// set.
150func TestAgentRecovery(t *testing.T) {
151 b := dut()
152 conn, err := b.Open(true)
153 if err != nil {
154 t.Fatalf("Open failed: %v", err)
155 }
156
157 ctx, ctxC := context.WithCancel(context.Background())
158 defer ctxC()
159
160 session, err := conn.StartSession(ctx)
161 if err != nil {
162 t.Fatalf("Starting session failed: %v", err)
163 }
164
165 for i, scenario := range []struct {
166 // Whether recovery is expected to run.
167 wantRun bool
168 // started will add a AgentStarted tag for a given time, if set.
169 started time.Time
170 // heartbeat will add a AgentHeartbeat tag for a given time, if set.
171 heartbeat time.Time
Serge Bazanskic59c5152023-05-11 16:15:30 +0200172 // requestGeneration will populate a OSInstallationRequest if not zero.
173 requestGeneration int64
174 // requestGeneration will populate a OSInstallationResponse if not zero.
175 reportGeneration int64
Serge Bazanski68ca3702022-11-02 17:30:44 +0100176 }{
177 // No start, no heartbeat -> no recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200178 {false, time.Time{}, time.Time{}, 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100179 // Started recently, no heartbeat -> no recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200180 {false, time.Now(), time.Time{}, 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100181 // Started a while ago, heartbeat active -> no recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200182 {false, time.Now().Add(-40 * time.Minute), time.Now(), 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100183
184 // Started a while ago, no heartbeat -> recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200185 {true, time.Now().Add(-40 * time.Minute), time.Time{}, 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100186 // Started a while ago, no recent heartbeat -> recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200187 {true, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 0, 0},
188
189 // Installation request without report -> recovery expected.
190 {true, time.Now().Add(-40 * time.Minute), time.Time{}, 10, 0},
191 {true, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 10, 0},
192 // Installation request mismatching report -> recovery expected.
193 {true, time.Now().Add(-40 * time.Minute), time.Time{}, 10, 9},
194 {true, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 10, 9},
195 // Installation request matching report -> no recovery expected.
196 {false, time.Now().Add(-40 * time.Minute), time.Time{}, 10, 10},
197 {false, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 10, 10},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100198 } {
Serge Bazanskic59c5152023-05-11 16:15:30 +0200199 var machineID uuid.UUID
Serge Bazanski68ca3702022-11-02 17:30:44 +0100200 if err := session.Transact(ctx, func(q *model.Queries) error {
201 machine, err := q.NewMachine(ctx)
202 if err != nil {
203 return fmt.Errorf("NewMachine: %w", err)
204 }
Serge Bazanskic59c5152023-05-11 16:15:30 +0200205 machineID = machine.MachineID
Serge Bazanski68ca3702022-11-02 17:30:44 +0100206 if err := q.MachineAddProvided(ctx, model.MachineAddProvidedParams{
207 MachineID: machine.MachineID,
208 Provider: model.ProviderEquinix,
209 ProviderID: fmt.Sprintf("test-%d", i),
210 }); err != nil {
211 return fmt.Errorf("MachineAddProvided: %w", err)
212 }
213 if !scenario.started.IsZero() {
214 if err := q.MachineSetAgentStarted(ctx, model.MachineSetAgentStartedParams{
215 MachineID: machine.MachineID,
216 AgentStartedAt: scenario.started,
217 AgentPublicKey: []byte("fake"),
218 }); err != nil {
219 return fmt.Errorf("MachineSetAgentStarted: %w", err)
220 }
221 }
222 if !scenario.heartbeat.IsZero() {
223 if err := q.MachineSetAgentHeartbeat(ctx, model.MachineSetAgentHeartbeatParams{
224 MachineID: machine.MachineID,
225 AgentHeartbeatAt: scenario.heartbeat,
226 }); err != nil {
227 return fmt.Errorf("MachineSetAgentHeartbeat: %w", err)
228 }
229 }
Serge Bazanskic59c5152023-05-11 16:15:30 +0200230 if scenario.requestGeneration != 0 {
231 if err := q.MachineSetOSInstallationRequest(ctx, model.MachineSetOSInstallationRequestParams{
232 MachineID: machine.MachineID,
233 Generation: scenario.requestGeneration,
234 }); err != nil {
235 return fmt.Errorf("MachineSetOSInstallationRequest: %w", err)
236 }
237 }
238 if scenario.reportGeneration != 0 {
239 if err := q.MachineSetOSInstallationReport(ctx, model.MachineSetOSInstallationReportParams{
Tim Windelschmidt53087302023-06-27 16:36:31 +0200240 MachineID: machine.MachineID,
241 Generation: scenario.reportGeneration,
242 OsInstallationResult: model.MachineOsInstallationResultSuccess,
Serge Bazanskic59c5152023-05-11 16:15:30 +0200243 }); err != nil {
244 return fmt.Errorf("MachineSetOSInstallationReport: %w", err)
245 }
246 }
Serge Bazanski68ca3702022-11-02 17:30:44 +0100247 return nil
248 }); err != nil {
249 t.Errorf("%d: setup failed: %v", i, err)
250 continue
251 }
252
Serge Bazanskic59c5152023-05-11 16:15:30 +0200253 found := false
Serge Bazanski68ca3702022-11-02 17:30:44 +0100254 if err := session.Transact(ctx, func(q *model.Queries) error {
Tim Windelschmidt0e749612023-08-07 17:42:59 +0000255 candidates, err := q.GetMachineForAgentRecovery(ctx, model.GetMachineForAgentRecoveryParams{
256 Limit: 100,
257 Provider: model.ProviderEquinix,
258 })
Serge Bazanski68ca3702022-11-02 17:30:44 +0100259 if err != nil {
260 return fmt.Errorf("GetMachinesForAgentRecovery: %w", err)
261 }
Serge Bazanskic59c5152023-05-11 16:15:30 +0200262 for _, c := range candidates {
263 if c.MachineID == machineID {
264 found = true
265 break
266 }
Serge Bazanski68ca3702022-11-02 17:30:44 +0100267 }
268 return nil
269 }); err != nil {
Serge Bazanskic59c5152023-05-11 16:15:30 +0200270 t.Errorf("%d: failed to retrieve candidates: %v", i, err)
271 }
272 if scenario.wantRun && !found {
273 t.Errorf("%d: expected recovery but not scheduled", i)
274 }
275 if !scenario.wantRun && found {
276 t.Errorf("%d: expected no recovery but is scheduled", i)
Serge Bazanski68ca3702022-11-02 17:30:44 +0100277 }
278 }
279}