summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJudah Schvimer <judah@mongodb.com>2016-01-14 16:36:01 -0500
committerJudah Schvimer <judah@mongodb.com>2016-01-14 16:37:01 -0500
commitc72c5822cf2169d78b3c76c771956d835944a943 (patch)
treef4e4ac00fe2677c2aee53ab24105e2df47f21a61
parent708e71dfbfeb28c3f8c1ed37ee121320f483f808 (diff)
downloadmongo-c72c5822cf2169d78b3c76c771956d835944a943.tar.gz
TOOLS-106 mongorestore --oplogReplay searches for any valid source
-rw-r--r--common/intents/intent.go136
-rw-r--r--mongorestore/filepath.go30
-rw-r--r--mongorestore/mongorestore.go25
-rw-r--r--mongorestore/oplog.go2
-rw-r--r--mongorestore/options.go1
-rw-r--r--test/qa-tests/jstests/restore/oplog_replay_conflict.js35
-rw-r--r--test/qa-tests/jstests/restore/oplog_replay_local_main.js63
-rw-r--r--test/qa-tests/jstests/restore/oplog_replay_local_rs.js67
-rw-r--r--test/qa-tests/jstests/restore/oplog_replay_priority_oplog.js41
-rw-r--r--test/qa-tests/jstests/restore/oplog_replay_specify_file.js69
-rw-r--r--test/qa-tests/jstests/restore/testdata/dump_local_oplog/local/oplog.rs.bsonbin0 -> 9190 bytes
-rw-r--r--test/qa-tests/jstests/restore/testdata/dump_local_oplog/local/oplog.rs.metadata.json1
-rw-r--r--test/qa-tests/jstests/restore/testdata/dump_oplog_conflict/oplog.bsonbin0 -> 525 bytes
-rw-r--r--test/qa-tests/jstests/restore/testdata/extra_oplog.bsonbin0 -> 525 bytes
14 files changed, 436 insertions, 34 deletions
diff --git a/common/intents/intent.go b/common/intents/intent.go
index 74fd738ed89..c73d84a297b 100644
--- a/common/intents/intent.go
+++ b/common/intents/intent.go
@@ -43,7 +43,10 @@ func (it *Intent) Namespace() string {
}
func (it *Intent) IsOplog() bool {
- return it.DB == "" && it.C == "oplog"
+ if it.DB == "" && it.C == "oplog" {
+ return true
+ }
+ return it.DB == "local" && (it.C == "oplog.rs" || it.C == "oplog.$main")
}
func (it *Intent) IsUsers() bool {
@@ -84,6 +87,26 @@ func (intent *Intent) IsSpecialCollection() bool {
return intent.IsSystemIndexes() || intent.IsUsers() || intent.IsRoles() || intent.IsAuthVersion()
}
+func (existing *Intent) MergeIntent(intent *Intent) {
+ // merge new intent into old intent
+ if existing.BSONFile == nil {
+ existing.BSONFile = intent.BSONFile
+ }
+ if existing.Size == 0 {
+ existing.Size = intent.Size
+ }
+ if existing.Location == "" {
+ existing.Location = intent.Location
+ }
+ if existing.MetadataFile == nil {
+ existing.MetadataFile = intent.MetadataFile
+ }
+ if existing.MetadataLocation == "" {
+ existing.MetadataLocation = intent.MetadataLocation
+ }
+
+}
+
type Manager struct {
// intents are for all of the regular user created collections
intents map[string]*Intent
@@ -109,6 +132,12 @@ type Manager struct {
rolesIntent *Intent
versionIntent *Intent
indexIntents map[string]*Intent
+
+ // Tells the manager if it should choose a single oplog when multiple are provided.
+ smartPickOplog bool
+
+ // Indicates if an the manager has seen two conflicting oplogs.
+ oplogConflict bool
}
func NewIntentManager() *Manager {
@@ -118,9 +147,15 @@ func NewIntentManager() *Manager {
intentsByDiscoveryOrder: []*Intent{},
priotitizerLock: &sync.Mutex{},
indexIntents: map[string]*Intent{},
+ smartPickOplog: false,
+ oplogConflict: false,
}
}
+func (mgr *Manager) SetSmartPickOplog(smartPick bool) {
+ mgr.smartPickOplog = smartPick
+}
+
// HasConfigDBIntent returns a bool indicating if any of the intents refer to the "config" database.
// This can be used to check for possible unwanted conflicts before restoring to a sharded system.
func (mgr *Manager) HasConfigDBIntent() bool {
@@ -132,6 +167,72 @@ func (mgr *Manager) HasConfigDBIntent() bool {
return false
}
+// PutOplogIntent takes an intent for an oplog and stores it in the intent manager with the
+// provided key. If the manager has smartPickOplog enabled, then it uses a priority system
+// to determine which oplog intent to maintain as the actual oplog.
+func (manager *Manager) PutOplogIntent(intent *Intent, managerKey string) {
+ if manager.smartPickOplog {
+ if existing := manager.specialIntents[managerKey]; existing != nil {
+ existing.MergeIntent(intent)
+ return
+ }
+ if manager.oplogIntent == nil {
+ // If there is no oplog intent, make this one the oplog.
+ manager.oplogIntent = intent
+ manager.specialIntents[managerKey] = intent
+ } else if intent.DB == "" {
+ // We already have an oplog and this is a top priority oplog.
+ if manager.oplogIntent.DB == "" {
+ // If the manager's current oplog is also top priority, we have a
+ // conflict and ignore this oplog.
+ manager.oplogConflict = true
+ } else {
+ // If the manager's current oplog is lower priority, replace it and
+ // move that one to be a normal intent.
+ manager.putNormalIntent(manager.oplogIntent)
+ delete(manager.specialIntents, manager.oplogIntent.Namespace())
+ manager.oplogIntent = intent
+ manager.specialIntents[managerKey] = intent
+ }
+ } else {
+ // We already have an oplog and this is a low priority oplog.
+ if manager.oplogIntent.DB != "" {
+ // If the manager's current oplog is also low priority, set a conflict.
+ manager.oplogConflict = true
+ }
+ // No matter what, set this lower priority oplog to be a normal intent.
+ manager.putNormalIntent(intent)
+ }
+ } else {
+ if intent.DB == "" && intent.C == "oplog" {
+ // If this is a normal oplog, then add it as an oplog intent.
+ if existing := manager.specialIntents[managerKey]; existing != nil {
+ existing.MergeIntent(intent)
+ return
+ }
+ manager.oplogIntent = intent
+ manager.specialIntents[managerKey] = intent
+ } else {
+ manager.putNormalIntent(intent)
+ }
+ }
+}
+
+func (manager *Manager) putNormalIntent(intent *Intent) {
+ // BSON and metadata files for the same collection are merged
+ // into the same intent. This is done to allow for simple
+ // pairing of BSON + metadata without keeping track of the
+ // state of the filepath walker
+ if existing := manager.intents[intent.Namespace()]; existing != nil {
+ existing.MergeIntent(intent)
+ return
+ }
+
+ // if key doesn't already exist, add it to the manager
+ manager.intents[intent.Namespace()] = intent
+ manager.intentsByDiscoveryOrder = append(manager.intentsByDiscoveryOrder, intent)
+}
+
// Put inserts an intent into the manager. Intents for the same collection
// are merged together, so that BSON and metadata files for the same collection
// are returned in the same intent.
@@ -142,8 +243,7 @@ func (manager *Manager) Put(intent *Intent) {
// bucket special-case collections
if intent.IsOplog() {
- manager.oplogIntent = intent
- manager.specialIntents[intent.Namespace()] = intent
+ manager.PutOplogIntent(intent, intent.Namespace())
return
}
if intent.IsSystemIndexes() {
@@ -173,33 +273,11 @@ func (manager *Manager) Put(intent *Intent) {
return
}
- // BSON and metadata files for the same collection are merged
- // into the same intent. This is done to allow for simple
- // pairing of BSON + metadata without keeping track of the
- // state of the filepath walker
- if existing := manager.intents[intent.Namespace()]; existing != nil {
- // merge new intent into old intent
- if existing.BSONFile == nil {
- existing.BSONFile = intent.BSONFile
- }
- if existing.Size == 0 {
- existing.Size = intent.Size
- }
- if existing.Location == "" {
- existing.Location = intent.Location
- }
- if existing.MetadataFile == nil {
- existing.MetadataFile = intent.MetadataFile
- }
- if existing.MetadataLocation == "" {
- existing.MetadataLocation = intent.MetadataLocation
- }
- return
- }
+ manager.putNormalIntent(intent)
+}
- // if key doesn't already exist, add it to the manager
- manager.intents[intent.Namespace()] = intent
- manager.intentsByDiscoveryOrder = append(manager.intentsByDiscoveryOrder, intent)
+func (manager *Manager) GetOplogConflict() bool {
+ return manager.oplogConflict
}
// Intents returns a slice containing all of the intents in the manager.
diff --git a/mongorestore/filepath.go b/mongorestore/filepath.go
index 3a34a7deb31..5eaf7e32257 100644
--- a/mongorestore/filepath.go
+++ b/mongorestore/filepath.go
@@ -230,7 +230,6 @@ func (restore *MongoRestore) getInfoFromFilename(filename string) (string, FileT
// the databases and collections it finds.
func (restore *MongoRestore) CreateAllIntents(dir archive.DirLike, filterDB string, filterCollection string) error {
log.Logf(log.DebugHigh, "using %v as dump root directory", dir.Path())
- foundOplog := false
entries, err := dir.ReadDir()
if err != nil {
return fmt.Errorf("error reading root dump folder: %v", err)
@@ -253,7 +252,6 @@ func (restore *MongoRestore) CreateAllIntents(dir archive.DirLike, filterDB stri
if restore.InputOptions.OplogReplay {
log.Log(log.DebugLow, "found oplog.bson file to replay")
}
- foundOplog = true
oplogIntent := &intents.Intent{
C: "oplog",
Size: entry.Size(),
@@ -298,10 +296,34 @@ func (restore *MongoRestore) CreateAllIntents(dir archive.DirLike, filterDB stri
}
}
}
- if restore.InputOptions.OplogReplay && !foundOplog {
- return fmt.Errorf("no %v/oplog.bson file to replay; make sure you run mongodump with --oplog", dir.Path())
+ return nil
+}
+
+// CreateIntentForOplog creates an intent for a file that we want to treat as an oplog.
+func (restore *MongoRestore) CreateIntentForOplog() error {
+ target, err := newActualPath(restore.InputOptions.OplogFile)
+ db := ""
+ collection := "oplog"
+ if err != nil {
+ return err
+ }
+ log.Logf(log.DebugLow, "reading oplog from %v", target.Path())
+
+ if target.IsDir() {
+ return fmt.Errorf("file %v is a directory, not a bson file", target.Path())
}
+
+ // Then create its intent.
+ intent := &intents.Intent{
+ DB: db,
+ C: collection,
+ Size: target.Size(),
+ Location: target.Path(),
+ }
+ intent.BSONFile = &realBSONFile{path: target.Path(), intent: intent, gzip: restore.InputOptions.Gzip}
+ restore.manager.PutOplogIntent(intent, "oplogFile")
return nil
+
}
// CreateIntentsForDB drills down into the dir folder, creating intents
diff --git a/mongorestore/mongorestore.go b/mongorestore/mongorestore.go
index a905bfdeda1..928c47c02fe 100644
--- a/mongorestore/mongorestore.go
+++ b/mongorestore/mongorestore.go
@@ -118,6 +118,14 @@ func (restore *MongoRestore) ParseAndValidateOptions() error {
return fmt.Errorf("error parsing timestamp argument to --oplogLimit: %v", err)
}
}
+ if restore.InputOptions.OplogFile != "" {
+ if !restore.InputOptions.OplogReplay {
+ return fmt.Errorf("cannot use --oplogFile without --oplogReplay enabled")
+ }
+ if restore.InputOptions.Archive != "" {
+ return fmt.Errorf("cannot use --oplogFile with --archive specified")
+ }
+ }
// check if we are using a replica set and fall back to w=1 if we aren't (for <= 2.4)
nodeType, err := restore.SessionProvider.GetNodeType()
@@ -183,6 +191,9 @@ func (restore *MongoRestore) Restore() error {
// Build up all intents to be restored
restore.manager = intents.NewIntentManager()
+ if restore.InputOptions.Archive == "" && restore.InputOptions.OplogReplay {
+ restore.manager.SetSmartPickOplog(true)
+ }
if restore.InputOptions.Archive != "" {
archiveReader, err := restore.getArchiveReader()
@@ -296,6 +307,20 @@ func (restore *MongoRestore) Restore() error {
"remove the 'config' directory from the dump directory first")
}
+ if restore.InputOptions.OplogFile != "" {
+ err = restore.CreateIntentForOplog()
+ if err != nil {
+ return fmt.Errorf("error reading oplog file: %v", err)
+ }
+ }
+ if restore.InputOptions.OplogReplay && restore.manager.Oplog() == nil {
+ return fmt.Errorf("no oplog file to replay; make sure you run mongodump with --oplog")
+ }
+ if restore.manager.GetOplogConflict() {
+ return fmt.Errorf("cannot provide both an oplog.bson file and an oplog file with --oplogFile, " +
+ "nor can you provide both a local/oplog.rs.bson and a local/oplog.$main.bson file.")
+ }
+
if restore.InputOptions.Archive != "" {
namespaceChan := make(chan string, 1)
namespaceErrorChan := make(chan error)
diff --git a/mongorestore/oplog.go b/mongorestore/oplog.go
index 8c9412b47a8..2bbb6d00af1 100644
--- a/mongorestore/oplog.go
+++ b/mongorestore/oplog.go
@@ -25,7 +25,7 @@ func (restore *MongoRestore) RestoreOplog() error {
intent := restore.manager.Oplog()
if intent == nil {
// this should not be reached
- log.Log(log.Always, "no oplog.bson file in root of the dump directory, skipping oplog application")
+ log.Log(log.Always, "no oplog file provided, skipping oplog application")
return nil
}
if err := intent.BSONFile.Open(); err != nil {
diff --git a/mongorestore/options.go b/mongorestore/options.go
index 7b0d5b01c09..5063d25390e 100644
--- a/mongorestore/options.go
+++ b/mongorestore/options.go
@@ -15,6 +15,7 @@ type InputOptions struct {
Objcheck bool `long:"objcheck" description:"validate all objects before inserting"`
OplogReplay bool `long:"oplogReplay" description:"replay oplog for point-in-time restore"`
OplogLimit string `long:"oplogLimit" value-name:"<seconds>[:ordinal]" description:"only include oplog entries before the provided Timestamp"`
+ OplogFile string `long:"oplogFile" value-name:"<filename>" description:"oplog file to use for replay of oplog"`
Archive string `long:"archive" value-name:"<filename>" optional:"true" optional-value:"-" description:"restore dump from the specified archive file. If flag is specified without a value, archive is read from stdin"`
RestoreDBUsersAndRoles bool `long:"restoreDbUsersAndRoles" description:"restore user and role definitions for the given database"`
Directory string `long:"dir" value-name:"<directory-name>" description:"input directory, use '-' for stdin"`
diff --git a/test/qa-tests/jstests/restore/oplog_replay_conflict.js b/test/qa-tests/jstests/restore/oplog_replay_conflict.js
new file mode 100644
index 00000000000..9f332a3b9f7
--- /dev/null
+++ b/test/qa-tests/jstests/restore/oplog_replay_conflict.js
@@ -0,0 +1,35 @@
+/**
+ * oplog_replay_conflict.js
+ *
+ * This file tests mongorestore with --oplogReplay where the user provides two top priority
+ * oplogs and mongorestore should exit with an error.
+ */
+(function() {
+ 'use strict';
+ if (typeof getToolTest === 'undefined') {
+ load('jstests/configs/plain_28.config.js');
+ }
+
+ var commonToolArgs = getCommonToolArguments();
+ var restoreTarget = 'jstests/restore/testdata/dump_oplog_conflict';
+
+ var toolTest = getToolTest('oplog_replay_conflict');
+
+ // The test db and collections we'll be using.
+ var testDB = toolTest.db.getSiblingDB('test');
+ testDB.createCollection('data');
+ var testColl = testDB.data;
+
+ // Replay the oplog from the provided oplog
+ var ret = toolTest.runTool.apply(
+ toolTest,
+ ['restore',
+ '--oplogReplay',
+ '--oplogFile', 'jstests/restore/testdata/extra_oplog.bson',
+ restoreTarget].concat(commonToolArgs));
+
+ assert.eq(0, testColl.count(),
+ "no original entries should be restored");
+ assert.eq(1, ret, "restore operation succeeded when it shouldn't have");
+ toolTest.stop();
+}());
diff --git a/test/qa-tests/jstests/restore/oplog_replay_local_main.js b/test/qa-tests/jstests/restore/oplog_replay_local_main.js
new file mode 100644
index 00000000000..be325f94aa0
--- /dev/null
+++ b/test/qa-tests/jstests/restore/oplog_replay_local_main.js
@@ -0,0 +1,63 @@
+/**
+ * oplog_replay_local_main.js
+ *
+ * This file tests mongorestore with --oplogReplay where the oplog file is in the 'oplog.$main'
+ * collection of the 'local' database. This occurs when using master-slave replication.
+ */
+(function() {
+ 'use strict';
+
+ var dumpTarget = 'oplog_replay_local_main';
+ var rt = new ReplTest('oplog_replay_local_main');
+ var m = rt.start(true);
+ // Set the test db to 'local' and collection to 'oplog.$main' to fake a replica set oplog
+ var testDB = m.getDB('local');
+ var testColl = testDB.oplog.$main;
+ var testRestoreDB = m.getDB('test');
+ var testRestoreColl = testRestoreDB.op;
+ resetDbpath(dumpTarget);
+
+ var lastop = function() {
+ return testColl.find().sort( {$natural:-1} ).next();
+ };
+
+ var lastTS = lastop().ts.t;
+ var oplogSize = 100;
+
+ // Create a fake oplog consisting of 100 inserts.
+ for (var i = 0; i < oplogSize; i++) {
+ var op = { ts: new Timestamp(lastTS, i),
+ op: 'i',
+ o: {_id: i, x: 'a' + i},
+ 'ns': 'test.op'};
+ assert.commandWorked(testDB.runCommand({godinsert: 'oplog.$main', obj: op}));
+ }
+
+ // Dump the fake oplog.
+ var ret = runMongoProgram.apply(
+ this,
+ ['mongodump',
+ '--port', rt.ports[0],
+ '--db', 'local',
+ '-c', 'oplog.$main',
+ '--out', dumpTarget]);
+ assert.eq(0, ret, "dump operation failed");
+
+ // Create the test.op collection.
+ testRestoreColl.drop();
+ testRestoreDB.createCollection("op");
+ assert.eq(0, testRestoreColl.count());
+
+ // Replay the oplog from the provided oplog
+ ret = runMongoProgram.apply(
+ this,
+ ['mongorestore',
+ '--port', rt.ports[0],
+ '--oplogReplay',
+ dumpTarget]);
+ assert.eq(0, ret, "restore operation failed");
+
+ assert.eq(oplogSize, testRestoreColl.count(),
+ "all oplog entries should be inserted");
+ rt.stop(true);
+}());
diff --git a/test/qa-tests/jstests/restore/oplog_replay_local_rs.js b/test/qa-tests/jstests/restore/oplog_replay_local_rs.js
new file mode 100644
index 00000000000..b81d10eade0
--- /dev/null
+++ b/test/qa-tests/jstests/restore/oplog_replay_local_rs.js
@@ -0,0 +1,67 @@
+/**
+ * oplog_replay_local_rs.js
+ *
+ * This file tests mongorestore with --oplogReplay where the oplog file is in the 'oplog.rs'
+ * collection of the 'local' database. This occurs when using a replica-set for replication.
+ */
+(function() {
+ 'use strict';
+ if (typeof getToolTest === 'undefined') {
+ load('jstests/configs/plain_28.config.js');
+ }
+
+ var commonToolArgs = getCommonToolArguments();
+ var dumpTarget = 'oplog_replay_local_rs';
+
+ var toolTest = getToolTest('oplog_replay_local_rs');
+
+ // Set the test db to 'local' and collection to 'oplog.rs' to fake a replica set oplog
+ var testDB = toolTest.db.getSiblingDB('local');
+ var testColl = testDB['oplog.rs'];
+ var testRestoreDB = toolTest.db.getSiblingDB('test');
+ var testRestoreColl = testRestoreDB.op;
+ resetDbpath(dumpTarget);
+
+ var oplogSize = 100;
+ testDB.createCollection('oplog.rs', { capped: true, size: 100000 } );
+
+ // Create a fake oplog consisting of 100 inserts.
+ for (var i = 0; i < oplogSize; i++) {
+ var r = testColl.insert({ ts: new Timestamp(0, i),
+ op: "i",
+ o: { _id: i, x: 'a' + i },
+ "ns":"test.op" });
+ assert.eq(1, r.nInserted, "insert failed");
+ }
+
+ // Dump the fake oplog.
+ var ret = toolTest.runTool.apply(
+ toolTest,
+ ['dump',
+ '--db', 'local',
+ '-c', 'oplog.rs',
+ '--out', dumpTarget].concat(commonToolArgs));
+ assert.eq(0, ret, "dump operation failed");
+
+ // Dump original data.
+ testColl.drop();
+ assert.eq(0, testColl.count(), "all original entries should be dropped");
+
+
+ // Create the test.op collection.
+ testRestoreColl.drop();
+ testRestoreDB.createCollection("op");
+ assert.eq(0, testRestoreColl.count());
+
+ // Replay the oplog from the provided oplog
+ ret = toolTest.runTool.apply(
+ toolTest,
+ ['restore',
+ '--oplogReplay',
+ dumpTarget].concat(commonToolArgs));
+ assert.eq(0, ret, "restore operation failed");
+
+ assert.eq(oplogSize, testRestoreColl.count(),
+ "all oplog entries should be inserted");
+ toolTest.stop();
+}());
diff --git a/test/qa-tests/jstests/restore/oplog_replay_priority_oplog.js b/test/qa-tests/jstests/restore/oplog_replay_priority_oplog.js
new file mode 100644
index 00000000000..5317d8734be
--- /dev/null
+++ b/test/qa-tests/jstests/restore/oplog_replay_priority_oplog.js
@@ -0,0 +1,41 @@
+/**
+ * oplog_replay_priority_oplog.js
+ *
+ * This file tests mongorestore with --oplogReplay where the user provides two oplogs and
+ * mongorestore only restores the higher priority one.
+ */
+(function() {
+ 'use strict';
+ if (typeof getToolTest === 'undefined') {
+ load('jstests/configs/plain_28.config.js');
+ }
+
+ var commonToolArgs = getCommonToolArguments();
+ var restoreTarget = 'jstests/restore/testdata/dump_local_oplog';
+
+ var toolTest = getToolTest('oplog_replay_priority_oplog');
+
+ // The test db and collections we'll be using.
+ var testDB = toolTest.db.getSiblingDB('test');
+ testDB.createCollection('data');
+ var testColl = testDB.data;
+ testDB.createCollection('op');
+ var restoreColl = testDB.op;
+
+ // Replay the oplog from the provided oplog
+ var ret = toolTest.runTool.apply(
+ toolTest,
+ ['restore',
+ '--oplogReplay',
+ '--oplogFile', 'jstests/restore/testdata/extra_oplog.bson',
+ restoreTarget].concat(commonToolArgs));
+ assert.eq(0, ret, "restore operation failed");
+
+ // Extra oplog has 5 entries as explained in oplog_replay_and_limit.js
+ assert.eq(5, testColl.count(),
+ "all original entries from high priority oplog should be restored");
+ assert.eq(0, restoreColl.count(),
+ "no original entries from low priority oplog should be restored");
+ toolTest.stop();
+}());
+
diff --git a/test/qa-tests/jstests/restore/oplog_replay_specify_file.js b/test/qa-tests/jstests/restore/oplog_replay_specify_file.js
new file mode 100644
index 00000000000..f87b281d6e3
--- /dev/null
+++ b/test/qa-tests/jstests/restore/oplog_replay_specify_file.js
@@ -0,0 +1,69 @@
+/**
+ * oplog_replay_specify_file.js
+ *
+ * This file tests mongorestore with --oplogReplay where the user specifies a file with the
+ * --oplogFile flag.
+ */
+(function() {
+ 'use strict';
+ if (typeof getToolTest === 'undefined') {
+ load('jstests/configs/plain_28.config.js');
+ }
+
+ var commonToolArgs = getCommonToolArguments();
+ var dumpTarget = 'oplog_replay_specify_file';
+
+ var toolTest = getToolTest('oplog_replay_specify_file');
+
+ // The test db and collections we'll be using.
+ var testDB = toolTest.db.getSiblingDB('test_oplog');
+ var testColl = testDB.foo;
+ var testRestoreDB = toolTest.db.getSiblingDB('test');
+ var testRestoreColl = testRestoreDB.op;
+ resetDbpath(dumpTarget);
+
+ var oplogSize = 100;
+
+ // Create a fake oplog consisting of 100 inserts.
+ for (var i = 0; i < oplogSize; i++) {
+ testColl.insert({ ts: new Timestamp(0,i),
+ op: "i",
+ o: { _id: i, x: 'a' + i },
+ "ns":"test.op" });
+ }
+
+ // Dump the fake oplog.
+ var ret = toolTest.runTool.apply(
+ toolTest,
+ ['dump',
+ '--db', 'test_oplog',
+ '-c', 'foo',
+ '--out', dumpTarget].concat(commonToolArgs));
+ assert.eq(0, ret, "dump operation failed");
+
+ // Dump original data.
+ testColl.drop();
+ assert.eq(0, testColl.count(),
+ "all original entries should be dropped");
+
+
+ // Create the test.op collection.
+ testRestoreColl.drop();
+ testRestoreDB.createCollection("op");
+ assert.eq(0, testRestoreColl.count());
+
+ // Replay the oplog from the provided oplog
+ ret = toolTest.runTool.apply(
+ toolTest,
+ ['restore',
+ '--oplogReplay',
+ '--oplogFile', dumpTarget + '/test_oplog/foo.bson',
+ dumpTarget].concat(commonToolArgs));
+ assert.eq(0, ret, "restore operation failed");
+
+ assert.eq(oplogSize, testRestoreColl.count(),
+ "all oplog entries should be inserted");
+ assert.eq(oplogSize, testColl.count(),
+ "all original entries should be restored");
+ toolTest.stop();
+}());
diff --git a/test/qa-tests/jstests/restore/testdata/dump_local_oplog/local/oplog.rs.bson b/test/qa-tests/jstests/restore/testdata/dump_local_oplog/local/oplog.rs.bson
new file mode 100644
index 00000000000..6051944948e
--- /dev/null
+++ b/test/qa-tests/jstests/restore/testdata/dump_local_oplog/local/oplog.rs.bson
Binary files differ
diff --git a/test/qa-tests/jstests/restore/testdata/dump_local_oplog/local/oplog.rs.metadata.json b/test/qa-tests/jstests/restore/testdata/dump_local_oplog/local/oplog.rs.metadata.json
new file mode 100644
index 00000000000..9e28c8db056
--- /dev/null
+++ b/test/qa-tests/jstests/restore/testdata/dump_local_oplog/local/oplog.rs.metadata.json
@@ -0,0 +1 @@
+{"options":{"capped":true,"size":100096},"indexes":[]} \ No newline at end of file
diff --git a/test/qa-tests/jstests/restore/testdata/dump_oplog_conflict/oplog.bson b/test/qa-tests/jstests/restore/testdata/dump_oplog_conflict/oplog.bson
new file mode 100644
index 00000000000..a9ada58715f
--- /dev/null
+++ b/test/qa-tests/jstests/restore/testdata/dump_oplog_conflict/oplog.bson
Binary files differ
diff --git a/test/qa-tests/jstests/restore/testdata/extra_oplog.bson b/test/qa-tests/jstests/restore/testdata/extra_oplog.bson
new file mode 100644
index 00000000000..a9ada58715f
--- /dev/null
+++ b/test/qa-tests/jstests/restore/testdata/extra_oplog.bson
Binary files differ