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