blob: 3d23efcf226319193b174922b92e8c887eba7cdf [file] [log] [blame]
Serge Bazanski424e2012023-02-15 23:31:49 +01001package bmdb
2
3import (
4 "context"
5 "fmt"
6 "strings"
7 "testing"
8 "time"
9
10 "github.com/google/uuid"
Serge Bazanski10b21542023-04-13 12:12:05 +020011 "google.golang.org/protobuf/proto"
Serge Bazanski424e2012023-02-15 23:31:49 +010012
Serge Bazanski10b21542023-04-13 12:12:05 +020013 apb "source.monogon.dev/cloud/agent/api"
Serge Bazanski424e2012023-02-15 23:31:49 +010014 "source.monogon.dev/cloud/bmaas/bmdb/model"
15 "source.monogon.dev/cloud/bmaas/bmdb/reflection"
Serge Bazanski10b21542023-04-13 12:12:05 +020016 "source.monogon.dev/cloud/bmaas/server/api"
Serge Bazanski424e2012023-02-15 23:31:49 +010017)
18
19// TestReflection exercises the BMDB reflection schema reflection and data
20// retrieval code. Ideally this code would live in //cloud/bmaas/bmdb/reflection,
21// but due to namespacing issues it lives here.
22func TestReflection(t *testing.T) {
23 b := dut()
24 conn, err := b.Open(true)
25 if err != nil {
26 t.Fatalf("Open failed: %v", err)
27 }
28
29 ctx, ctxC := context.WithCancel(context.Background())
30 defer ctxC()
31
32 sess, err := conn.StartSession(ctx)
33 if err != nil {
34 t.Fatalf("StartSession: %v", err)
35 }
36
37 // Create 10 test machines.
38 var mids []uuid.UUID
39 sess.Transact(ctx, func(q *model.Queries) error {
40 for i := 0; i < 10; i += 1 {
41 mach, err := q.NewMachine(ctx)
42 if err != nil {
43 return err
44 }
45 err = q.MachineAddProvided(ctx, model.MachineAddProvidedParams{
46 MachineID: mach.MachineID,
47 Provider: model.ProviderEquinix,
48 ProviderID: fmt.Sprintf("test-%d", i),
49 })
50 if err != nil {
51 return err
52 }
53 mids = append(mids, mach.MachineID)
54 }
55 return nil
56 })
57 if err != nil {
58 t.Fatal(err)
59 }
60 // Start and fail work on one of the machines with an hour long backoff.
61 w, err := sess.Work(ctx, model.ProcessUnitTest1, func(q *model.Queries) ([]uuid.UUID, error) {
62 return mids[0:1], nil
63 })
64 if err != nil {
65 t.Fatal(err)
66 }
67 to := time.Hour
68 w.Fail(ctx, &to, "failure test")
69
70 // On another machine, create a failure with a 1 second backoff.
71 w, err = sess.Work(ctx, model.ProcessUnitTest1, func(q *model.Queries) ([]uuid.UUID, error) {
72 return mids[1:2], nil
73 })
74 if err != nil {
75 t.Fatal(err)
76 }
77 to = time.Second
78 w.Fail(ctx, &to, "failure test")
79 // Later on in the test we must wait for this backoff to actually elapse. Start
80 // counting now.
81 elapsed := time.NewTicker(to * 1)
82 defer elapsed.Stop()
83
84 // On another machine, create work and don't finish it yet.
85 _, err = sess.Work(ctx, model.ProcessUnitTest1, func(q *model.Queries) ([]uuid.UUID, error) {
86 return mids[2:3], nil
87 })
88 if err != nil {
89 t.Fatal(err)
90 }
91
92 schema, err := conn.Reflect(ctx)
93 if err != nil {
94 t.Fatalf("ReflectTagTypes: %v", err)
95 }
96
97 // Dump all in strict mode.
98 opts := &reflection.GetMachinesOpts{
99 Strict: true,
100 }
101 res, err := schema.GetMachines(ctx, opts)
102 if err != nil {
103 t.Fatalf("Dump failed: %v", err)
104 }
105 if res.Query == "" {
106 t.Errorf("Query not set on result")
107 }
108 machines := res.Data
109 if want, got := 10, len(machines); want != got {
110 t.Fatalf("Expected %d machines in dump, got %d", want, got)
111 }
112
113 // Expect Provided tag on all machines. Do a detailed check on fields, too.
114 for _, machine := range machines {
115 tag, ok := machine.Tags["Provided"]
116 if !ok {
117 t.Errorf("No Provided tag on machine.")
118 continue
119 }
120 if want, got := "Provided", tag.Type.Name(); want != got {
121 t.Errorf("Provided tag should have type %q, got %q", want, got)
122 }
123 if provider := tag.Field("provider"); provider != nil {
124 if want, got := provider.HumanValue(), "Equinix"; want != got {
125 t.Errorf("Wanted Provided.provider value %q, got %q", want, got)
126 }
127 } else {
128 t.Errorf("Provider tag has no provider field")
129 }
130 if providerId := tag.Field("provider_id"); providerId != nil {
131 if !strings.HasPrefix(providerId.HumanValue(), "test-") {
132 t.Errorf("Unexpected provider_id value %q", providerId.HumanValue())
133 }
134 } else {
135 t.Errorf("Provider tag has no provider_id field")
136 }
137 }
138
139 // Now just dump one machine.
140 opts.FilterMachine = &mids[0]
141 res, err = schema.GetMachines(ctx, opts)
142 if err != nil {
143 t.Fatalf("Dump failed: %v", err)
144 }
145 machines = res.Data
146 if want, got := 1, len(machines); want != got {
147 t.Fatalf("Expected %d machines in dump, got %d", want, got)
148 }
149 if want, got := mids[0].String(), machines[0].ID.String(); want != got {
150 t.Fatalf("Expected machine %s, got %s", want, got)
151 }
152
153 // Now dump a machine that doesn't exist. That should just return an empty list.
154 fakeMid := uuid.New()
155 opts.FilterMachine = &fakeMid
156 res, err = schema.GetMachines(ctx, opts)
157 if err != nil {
158 t.Fatalf("Dump failed: %v", err)
159 }
160 machines = res.Data
161 if want, got := 0, len(machines); want != got {
162 t.Fatalf("Expected %d machines in dump, got %d", want, got)
163 }
164
165 // Finally, check the special case machines. The first one should have an active
166 // backoff.
167 opts.FilterMachine = &mids[0]
168 res, err = schema.GetMachines(ctx, opts)
169 if err != nil {
170 t.Errorf("Dump failed: %v", err)
171 } else {
172 machine := res.Data[0]
173 if _, ok := machine.Backoffs["UnitTest1"]; !ok {
174 t.Errorf("Expected UnitTest1 backoff on machine")
175 }
176 }
177 // The second one should have an expired backoff that shouldn't be reported in a
178 // normal call..
179 <-elapsed.C
180 opts.FilterMachine = &mids[1]
181 res, err = schema.GetMachines(ctx, opts)
182 if err != nil {
183 t.Errorf("Dump failed: %v", err)
184 } else {
185 machine := res.Data[0]
186 if _, ok := machine.Backoffs["UnitTest1"]; ok {
187 t.Errorf("Expected no UnitTest1 backoff on machine")
188 }
189 }
190 // But if we ask for expired backoffs, we should get it.
191 opts.ExpiredBackoffs = true
192 res, err = schema.GetMachines(ctx, opts)
193 if err != nil {
194 t.Errorf("Dump failed: %v", err)
195 } else {
196 machine := res.Data[0]
197 if _, ok := machine.Backoffs["UnitTest1"]; !ok {
198 t.Errorf("Expected UnitTest1 backoff on machine")
199 }
200 }
201 // Finally, the third machine should have an active Work item.
202 opts.FilterMachine = &mids[2]
203 res, err = schema.GetMachines(ctx, opts)
204 if err != nil {
205 t.Errorf("Dump failed: %v", err)
206 } else {
207 machine := res.Data[0]
208 if _, ok := machine.Work["UnitTest1"]; !ok {
209 t.Errorf("Expected UnitTest1 work item on machine")
210 }
211 }
212}
Serge Bazanski10b21542023-04-13 12:12:05 +0200213
214// TestReflectionProtoFields ensures that the basic proto field introspection
215// functionality works.
216func TestReflectionProtoFields(t *testing.T) {
217 s := dut()
218 ctx, ctxC := context.WithCancel(context.Background())
219 defer ctxC()
220
221 bmdb, err := s.Open(true)
222 if err != nil {
223 t.Fatalf("Open: %v", err)
224 }
225 sess, err := bmdb.StartSession(ctx)
226 if err != nil {
227 t.Fatalf("StartSession: %v", err)
228 }
229 var machine model.Machine
230 err = sess.Transact(ctx, func(q *model.Queries) error {
231 machine, err = q.NewMachine(ctx)
232 if err != nil {
233 return err
234 }
235
236 report := &api.AgentHardwareReport{
237 Report: &apb.Node{
238 Manufacturer: "Charles Babbage",
239 Product: "Analytical Engine",
240 SerialNumber: "183701",
241 MemoryInstalledBytes: 14375,
242 MemoryUsableRatio: 1.0,
243 Cpu: []*apb.CPU{
244 {
245 Architecture: nil,
246 HardwareThreads: 1,
247 Cores: 1,
248 },
249 },
250 },
251 Warning: []string{"something went wrong"},
252 }
253 b, _ := proto.Marshal(report)
254 return q.MachineSetHardwareReport(ctx, model.MachineSetHardwareReportParams{
255 MachineID: machine.MachineID,
256 HardwareReportRaw: b,
257 })
258 })
259 if err != nil {
260 t.Fatalf("Failed to submit hardware report: %v", err)
261 }
262
263 schem, err := bmdb.Reflect(ctx)
264 if err != nil {
265 t.Fatalf("Failed to reflect on database: %v", err)
266 }
267
268 machines, err := schem.GetMachines(ctx, &reflection.GetMachinesOpts{FilterMachine: &machine.MachineID, Strict: true})
269 if err != nil {
270 t.Fatalf("Failed to get machine: %v", err)
271 }
272 if len(machines.Data) != 1 {
273 t.Errorf("Expected one machine, got %d", len(machines.Data))
274 } else {
275 machine := machines.Data[0]
276 ty := machine.Tags["HardwareReport"].Field("hardware_report_raw").Type.HumanType()
277 if want, got := "cloud.bmaas.server.api.AgentHardwareReport", ty; want != got {
278 t.Errorf("Mismatch in type: wanted %q, got %q", want, got)
279 }
280 v := machine.Tags["HardwareReport"].Field("hardware_report_raw").HumanValue()
281 if !strings.Contains(v, "manufacturer:") {
282 t.Errorf("Invalid serialized prototext: %s", v)
283 }
284 }
285}