Skip to content

Commit ba29f80

Browse files
authored
Merge pull request #1161 from percona/PBM-1497-applyOps-stale-config
PBM-1497: Exclude balancer ops during PITR
2 parents da3bd6c + 5a47fcb commit ba29f80

File tree

2 files changed

+147
-1
lines changed

2 files changed

+147
-1
lines changed

pbm/oplog/restore.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,24 @@ var dontPreserveUUID = []string{
100100
"*.system.views", // timeseries
101101
}
102102

103+
// ConfigCollToKeep defines a list of collections in the `config`
104+
// database that PBM will apply oplog events.
105+
var configCollToKeep = []string{
106+
"chunks",
107+
"collections",
108+
"databases",
109+
"shards",
110+
"tags",
111+
"version",
112+
}
113+
114+
const settingsColl = "settings"
115+
116+
var settingsToSkip = []string{
117+
"balancer",
118+
"automerge",
119+
}
120+
103121
var ErrNoCloningNamespace = errors.New("cloning namespace desn't exist")
104122

105123
// cloneNS has all data related to cloning namespace within oplog
@@ -316,16 +334,23 @@ func (o *OplogRestore) SetCloneNS(ctx context.Context, ns snapshot.CloneNS) erro
316334
return nil
317335
}
318336

337+
// isOpAllowed inspects whether op is allowed from config database point of view.
338+
// It allows/disallows only specific collections for config database.
339+
// It disallows balancer settings that would cause the balancer to work during PITR.
319340
func isOpAllowed(oe *Record) bool {
320341
coll, ok := strings.CutPrefix(oe.Namespace, "config.")
321342
if !ok {
322343
return true // OK: not a "config" database. allow any ops
323344
}
324345

325-
if slices.Contains(dumprestore.ConfigCollectionsToKeep, coll) {
346+
if slices.Contains(configCollToKeep, coll) {
326347
return true // OK: create/update/delete a doc
327348
}
328349

350+
if coll == settingsColl {
351+
return isConfigSettingAllowed(oe)
352+
}
353+
329354
if coll != "$cmd" || len(oe.Object) == 0 {
330355
return false // other collection is not allowed
331356
}
@@ -342,6 +367,20 @@ func isOpAllowed(oe *Record) bool {
342367
return false
343368
}
344369

370+
// isConfigSettingAllowed filter out entries from config.settings collection
371+
func isConfigSettingAllowed(oe *Record) bool {
372+
for _, e := range oe.Query {
373+
if e.Key != "_id" {
374+
continue
375+
}
376+
setting, _ := e.Value.(string)
377+
if slices.Contains(settingsToSkip, setting) {
378+
return false
379+
}
380+
}
381+
return true // allow setting from config.settings
382+
}
383+
345384
func (o *OplogRestore) isOpSelected(oe *Record) bool {
346385
if o.includeNS == nil || o.includeNS[""] != nil {
347386
return true
@@ -412,6 +451,8 @@ func (o *OplogRestore) isOpForCloning(oe *db.Oplog) bool {
412451
return false
413452
}
414453

454+
// isOpExcluded check if collection is excluded.
455+
// Mostly refers PBM's control collections, but also some config and admin ones.
415456
func (o *OplogRestore) isOpExcluded(oe *Record) bool {
416457
if o.excludeNS == nil {
417458
return false

pbm/oplog/restore_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,85 @@ func TestIsOpForCloning(t *testing.T) {
141141
}
142142
}
143143

144+
func TestIsOpAllowed(t *testing.T) {
145+
testCases := []struct {
146+
desc string
147+
entry *db.Oplog
148+
opAllowed bool
149+
}{
150+
{
151+
desc: "any other than config",
152+
entry: createInsertOp(t, "db1.c1"),
153+
opAllowed: true,
154+
},
155+
{
156+
desc: "config.chunks",
157+
entry: createInsertOp(t, "config.chunks"),
158+
opAllowed: true,
159+
},
160+
{
161+
desc: "config.collections",
162+
entry: createInsertOp(t, "config.collections"),
163+
opAllowed: true,
164+
},
165+
{
166+
desc: "config.databases",
167+
entry: createInsertOp(t, "config.databases"),
168+
opAllowed: true,
169+
},
170+
{
171+
desc: "config.shards",
172+
entry: createInsertOp(t, "config.shards"),
173+
opAllowed: true,
174+
},
175+
{
176+
desc: "config.tags",
177+
entry: createUpdateOp(t, "config.tags"),
178+
opAllowed: true,
179+
},
180+
{
181+
desc: "config.version",
182+
entry: createDeleteOp(t, "config.version"),
183+
opAllowed: true,
184+
},
185+
{
186+
desc: "dissalowed config collection",
187+
entry: createInsertOp(t, "config.mongos"),
188+
opAllowed: false,
189+
},
190+
{
191+
desc: "wrong config.settings doc will not break",
192+
entry: createDeleteOp(t, "config.settings"),
193+
opAllowed: true,
194+
},
195+
{
196+
desc: "settings doc for balancer",
197+
entry: configSettingsBalancerEntry(),
198+
opAllowed: false,
199+
},
200+
{
201+
desc: "settings doc for automerge",
202+
entry: configSettingsAutomergeEntry(),
203+
opAllowed: false,
204+
},
205+
{
206+
desc: "allowed settings doc",
207+
entry: configSettingsAnyOtherEntry(),
208+
opAllowed: true,
209+
},
210+
}
211+
212+
for _, tC := range testCases {
213+
t.Run(tC.desc, func(t *testing.T) {
214+
res := isOpAllowed(tC.entry)
215+
if res != tC.opAllowed {
216+
t.Errorf("%s: for entry: %+v isOpAllowed is: %t, but it should be opposite",
217+
tC.desc, tC.entry, tC.opAllowed)
218+
}
219+
})
220+
}
221+
}
222+
144223
func TestApply(t *testing.T) {
145224
t.Run("collection restore", func(t *testing.T) {
146225
testCases := []struct {
@@ -638,3 +717,29 @@ func replaceNsWithinOpEntry(t *testing.T, jsonEntry, ns string) *db.Oplog {
638717
}
639718
return &oe
640719
}
720+
721+
func configSettingsBalancerEntry() *db.Oplog {
722+
return &db.Oplog{
723+
Operation: "i",
724+
Namespace: "config.settings",
725+
Object: bson.D{{"_id", "balancer"}, {"mode", "off"}, {"stopped", true}},
726+
Query: bson.D{{"_id", "balancer"}},
727+
}
728+
}
729+
730+
func configSettingsAutomergeEntry() *db.Oplog {
731+
return &db.Oplog{
732+
Operation: "u",
733+
Namespace: "config.settings",
734+
Query: bson.D{{"_id", "automerge"}},
735+
}
736+
}
737+
738+
func configSettingsAnyOtherEntry() *db.Oplog {
739+
return &db.Oplog{
740+
Operation: "i",
741+
Namespace: "config.settings",
742+
Object: bson.D{{"_id", "chunksize"}, {"value", 128}},
743+
Query: bson.D{{"_id", "chunksize"}},
744+
}
745+
}

0 commit comments

Comments
 (0)