Skip to content

Commit ae2ec70

Browse files
committed
PBM-1649 Basic agent tests for cleanup and delete-backup
1 parent b50c8a5 commit ae2ec70

File tree

1 file changed

+384
-0
lines changed

1 file changed

+384
-0
lines changed

cmd/pbm-agent/delete_test.go

Lines changed: 384 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,384 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"log"
8+
"os"
9+
"testing"
10+
"time"
11+
12+
"github.com/percona/percona-backup-mongodb/pbm/backup"
13+
"github.com/percona/percona-backup-mongodb/pbm/config"
14+
"github.com/percona/percona-backup-mongodb/pbm/connect"
15+
"github.com/percona/percona-backup-mongodb/pbm/ctrl"
16+
"github.com/percona/percona-backup-mongodb/pbm/defs"
17+
pbmlog "github.com/percona/percona-backup-mongodb/pbm/log"
18+
"github.com/percona/percona-backup-mongodb/pbm/storage"
19+
"github.com/percona/percona-backup-mongodb/pbm/storage/fs"
20+
"github.com/percona/percona-backup-mongodb/pbm/topo"
21+
"github.com/stretchr/testify/assert"
22+
"github.com/testcontainers/testcontainers-go/modules/mongodb"
23+
"go.mongodb.org/mongo-driver/bson"
24+
"go.mongodb.org/mongo-driver/bson/primitive"
25+
"golang.org/x/sync/errgroup"
26+
)
27+
28+
type TestEnvironment struct {
29+
Mongo *TestMongo
30+
Agent *Agent
31+
StoragePath string
32+
stopAgentFunc context.CancelFunc
33+
eg errgroup.Group
34+
}
35+
36+
type TestMongo struct {
37+
Container *mongodb.MongoDBContainer
38+
ReplSet string
39+
CS string
40+
Client connect.Client
41+
image string
42+
csParams string
43+
}
44+
45+
var TestEnv = newTestEnv()
46+
47+
func newTestEnv() *TestEnvironment {
48+
mongo := &TestMongo{
49+
ReplSet: "rs0",
50+
image: "perconalab/percona-server-mongodb:8.0.4-multi",
51+
csParams: "directConnection=true",
52+
}
53+
tenv := &TestEnvironment{
54+
Mongo: mongo,
55+
}
56+
57+
err := tenv.InitStorage()
58+
if err != nil {
59+
log.Fatalf("failed to create temp dir: %s", err)
60+
}
61+
62+
return tenv
63+
}
64+
65+
func (tenv *TestEnvironment) InitStorage() error {
66+
stg, err := os.MkdirTemp("", "pbm_tests_*")
67+
if err != nil {
68+
return err
69+
}
70+
tenv.StoragePath = stg
71+
return nil
72+
}
73+
74+
func (tenv *TestEnvironment) GetSorageTmpDir(name string) (string, error) {
75+
if name == "" {
76+
name = "default"
77+
}
78+
return os.MkdirTemp(tenv.StoragePath, name+"_*")
79+
}
80+
81+
func (tenv *TestEnvironment) StartMongo(ctx context.Context) error {
82+
cnt, err := mongodb.Run(ctx, tenv.Mongo.image, mongodb.WithReplicaSet(tenv.Mongo.ReplSet))
83+
if err != nil {
84+
return err
85+
}
86+
87+
cs, err := cnt.ConnectionString(ctx)
88+
if err != nil {
89+
return err
90+
}
91+
cs += "&" + tenv.Mongo.csParams
92+
93+
client, err := connect.Connect(ctx, cs, "test")
94+
if err != nil {
95+
return err
96+
}
97+
98+
tenv.Mongo.CS = cs
99+
tenv.Mongo.Client = client
100+
tenv.Mongo.Container = cnt
101+
102+
return nil
103+
}
104+
105+
func (tm *TestMongo) StopMongo(ctx context.Context) error {
106+
return tm.Container.Terminate(ctx)
107+
}
108+
109+
func (tenv *TestEnvironment) CreateAgent(ctx context.Context) error {
110+
if tenv.Agent != nil {
111+
return fmt.Errorf("agent already created")
112+
}
113+
a, err := newAgent(ctx, tenv.Mongo.Client, tenv.Mongo.CS, 1)
114+
if err != nil {
115+
return err
116+
}
117+
tenv.Agent = a
118+
return nil
119+
}
120+
121+
func (tenv *TestEnvironment) StartAgent(ctx context.Context) error {
122+
if tenv.Agent == nil {
123+
if err := tenv.CreateAgent(ctx); err != nil {
124+
return err
125+
}
126+
}
127+
c, cancel := context.WithCancel(ctx)
128+
tenv.stopAgentFunc = cancel
129+
130+
// create config
131+
err := TestEnv.setConfigWithStorage(nil)
132+
if err != nil {
133+
return err
134+
}
135+
136+
// start the agent
137+
tenv.eg.Go(func() error {
138+
tenv.Agent.HbStatus(c)
139+
return nil
140+
})
141+
tenv.eg.Go(func() error {
142+
return tenv.Agent.Start(c)
143+
})
144+
145+
return nil
146+
}
147+
148+
func (tenv *TestEnvironment) Cleanup(ctx context.Context) error {
149+
agentErr := tenv.StopAgent()
150+
mongoErr := tenv.Mongo.StopMongo(ctx)
151+
stgErr := os.RemoveAll(tenv.StoragePath)
152+
153+
return errors.Join(agentErr, mongoErr, stgErr)
154+
}
155+
156+
func (tenv *TestEnvironment) ResetStorage() error {
157+
err := os.RemoveAll(tenv.StoragePath)
158+
if err != nil {
159+
return err
160+
}
161+
return tenv.InitStorage()
162+
}
163+
164+
func (tenv *TestEnvironment) ResetAgent(t *testing.T) {
165+
err := tenv.StopAgent()
166+
if err != nil {
167+
assert.FailNow(t, "failed to stop agent: %v", err)
168+
}
169+
err = tenv.ResetStorage()
170+
if err != nil {
171+
assert.FailNow(t, "failed to reset storage: %v", err)
172+
}
173+
174+
// clear admin.pbm* collections
175+
db := tenv.Mongo.Client.MongoClient().Database(defs.DB)
176+
collections, err := db.ListCollectionNames(t.Context(), bson.D{{Key: "name", Value: primitive.Regex{Pattern: "^pbm"}}})
177+
if err != nil {
178+
assert.FailNow(t, "failed to list admin.pbm* collections: %v", err)
179+
}
180+
for _, coll := range collections {
181+
err = db.Collection(coll).Drop(t.Context())
182+
if err != nil {
183+
assert.FailNow(t, "failed to drop admin.pbm* collection %q: %v", coll, err)
184+
}
185+
}
186+
187+
err = tenv.StartAgent(t.Context())
188+
if err != nil {
189+
assert.FailNow(t, "failed to start agent: %v", err)
190+
}
191+
}
192+
193+
func (tenv *TestEnvironment) StopAgent() error {
194+
if tenv.stopAgentFunc == nil {
195+
return nil
196+
}
197+
tenv.stopAgentFunc()
198+
err := tenv.eg.Wait()
199+
tenv.stopAgentFunc = nil
200+
201+
return err
202+
}
203+
204+
func (tenv *TestEnvironment) AddStorageProfile(t *testing.T, name string) {
205+
err := tenv.setConfigWithStorage(&config.Config{IsProfile: true, Name: name})
206+
if err != nil {
207+
assert.FailNow(t, "failed to add storage profile: %v", err)
208+
}
209+
}
210+
211+
func (tenv *TestEnvironment) setConfigWithStorage(cfg *config.Config) error {
212+
if cfg == nil {
213+
cfg = &config.Config{}
214+
}
215+
216+
path, err := tenv.GetSorageTmpDir(cfg.Name)
217+
if err != nil {
218+
return err
219+
}
220+
221+
cfg.Storage = config.StorageConf{
222+
Type: storage.Filesystem,
223+
Filesystem: &fs.Config{
224+
Path: path,
225+
},
226+
}
227+
228+
if cfg.IsProfile {
229+
err = config.AddProfile(context.Background(), tenv.Mongo.Client, cfg)
230+
} else {
231+
err = config.SetConfig(context.Background(), tenv.Mongo.Client, cfg)
232+
}
233+
234+
return err
235+
}
236+
237+
func TestMain(m *testing.M) {
238+
ctx := context.Background()
239+
240+
err := TestEnv.StartMongo(ctx)
241+
if err != nil {
242+
log.Fatalf("failed to start test MongoDB: %s", err)
243+
}
244+
245+
defer func() {
246+
err = TestEnv.Cleanup(ctx)
247+
if err != nil {
248+
log.Fatalf("failed to cleanup test environment: %s", err)
249+
}
250+
}()
251+
252+
code := m.Run()
253+
os.Exit(code)
254+
}
255+
256+
func TestDeleteBackup(t *testing.T) {
257+
TestEnv.ResetAgent(t)
258+
TestEnv.AddStorageProfile(t, "secondary")
259+
260+
// run backups
261+
runBackup(t, t.Context(), TestEnv.Mongo.Client, TestEnv.Agent.brief, "")
262+
runBackup(t, t.Context(), TestEnv.Mongo.Client, TestEnv.Agent.brief, "secondary")
263+
assertBackupCount(t, TestEnv.Mongo.Client, 1, "")
264+
assertBackupCount(t, TestEnv.Mongo.Client, 1, "secondary")
265+
266+
t.Run("delete-default", func(t *testing.T) {
267+
runDeleteBackup(t, t.Context(), TestEnv.Mongo.Client, TestEnv.Agent, "")
268+
assertBackupCount(t, TestEnv.Mongo.Client, 0, "")
269+
assertBackupCount(t, TestEnv.Mongo.Client, 1, "secondary")
270+
271+
})
272+
273+
runBackup(t, t.Context(), TestEnv.Mongo.Client, TestEnv.Agent.brief, "")
274+
assertBackupCount(t, TestEnv.Mongo.Client, 1, "")
275+
276+
t.Run("delete-profile", func(t *testing.T) {
277+
runDeleteBackup(t, t.Context(), TestEnv.Mongo.Client, TestEnv.Agent, "secondary")
278+
assertBackupCount(t, TestEnv.Mongo.Client, 1, "")
279+
assertBackupCount(t, TestEnv.Mongo.Client, 0, "secondary")
280+
})
281+
}
282+
283+
func TestCleanupBackups(t *testing.T) {
284+
TestEnv.ResetAgent(t)
285+
TestEnv.AddStorageProfile(t, "secondary")
286+
287+
// run backups
288+
runBackup(t, t.Context(), TestEnv.Mongo.Client, TestEnv.Agent.brief, "")
289+
runBackup(t, t.Context(), TestEnv.Mongo.Client, TestEnv.Agent.brief, "secondary")
290+
assertBackupCount(t, TestEnv.Mongo.Client, 1, "")
291+
assertBackupCount(t, TestEnv.Mongo.Client, 1, "secondary")
292+
293+
t.Run("cleanup-default", func(t *testing.T) {
294+
runCleanupBackup(t, t.Context(), TestEnv.Mongo.Client, TestEnv.Agent, "")
295+
assertBackupCount(t, TestEnv.Mongo.Client, 0, "")
296+
assertBackupCount(t, TestEnv.Mongo.Client, 1, "secondary")
297+
298+
})
299+
300+
runBackup(t, t.Context(), TestEnv.Mongo.Client, TestEnv.Agent.brief, "")
301+
assertBackupCount(t, TestEnv.Mongo.Client, 1, "")
302+
303+
t.Run("cleanup-profile", func(t *testing.T) {
304+
runCleanupBackup(t, t.Context(), TestEnv.Mongo.Client, TestEnv.Agent, "secondary")
305+
assertBackupCount(t, TestEnv.Mongo.Client, 1, "")
306+
assertBackupCount(t, TestEnv.Mongo.Client, 0, "secondary")
307+
})
308+
}
309+
310+
func runCleanupBackup(t *testing.T, ctx context.Context, client connect.Client, agent *Agent, profile string) {
311+
opid := ctrl.OPID(primitive.NilObjectID)
312+
ts := time.Now().UTC().Unix()
313+
ep, err := config.GetEpoch(ctx, client)
314+
assert.Nil(t, err)
315+
316+
cmd := ctrl.CleanupCmd{
317+
OlderThan: primitive.Timestamp{T: uint32(ts), I: 0},
318+
Profile: profile,
319+
}
320+
agent.Cleanup(ctx, &cmd, opid, ep)
321+
}
322+
323+
func runDeleteBackup(t *testing.T, ctx context.Context, client connect.Client, agent *Agent, profile string) {
324+
ts := time.Now().UTC().Unix()
325+
opid := ctrl.OPID(primitive.NilObjectID)
326+
ep, err := config.GetEpoch(ctx, client)
327+
assert.Nil(t, err)
328+
329+
cmd := ctrl.DeleteBackupCmd{
330+
OlderThan: ts,
331+
Profile: profile,
332+
}
333+
agent.Delete(ctx, &cmd, opid, ep)
334+
}
335+
336+
func runBackup(t *testing.T, ctx context.Context, client connect.Client, brief topo.NodeBrief, profile string) {
337+
ts := time.Now().UTC().Format(time.RFC3339)
338+
opid := ctrl.OPID(primitive.NilObjectID)
339+
cmd := ctrl.BackupCmd{
340+
Type: defs.LogicalBackup,
341+
Name: ts,
342+
}
343+
344+
cfgFull, err := config.GetProfiledConfig(ctx, client, profile)
345+
assert.Nil(t, err)
346+
347+
bcp := backup.New(client, client.MongoClient(), brief, 1)
348+
bcp.SetConfig(cfgFull)
349+
350+
err = bcp.Init(ctx, &cmd, opid, topo.BalancerModeOff)
351+
assert.Nil(t, err)
352+
353+
err = bcp.Run(ctx, &cmd, opid, pbmlog.DiscardEvent)
354+
assert.Nil(t, err)
355+
}
356+
357+
func assertBackupCount(t *testing.T, client connect.Client, count int, profile string) {
358+
cfg, err := config.GetProfiledConfig(context.Background(), client, profile)
359+
assert.Nil(t, err)
360+
361+
f := bson.D{{Key: "store.profile", Value: nil}}
362+
if profile != "" {
363+
f = bson.D{
364+
{Key: "store.profile", Value: true},
365+
{Key: "store.name", Value: profile},
366+
}
367+
}
368+
cur, err := client.BcpCollection().Find(context.Background(), f)
369+
assert.Nil(t, err)
370+
371+
var bcps []backup.BackupMeta
372+
err = cur.All(context.Background(), &bcps)
373+
assert.Nil(t, err)
374+
assert.Len(t, bcps, count, "expected %d backups, got %d", count, len(bcps))
375+
376+
stg, err := fs.New(cfg.Storage.Filesystem)
377+
assert.Nil(t, err)
378+
379+
for _, bcp := range bcps {
380+
name := bcp.Name
381+
err := backup.CheckBackupFiles(context.Background(), stg, name)
382+
assert.Nil(t, err, "backup %q files check failed", name)
383+
}
384+
}

0 commit comments

Comments
 (0)