Skip to content

Commit 04ec70f

Browse files
committed
PBM-1649 Basic tests for cleanup and delete-backup components
1 parent f2b8713 commit 04ec70f

File tree

1 file changed

+375
-0
lines changed

1 file changed

+375
-0
lines changed

pbm/backup/delete_test.go

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

0 commit comments

Comments
 (0)