blob: 8583efd2d665de3b611643fb8a5fa185412710d0 [file] [log] [blame]
Serge Bazanskiae004682023-04-18 13:28:48 +02001package manager
2
3import (
4 "context"
5 "testing"
6 "time"
7
8 "github.com/packethost/packngo"
9 "golang.org/x/time/rate"
10
11 "source.monogon.dev/cloud/bmaas/bmdb"
12 "source.monogon.dev/cloud/bmaas/bmdb/model"
13 "source.monogon.dev/cloud/lib/component"
14)
15
16type recovererDut struct {
17 f *fakequinix
18 r *Recoverer
19 bmdb *bmdb.Connection
20 ctx context.Context
21}
22
23func newRecovererDut(t *testing.T) *recovererDut {
24 t.Helper()
25
26 rc := RecovererConfig{
27 ControlLoopConfig: ControlLoopConfig{
28 DBQueryLimiter: rate.NewLimiter(rate.Every(time.Second), 10),
29 },
30 }
31
32 f := newFakequinix("fake", 100)
33 r, err := NewRecoverer(f, rc)
34 if err != nil {
35 t.Fatalf("Could not create Initializer: %v", err)
36 }
37
38 b := bmdb.BMDB{
39 Config: bmdb.Config{
40 Database: component.CockroachConfig{
41 InMemory: true,
42 },
43 ComponentName: "test",
44 RuntimeInfo: "test",
45 },
46 }
47 conn, err := b.Open(true)
48 if err != nil {
49 t.Fatalf("Could not create in-memory BMDB: %v", err)
50 }
51
52 ctx, ctxC := context.WithCancel(context.Background())
53 t.Cleanup(ctxC)
54
55 go RunControlLoop(ctx, conn, r)
56
57 return &recovererDut{
58 f: f,
59 r: r,
60 bmdb: conn,
61 ctx: ctx,
62 }
63}
64
65// TestRecoverySmokes makes sure that the Initializer in recovery mode doesn't go
66// up in flames on the happy path.
67func TestRecoverySmokes(t *testing.T) {
68 dut := newRecovererDut(t)
69 f := dut.f
70 ctx := dut.ctx
71 conn := dut.bmdb
72
73 reservations, _ := f.ListReservations(ctx, "fake")
74
75 sess, err := conn.StartSession(ctx)
76 if err != nil {
77 t.Fatalf("Failed to create BMDB session: %v", err)
78 }
79
80 // Create test machine that should be selected for recovery.
81 // First in Fakequinix...
82 dev, _ := f.CreateDevice(ctx, &packngo.DeviceCreateRequest{
83 Hostname: "test-devices",
84 OS: "fake",
85 ProjectID: "fake",
86 HardwareReservationID: reservations[0].ID,
87 ProjectSSHKeys: []string{},
88 })
89 // ... and in BMDB.
90 err = sess.Transact(ctx, func(q *model.Queries) error {
91 machine, err := q.NewMachine(ctx)
92 if err != nil {
93 return err
94 }
95 err = q.MachineAddProvided(ctx, model.MachineAddProvidedParams{
96 MachineID: machine.MachineID,
97 Provider: model.ProviderEquinix,
98 ProviderID: dev.ID,
99 })
100 if err != nil {
101 return err
102 }
103 return q.MachineSetAgentStarted(ctx, model.MachineSetAgentStartedParams{
104 MachineID: machine.MachineID,
105 AgentStartedAt: time.Now().Add(time.Hour * -10),
106 AgentPublicKey: []byte("fakefakefakefake"),
107 })
108 })
109 if err != nil {
110 t.Fatalf("Failed to create test machine: %v", err)
111 }
112
113 // Expect to find 0 machines needing recovery.
114 deadline := time.Now().Add(10 * time.Second)
115 for {
116 if time.Now().After(deadline) {
117 t.Fatalf("Machines did not get processed in time")
118 }
119 time.Sleep(100 * time.Millisecond)
120
121 var machines []model.MachineProvided
122 err = sess.Transact(ctx, func(q *model.Queries) error {
123 var err error
124 machines, err = q.GetMachineForAgentRecovery(ctx, 100)
125 return err
126 })
127 if err != nil {
128 t.Fatalf("Failed to run Transaction: %v", err)
129 }
130 if len(machines) == 0 {
131 break
132 }
133 }
134
135 // Expect the target machine to have been rebooted.
136 dut.f.mu.Lock()
137 reboots := dut.f.reboots[dev.ID]
138 dut.f.mu.Unlock()
139 if want, got := 1, reboots; want != got {
140 t.Fatalf("Wanted %d reboot, got %d", want, got)
141 }
142
143 // Expect machine to now be available again for agent start.
144 var machines []model.MachineProvided
145 err = sess.Transact(ctx, func(q *model.Queries) error {
146 var err error
147 machines, err = q.GetMachinesForAgentStart(ctx, 100)
148 return err
149 })
150 if err != nil {
151 t.Fatalf("Failed to run Transaction: %v", err)
152 }
153 if want, got := 1, len(machines); want != got {
154 t.Fatalf("Wanted %d machine ready for agent start, got %d", want, got)
155 }
156}