blob: 849093004ef9a23790579e4d174579691ae25fdd [file] [log] [blame]
Serge Bazanski68ca3702022-11-02 17:30:44 +01001package bmdb
2
3import (
4 "context"
5 "fmt"
6 "testing"
7 "time"
8
9 "source.monogon.dev/cloud/bmaas/bmdb/model"
10)
11
12// TestAgentStart exercises GetMachinesForAgentStart.
13func TestAgentStart(t *testing.T) {
14 b := dut()
15 conn, err := b.Open(true)
16 if err != nil {
17 t.Fatalf("Open failed: %v", err)
18 }
19
20 ctx, ctxC := context.WithCancel(context.Background())
21 defer ctxC()
22
23 session, err := conn.StartSession(ctx)
24 if err != nil {
25 t.Fatalf("Starting session failed: %v", err)
26 }
27
28 // Create a test machine.
29 var machine model.Machine
30 err = session.Transact(ctx, func(q *model.Queries) error {
31 machine, err = q.NewMachine(ctx)
32 return err
33 })
34 if err != nil {
35 t.Fatalf("Creating machine failed: %v", err)
36 }
37
38 // It should be, by default, not a candidate for agent start as it's not yet
39 // provided by any provider.
40 expectNoCandidates := func() {
41 if err := session.Transact(ctx, func(q *model.Queries) error {
42 candidates, err := q.GetMachinesForAgentStart(ctx, 1)
43 if err != nil {
44 t.Fatalf("Could not retrieve machines for agent start: %v", err)
45 }
46 if want, got := 0, len(candidates); want != got {
47 t.Fatalf("Wanted %d machines for agent start, got %+v", want, candidates)
48 }
49 return nil
50 }); err != nil {
51 t.Fatal(err)
52 }
53 }
54 expectNoCandidates()
55
56 // Provide machine, and check it is now a candidate.
57 if err := session.Transact(ctx, func(q *model.Queries) error {
58 return q.MachineAddProvided(ctx, model.MachineAddProvidedParams{
59 MachineID: machine.MachineID,
60 Provider: model.ProviderEquinix,
61 ProviderID: "123",
62 })
63 }); err != nil {
64 t.Fatalf("could not add provided tag to machine: %v", err)
65 }
66 if err := session.Transact(ctx, func(q *model.Queries) error {
67 candidates, err := q.GetMachinesForAgentStart(ctx, 1)
68 if err != nil {
69 t.Fatalf("Could not retrieve machines for agent start: %v", err)
70 }
71 if want, got := 1, len(candidates); want != got {
72 t.Fatalf("Wanted %d machines for agent start, got %+v", want, candidates)
73 }
74 if want, got := machine.MachineID, candidates[0].MachineID; want != got {
75 t.Fatalf("Wanted %s for agent start, got %s", want, got)
76 }
77 return nil
78 }); err != nil {
79 t.Fatal(err)
80 }
81
82 // Add a start tag. Machine shouldn't be a candidate anymore.
83 if err := session.Transact(ctx, func(q *model.Queries) error {
84 return q.MachineSetAgentStarted(ctx, model.MachineSetAgentStartedParams{
85 MachineID: machine.MachineID,
86 AgentStartedAt: time.Now(),
87 AgentPublicKey: []byte("fakefakefakefake"),
88 })
89 }); err != nil {
90 t.Fatalf("could not add provided tag to machine: %v", err)
91 }
92 expectNoCandidates()
93}
94
95// TestAgentRecovery exercises GetMachinesForAgentRecovery though a few
96// different scenarios in which a test machine is present with different tags
97// set.
98func TestAgentRecovery(t *testing.T) {
99 b := dut()
100 conn, err := b.Open(true)
101 if err != nil {
102 t.Fatalf("Open failed: %v", err)
103 }
104
105 ctx, ctxC := context.WithCancel(context.Background())
106 defer ctxC()
107
108 session, err := conn.StartSession(ctx)
109 if err != nil {
110 t.Fatalf("Starting session failed: %v", err)
111 }
112
113 for i, scenario := range []struct {
114 // Whether recovery is expected to run.
115 wantRun bool
116 // started will add a AgentStarted tag for a given time, if set.
117 started time.Time
118 // heartbeat will add a AgentHeartbeat tag for a given time, if set.
119 heartbeat time.Time
120 }{
121 // No start, no heartbeat -> no recovery expected.
122 {false, time.Time{}, time.Time{}},
123 // Started recently, no heartbeat -> no recovery expected.
124 {false, time.Now(), time.Time{}},
125 // Started a while ago, heartbeat active -> no recovery expected.
126 {false, time.Now().Add(-40 * time.Minute), time.Now()},
127
128 // Started a while ago, no heartbeat -> recovery expected.
129 {true, time.Now().Add(-40 * time.Minute), time.Time{}},
130 // Started a while ago, no recent heartbeat -> recovery expected.
131 {true, time.Now().Add(-40 * time.Minute), time.Now().Add(-20 * time.Minute)},
132 } {
133 if err := session.Transact(ctx, func(q *model.Queries) error {
134 machine, err := q.NewMachine(ctx)
135 if err != nil {
136 return fmt.Errorf("NewMachine: %w", err)
137 }
138 if err := q.MachineAddProvided(ctx, model.MachineAddProvidedParams{
139 MachineID: machine.MachineID,
140 Provider: model.ProviderEquinix,
141 ProviderID: fmt.Sprintf("test-%d", i),
142 }); err != nil {
143 return fmt.Errorf("MachineAddProvided: %w", err)
144 }
145 if !scenario.started.IsZero() {
146 if err := q.MachineSetAgentStarted(ctx, model.MachineSetAgentStartedParams{
147 MachineID: machine.MachineID,
148 AgentStartedAt: scenario.started,
149 AgentPublicKey: []byte("fake"),
150 }); err != nil {
151 return fmt.Errorf("MachineSetAgentStarted: %w", err)
152 }
153 }
154 if !scenario.heartbeat.IsZero() {
155 if err := q.MachineSetAgentHeartbeat(ctx, model.MachineSetAgentHeartbeatParams{
156 MachineID: machine.MachineID,
157 AgentHeartbeatAt: scenario.heartbeat,
158 }); err != nil {
159 return fmt.Errorf("MachineSetAgentHeartbeat: %w", err)
160 }
161 }
162 return nil
163 }); err != nil {
164 t.Errorf("%d: setup failed: %v", i, err)
165 continue
166 }
167
168 if err := session.Transact(ctx, func(q *model.Queries) error {
169 candidates, err := q.GetMachineForAgentRecovery(ctx, 1)
170 if err != nil {
171 return fmt.Errorf("GetMachinesForAgentRecovery: %w", err)
172 }
173 if scenario.wantRun && len(candidates) == 0 {
174 return fmt.Errorf("machine unscheduled for recovery")
175 }
176 if !scenario.wantRun && len(candidates) != 0 {
177 return fmt.Errorf("machine scheduled for recovery")
178 }
179 return nil
180 }); err != nil {
181 t.Errorf("%d: test failed: %v", i, err)
182 }
183 }
184}