blob: 7ceae89f410d8054fca9809015ded7d0c8a2517c [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 {
Tim Windelschmidt0e749612023-08-07 17:42:59 +000045 candidates, err := q.GetMachinesForAgentStart(ctx, model.GetMachinesForAgentStartParams{
46 Limit: 1,
47 Provider: model.ProviderEquinix,
48 })
Serge Bazanski68ca3702022-11-02 17:30:44 +010049 if err != nil {
50 t.Fatalf("Could not retrieve machines for agent start: %v", err)
51 }
Serge Bazanskic59c5152023-05-11 16:15:30 +020052 if got := len(candidates); want != got {
Serge Bazanski68ca3702022-11-02 17:30:44 +010053 t.Fatalf("Wanted %d machines for agent start, got %+v", want, candidates)
54 }
55 return nil
56 }); err != nil {
57 t.Fatal(err)
58 }
59 }
Serge Bazanski68ca3702022-11-02 17:30:44 +010060
61 // Provide machine, and check it is now a candidate.
62 if err := session.Transact(ctx, func(q *model.Queries) error {
63 return q.MachineAddProvided(ctx, model.MachineAddProvidedParams{
64 MachineID: machine.MachineID,
65 Provider: model.ProviderEquinix,
66 ProviderID: "123",
67 })
68 }); err != nil {
69 t.Fatalf("could not add provided tag to machine: %v", err)
70 }
Serge Bazanskic59c5152023-05-11 16:15:30 +020071 expectCandidates(1)
Serge Bazanski68ca3702022-11-02 17:30:44 +010072
73 // Add a start tag. Machine shouldn't be a candidate anymore.
74 if err := session.Transact(ctx, func(q *model.Queries) error {
75 return q.MachineSetAgentStarted(ctx, model.MachineSetAgentStartedParams{
76 MachineID: machine.MachineID,
77 AgentStartedAt: time.Now(),
78 AgentPublicKey: []byte("fakefakefakefake"),
79 })
80 }); err != nil {
81 t.Fatalf("could not add provided tag to machine: %v", err)
82 }
Serge Bazanskic59c5152023-05-11 16:15:30 +020083 expectCandidates(0)
84
85 // Add a new machine which has an unfulfilled installation request. It should be
86 // a candidate.
87 if err = session.Transact(ctx, func(q *model.Queries) error {
88 machine, err = q.NewMachine(ctx)
89 if err != nil {
90 return err
91 }
92 if err := q.MachineAddProvided(ctx, model.MachineAddProvidedParams{
93 MachineID: machine.MachineID,
94 Provider: model.ProviderEquinix,
95 ProviderID: "234",
96 }); err != nil {
97 return err
98 }
99 if err := q.MachineSetOSInstallationRequest(ctx, model.MachineSetOSInstallationRequestParams{
100 MachineID: machine.MachineID,
101 Generation: 10,
102 }); err != nil {
103 return err
104 }
105 return nil
106 }); err != nil {
107 t.Fatalf("could not add new machine with installation request: %v", err)
108 }
109 expectCandidates(1)
110
111 // Fulfill installation request on machine with an older generation. it should
112 // remain a candidate.
113 if err = session.Transact(ctx, func(q *model.Queries) error {
114 if err := q.MachineSetOSInstallationReport(ctx, model.MachineSetOSInstallationReportParams{
Tim Windelschmidt53087302023-06-27 16:36:31 +0200115 MachineID: machine.MachineID,
116 Generation: 9,
117 OsInstallationResult: model.MachineOsInstallationResultSuccess,
Serge Bazanskic59c5152023-05-11 16:15:30 +0200118 }); err != nil {
119 return err
120 }
121 return nil
122 }); err != nil {
123 t.Fatalf("could not fulfill installation request with older generation: %v", err)
124 }
125 expectCandidates(1)
126
127 // Fulfill installation request with correct generation. The machine should not
128 // be a candidate anymore.
129 if err = session.Transact(ctx, func(q *model.Queries) error {
130 if err := q.MachineSetOSInstallationReport(ctx, model.MachineSetOSInstallationReportParams{
Tim Windelschmidt53087302023-06-27 16:36:31 +0200131 MachineID: machine.MachineID,
132 Generation: 10,
133 OsInstallationResult: model.MachineOsInstallationResultSuccess,
Serge Bazanskic59c5152023-05-11 16:15:30 +0200134 }); err != nil {
135 return err
136 }
137 return nil
138 }); err != nil {
139 t.Fatalf("could not fulfill installation request with current generation: %v", err)
140 }
141 expectCandidates(0)
Serge Bazanski68ca3702022-11-02 17:30:44 +0100142}
143
144// TestAgentRecovery exercises GetMachinesForAgentRecovery though a few
145// different scenarios in which a test machine is present with different tags
146// set.
147func TestAgentRecovery(t *testing.T) {
148 b := dut()
149 conn, err := b.Open(true)
150 if err != nil {
151 t.Fatalf("Open failed: %v", err)
152 }
153
154 ctx, ctxC := context.WithCancel(context.Background())
155 defer ctxC()
156
157 session, err := conn.StartSession(ctx)
158 if err != nil {
159 t.Fatalf("Starting session failed: %v", err)
160 }
161
162 for i, scenario := range []struct {
163 // Whether recovery is expected to run.
164 wantRun bool
165 // started will add a AgentStarted tag for a given time, if set.
166 started time.Time
167 // heartbeat will add a AgentHeartbeat tag for a given time, if set.
168 heartbeat time.Time
Serge Bazanskic59c5152023-05-11 16:15:30 +0200169 // requestGeneration will populate a OSInstallationRequest if not zero.
170 requestGeneration int64
171 // requestGeneration will populate a OSInstallationResponse if not zero.
172 reportGeneration int64
Serge Bazanski68ca3702022-11-02 17:30:44 +0100173 }{
174 // No start, no heartbeat -> no recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200175 {false, time.Time{}, time.Time{}, 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100176 // Started recently, no heartbeat -> no recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200177 {false, time.Now(), time.Time{}, 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100178 // Started a while ago, heartbeat active -> no recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200179 {false, time.Now().Add(-40 * time.Minute), time.Now(), 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100180
181 // Started a while ago, no heartbeat -> recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200182 {true, time.Now().Add(-40 * time.Minute), time.Time{}, 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100183 // Started a while ago, no recent heartbeat -> recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200184 {true, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 0, 0},
185
186 // Installation request without report -> recovery expected.
187 {true, time.Now().Add(-40 * time.Minute), time.Time{}, 10, 0},
188 {true, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 10, 0},
189 // Installation request mismatching report -> recovery expected.
190 {true, time.Now().Add(-40 * time.Minute), time.Time{}, 10, 9},
191 {true, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 10, 9},
192 // Installation request matching report -> no recovery expected.
193 {false, time.Now().Add(-40 * time.Minute), time.Time{}, 10, 10},
194 {false, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 10, 10},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100195 } {
Serge Bazanskic59c5152023-05-11 16:15:30 +0200196 var machineID uuid.UUID
Serge Bazanski68ca3702022-11-02 17:30:44 +0100197 if err := session.Transact(ctx, func(q *model.Queries) error {
198 machine, err := q.NewMachine(ctx)
199 if err != nil {
200 return fmt.Errorf("NewMachine: %w", err)
201 }
Serge Bazanskic59c5152023-05-11 16:15:30 +0200202 machineID = machine.MachineID
Serge Bazanski68ca3702022-11-02 17:30:44 +0100203 if err := q.MachineAddProvided(ctx, model.MachineAddProvidedParams{
204 MachineID: machine.MachineID,
205 Provider: model.ProviderEquinix,
206 ProviderID: fmt.Sprintf("test-%d", i),
207 }); err != nil {
208 return fmt.Errorf("MachineAddProvided: %w", err)
209 }
210 if !scenario.started.IsZero() {
211 if err := q.MachineSetAgentStarted(ctx, model.MachineSetAgentStartedParams{
212 MachineID: machine.MachineID,
213 AgentStartedAt: scenario.started,
214 AgentPublicKey: []byte("fake"),
215 }); err != nil {
216 return fmt.Errorf("MachineSetAgentStarted: %w", err)
217 }
218 }
219 if !scenario.heartbeat.IsZero() {
220 if err := q.MachineSetAgentHeartbeat(ctx, model.MachineSetAgentHeartbeatParams{
221 MachineID: machine.MachineID,
222 AgentHeartbeatAt: scenario.heartbeat,
223 }); err != nil {
224 return fmt.Errorf("MachineSetAgentHeartbeat: %w", err)
225 }
226 }
Serge Bazanskic59c5152023-05-11 16:15:30 +0200227 if scenario.requestGeneration != 0 {
228 if err := q.MachineSetOSInstallationRequest(ctx, model.MachineSetOSInstallationRequestParams{
229 MachineID: machine.MachineID,
230 Generation: scenario.requestGeneration,
231 }); err != nil {
232 return fmt.Errorf("MachineSetOSInstallationRequest: %w", err)
233 }
234 }
235 if scenario.reportGeneration != 0 {
236 if err := q.MachineSetOSInstallationReport(ctx, model.MachineSetOSInstallationReportParams{
Tim Windelschmidt53087302023-06-27 16:36:31 +0200237 MachineID: machine.MachineID,
238 Generation: scenario.reportGeneration,
239 OsInstallationResult: model.MachineOsInstallationResultSuccess,
Serge Bazanskic59c5152023-05-11 16:15:30 +0200240 }); err != nil {
241 return fmt.Errorf("MachineSetOSInstallationReport: %w", err)
242 }
243 }
Serge Bazanski68ca3702022-11-02 17:30:44 +0100244 return nil
245 }); err != nil {
246 t.Errorf("%d: setup failed: %v", i, err)
247 continue
248 }
249
Serge Bazanskic59c5152023-05-11 16:15:30 +0200250 found := false
Serge Bazanski68ca3702022-11-02 17:30:44 +0100251 if err := session.Transact(ctx, func(q *model.Queries) error {
Tim Windelschmidt0e749612023-08-07 17:42:59 +0000252 candidates, err := q.GetMachineForAgentRecovery(ctx, model.GetMachineForAgentRecoveryParams{
253 Limit: 100,
254 Provider: model.ProviderEquinix,
255 })
Serge Bazanski68ca3702022-11-02 17:30:44 +0100256 if err != nil {
257 return fmt.Errorf("GetMachinesForAgentRecovery: %w", err)
258 }
Serge Bazanskic59c5152023-05-11 16:15:30 +0200259 for _, c := range candidates {
260 if c.MachineID == machineID {
261 found = true
262 break
263 }
Serge Bazanski68ca3702022-11-02 17:30:44 +0100264 }
265 return nil
266 }); err != nil {
Serge Bazanskic59c5152023-05-11 16:15:30 +0200267 t.Errorf("%d: failed to retrieve candidates: %v", i, err)
268 }
269 if scenario.wantRun && !found {
270 t.Errorf("%d: expected recovery but not scheduled", i)
271 }
272 if !scenario.wantRun && found {
273 t.Errorf("%d: expected no recovery but is scheduled", i)
Serge Bazanski68ca3702022-11-02 17:30:44 +0100274 }
275 }
276}