blob: facc54f43cbf18048769c92a12cae8fa130d8b1b [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{
112 MachineID: machine.MachineID,
113 Generation: 9,
114 }); err != nil {
115 return err
116 }
117 return nil
118 }); err != nil {
119 t.Fatalf("could not fulfill installation request with older generation: %v", err)
120 }
121 expectCandidates(1)
122
123 // Fulfill installation request with correct generation. The machine should not
124 // be a candidate anymore.
125 if err = session.Transact(ctx, func(q *model.Queries) error {
126 if err := q.MachineSetOSInstallationReport(ctx, model.MachineSetOSInstallationReportParams{
127 MachineID: machine.MachineID,
128 Generation: 10,
129 }); err != nil {
130 return err
131 }
132 return nil
133 }); err != nil {
134 t.Fatalf("could not fulfill installation request with current generation: %v", err)
135 }
136 expectCandidates(0)
Serge Bazanski68ca3702022-11-02 17:30:44 +0100137}
138
139// TestAgentRecovery exercises GetMachinesForAgentRecovery though a few
140// different scenarios in which a test machine is present with different tags
141// set.
142func TestAgentRecovery(t *testing.T) {
143 b := dut()
144 conn, err := b.Open(true)
145 if err != nil {
146 t.Fatalf("Open failed: %v", err)
147 }
148
149 ctx, ctxC := context.WithCancel(context.Background())
150 defer ctxC()
151
152 session, err := conn.StartSession(ctx)
153 if err != nil {
154 t.Fatalf("Starting session failed: %v", err)
155 }
156
157 for i, scenario := range []struct {
158 // Whether recovery is expected to run.
159 wantRun bool
160 // started will add a AgentStarted tag for a given time, if set.
161 started time.Time
162 // heartbeat will add a AgentHeartbeat tag for a given time, if set.
163 heartbeat time.Time
Serge Bazanskic59c5152023-05-11 16:15:30 +0200164 // requestGeneration will populate a OSInstallationRequest if not zero.
165 requestGeneration int64
166 // requestGeneration will populate a OSInstallationResponse if not zero.
167 reportGeneration int64
Serge Bazanski68ca3702022-11-02 17:30:44 +0100168 }{
169 // No start, no heartbeat -> no recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200170 {false, time.Time{}, time.Time{}, 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100171 // Started recently, no heartbeat -> no recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200172 {false, time.Now(), time.Time{}, 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100173 // Started a while ago, heartbeat active -> no recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200174 {false, time.Now().Add(-40 * time.Minute), time.Now(), 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100175
176 // Started a while ago, no heartbeat -> recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200177 {true, time.Now().Add(-40 * time.Minute), time.Time{}, 0, 0},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100178 // Started a while ago, no recent heartbeat -> recovery expected.
Serge Bazanskic59c5152023-05-11 16:15:30 +0200179 {true, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 0, 0},
180
181 // Installation request without report -> recovery expected.
182 {true, time.Now().Add(-40 * time.Minute), time.Time{}, 10, 0},
183 {true, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 10, 0},
184 // Installation request mismatching report -> recovery expected.
185 {true, time.Now().Add(-40 * time.Minute), time.Time{}, 10, 9},
186 {true, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 10, 9},
187 // Installation request matching report -> no recovery expected.
188 {false, time.Now().Add(-40 * time.Minute), time.Time{}, 10, 10},
189 {false, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute), 10, 10},
Serge Bazanski68ca3702022-11-02 17:30:44 +0100190 } {
Serge Bazanskic59c5152023-05-11 16:15:30 +0200191 var machineID uuid.UUID
Serge Bazanski68ca3702022-11-02 17:30:44 +0100192 if err := session.Transact(ctx, func(q *model.Queries) error {
193 machine, err := q.NewMachine(ctx)
194 if err != nil {
195 return fmt.Errorf("NewMachine: %w", err)
196 }
Serge Bazanskic59c5152023-05-11 16:15:30 +0200197 machineID = machine.MachineID
Serge Bazanski68ca3702022-11-02 17:30:44 +0100198 if err := q.MachineAddProvided(ctx, model.MachineAddProvidedParams{
199 MachineID: machine.MachineID,
200 Provider: model.ProviderEquinix,
201 ProviderID: fmt.Sprintf("test-%d", i),
202 }); err != nil {
203 return fmt.Errorf("MachineAddProvided: %w", err)
204 }
205 if !scenario.started.IsZero() {
206 if err := q.MachineSetAgentStarted(ctx, model.MachineSetAgentStartedParams{
207 MachineID: machine.MachineID,
208 AgentStartedAt: scenario.started,
209 AgentPublicKey: []byte("fake"),
210 }); err != nil {
211 return fmt.Errorf("MachineSetAgentStarted: %w", err)
212 }
213 }
214 if !scenario.heartbeat.IsZero() {
215 if err := q.MachineSetAgentHeartbeat(ctx, model.MachineSetAgentHeartbeatParams{
216 MachineID: machine.MachineID,
217 AgentHeartbeatAt: scenario.heartbeat,
218 }); err != nil {
219 return fmt.Errorf("MachineSetAgentHeartbeat: %w", err)
220 }
221 }
Serge Bazanskic59c5152023-05-11 16:15:30 +0200222 if scenario.requestGeneration != 0 {
223 if err := q.MachineSetOSInstallationRequest(ctx, model.MachineSetOSInstallationRequestParams{
224 MachineID: machine.MachineID,
225 Generation: scenario.requestGeneration,
226 }); err != nil {
227 return fmt.Errorf("MachineSetOSInstallationRequest: %w", err)
228 }
229 }
230 if scenario.reportGeneration != 0 {
231 if err := q.MachineSetOSInstallationReport(ctx, model.MachineSetOSInstallationReportParams{
232 MachineID: machine.MachineID,
233 Generation: scenario.reportGeneration,
234 }); err != nil {
235 return fmt.Errorf("MachineSetOSInstallationReport: %w", err)
236 }
237 }
Serge Bazanski68ca3702022-11-02 17:30:44 +0100238 return nil
239 }); err != nil {
240 t.Errorf("%d: setup failed: %v", i, err)
241 continue
242 }
243
Serge Bazanskic59c5152023-05-11 16:15:30 +0200244 found := false
Serge Bazanski68ca3702022-11-02 17:30:44 +0100245 if err := session.Transact(ctx, func(q *model.Queries) error {
Serge Bazanskic59c5152023-05-11 16:15:30 +0200246 candidates, err := q.GetMachineForAgentRecovery(ctx, 100)
Serge Bazanski68ca3702022-11-02 17:30:44 +0100247 if err != nil {
248 return fmt.Errorf("GetMachinesForAgentRecovery: %w", err)
249 }
Serge Bazanskic59c5152023-05-11 16:15:30 +0200250 for _, c := range candidates {
251 if c.MachineID == machineID {
252 found = true
253 break
254 }
Serge Bazanski68ca3702022-11-02 17:30:44 +0100255 }
256 return nil
257 }); err != nil {
Serge Bazanskic59c5152023-05-11 16:15:30 +0200258 t.Errorf("%d: failed to retrieve candidates: %v", i, err)
259 }
260 if scenario.wantRun && !found {
261 t.Errorf("%d: expected recovery but not scheduled", i)
262 }
263 if !scenario.wantRun && found {
264 t.Errorf("%d: expected no recovery but is scheduled", i)
Serge Bazanski68ca3702022-11-02 17:30:44 +0100265 }
266 }
267}