@@ -81,6 +81,15 @@ var selectedNSSupportedCommands = map[string]struct{}{
81
81
"commitIndexBuild" : {},
82
82
}
83
83
84
+ var cloningNSSupportedCommands = map [string ]struct {}{
85
+ "createIndexes" : {},
86
+ "deleteIndex" : {},
87
+ "deleteIndexes" : {},
88
+ "dropIndex" : {},
89
+ "dropIndexes" : {},
90
+ "commitIndexBuild" : {},
91
+ }
92
+
84
93
var dontPreserveUUID = []string {
85
94
"admin.system.users" ,
86
95
"admin.system.roles" ,
@@ -89,6 +98,18 @@ var dontPreserveUUID = []string{
89
98
"*.system.views" , // timeseries
90
99
}
91
100
101
+ var ErrNoCloningNamespace = errors .New ("cloning namespace desn't exist" )
102
+
103
+ // cloneNS has all data related to cloning namespace within oplog
104
+ type cloneNS struct {
105
+ snapshot.CloneNS
106
+ toUUID primitive.Binary
107
+ }
108
+
109
+ func (c * cloneNS ) SetNSPair (nsPair snapshot.CloneNS ) {
110
+ c .CloneNS = nsPair
111
+ }
112
+
92
113
// OplogRestore is the oplog applyer
93
114
type OplogRestore struct {
94
115
dst * mongo.Client
@@ -120,7 +141,8 @@ type OplogRestore struct {
120
141
121
142
unsafe bool
122
143
123
- filter OpFilter
144
+ filter OpFilter
145
+ cloneNS cloneNS
124
146
}
125
147
126
148
const saveLastDistTxns = 100
@@ -196,6 +218,7 @@ func (o *OplogRestore) Apply(src io.ReadCloser) (primitive.Timestamp, error) {
196
218
defer bsonSource .Close ()
197
219
198
220
var lts primitive.Timestamp
221
+
199
222
for {
200
223
rawOplogEntry := bsonSource .LoadNext ()
201
224
if rawOplogEntry == nil {
@@ -257,6 +280,27 @@ func (o *OplogRestore) SetIncludeNS(nss []string) {
257
280
o .includeNS = dbs
258
281
}
259
282
283
+ // SetCloneNS sets all needed data for cloning namespace:
284
+ // collection names and target namespace UUID
285
+ func (o * OplogRestore ) SetCloneNS (ctx context.Context , ns snapshot.CloneNS ) error {
286
+ if ! ns .IsSpecified () {
287
+ return nil
288
+ }
289
+
290
+ o .cloneNS .SetNSPair (ns )
291
+
292
+ var err error
293
+ o .cloneNS .toUUID , err = getUUIDForNS (ctx , o .dst , o .cloneNS .ToNS )
294
+ if err != nil {
295
+ return errors .Wrap (err , "get to ns uuid" )
296
+ }
297
+ if o .cloneNS .toUUID .IsZero () {
298
+ return ErrNoCloningNamespace
299
+ }
300
+
301
+ return nil
302
+ }
303
+
260
304
func isOpAllowed (oe * Record ) bool {
261
305
coll , ok := strings .CutPrefix (oe .Namespace , "config." )
262
306
if ! ok {
@@ -310,6 +354,50 @@ func (o *OplogRestore) isOpSelected(oe *Record) bool {
310
354
return false
311
355
}
312
356
357
+ // isOpForCloning returns whether op needs to be processed or not in case of cloning NS.
358
+ // In case of non cloning use case, it's always true.
359
+ func (o * OplogRestore ) isOpForCloning (oe * db.Oplog ) bool {
360
+ if ! o .cloneNS .IsSpecified () {
361
+ return true
362
+ }
363
+
364
+ // i, u, d ops for cloning ns
365
+ if oe .Namespace == o .cloneNS .FromNS {
366
+ return true
367
+ }
368
+
369
+ // filter out all other i, u, d ops
370
+ if oe .Operation != "c" {
371
+ return false
372
+ }
373
+
374
+ db , coll , _ := strings .Cut (oe .Namespace , "." )
375
+ if coll != "$cmd" {
376
+ return false
377
+ }
378
+
379
+ cmd := oe .Object [0 ].Key
380
+ if cmd == "applyOps" {
381
+ return true // internal ops of applyOps are checked one by one later
382
+ }
383
+
384
+ cloneFromDB , cloneFromColl , _ := strings .Cut (o .cloneNS .FromNS , "." )
385
+ if db != cloneFromDB {
386
+ // it's command not relevant for db to clone from
387
+ return false
388
+ }
389
+
390
+ if _ , ok := cloningNSSupportedCommands [cmd ]; ok {
391
+ // check if command targets collection
392
+ collForCmd , _ := oe .Object [0 ].Value .(string )
393
+ if collForCmd == cloneFromColl {
394
+ return true
395
+ }
396
+ }
397
+
398
+ return false
399
+ }
400
+
313
401
func (o * OplogRestore ) isOpExcluded (oe * Record ) bool {
314
402
if o .excludeNS == nil {
315
403
return false
@@ -360,7 +448,8 @@ func (o *OplogRestore) handleOp(oe db.Oplog) error {
360
448
return nil
361
449
}
362
450
363
- if o .isOpExcluded (& oe ) || ! isOpAllowed (& oe ) || ! o .isOpSelected (& oe ) {
451
+ if o .isOpExcluded (& oe ) || ! isOpAllowed (& oe ) ||
452
+ ! o .isOpSelected (& oe ) || ! o .isOpForCloning (& oe ) {
364
453
return nil
365
454
}
366
455
@@ -676,10 +765,21 @@ func (o *OplogRestore) HandleUncommittedTxn(
676
765
return partial , uncommitted , nil
677
766
}
678
767
768
+ func (o * OplogRestore ) cloneEntry (op * db.Oplog ) {
769
+ if ! o .cloneNS .IsSpecified () {
770
+ return
771
+ }
772
+ if op .Namespace == o .cloneNS .FromNS {
773
+ * op .UI = o .cloneNS .toUUID
774
+ op .Namespace = o .cloneNS .ToNS
775
+ }
776
+ }
777
+
679
778
func (o * OplogRestore ) handleNonTxnOp (op db.Oplog ) error {
680
779
// have to handle it here one more time because before the op gets thru
681
780
// txnBuffer its namespace is `collection.$cmd` instead of the real one
682
- if o .isOpExcluded (& op ) || ! isOpAllowed (& op ) || ! o .isOpSelected (& op ) {
781
+ if o .isOpExcluded (& op ) || ! isOpAllowed (& op ) ||
782
+ ! o .isOpSelected (& op ) || ! o .isOpForCloning (& op ) {
683
783
return nil
684
784
}
685
785
@@ -688,6 +788,8 @@ func (o *OplogRestore) handleNonTxnOp(op db.Oplog) error {
688
788
return errors .Wrap (err , "filtering UUIDs from oplog" )
689
789
}
690
790
791
+ o .cloneEntry (& op )
792
+
691
793
dbName , collName , _ := strings .Cut (op .Namespace , "." )
692
794
if op .Operation == "c" {
693
795
if len (op .Object ) == 0 {
@@ -1125,3 +1227,29 @@ func isTruthy(val interface{}) bool {
1125
1227
func isFalsy (val interface {}) bool {
1126
1228
return ! isTruthy (val )
1127
1229
}
1230
+
1231
+ // getUUIDForNS ruturns UUID of existing collection.
1232
+ // When ns doesn't exist, it returns zero value without an error.
1233
+ // In case of error, it returns zero value for UUID in addition to error.
1234
+ func getUUIDForNS (ctx context.Context , m * mongo.Client , ns string ) (primitive.Binary , error ) {
1235
+ var uuid primitive.Binary
1236
+
1237
+ d , c , _ := strings .Cut (ns , "." )
1238
+ cur , err := m .Database (d ).ListCollections (ctx , bson.D {{"name" , c }})
1239
+ if err != nil {
1240
+ return uuid , errors .Wrap (err , "list collections" )
1241
+ }
1242
+ defer cur .Close (ctx )
1243
+
1244
+ for cur .Next (ctx ) {
1245
+ if subtype , data , ok := cur .Current .Lookup ("info" , "uuid" ).BinaryOK (); ok {
1246
+ uuid = primitive.Binary {
1247
+ Subtype : subtype ,
1248
+ Data : data ,
1249
+ }
1250
+ break
1251
+ }
1252
+ }
1253
+
1254
+ return uuid , errors .Wrap (cur .Err (), "list collections cursor" )
1255
+ }
0 commit comments