summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDavid Golden <xdg@xdg.me>2018-05-11 12:22:02 -0400
committerDavid Golden <xdg@xdg.me>2018-05-11 12:22:02 -0400
commit56cbcf28c6bd4d9f9e739fb834d83b61c99516f1 (patch)
treea276b5c489cefbca00fb15293d48756df5ea4241 /src
parent737596d7005ae061809cbd8483ba91b4a2a89d86 (diff)
downloadmongo-56cbcf28c6bd4d9f9e739fb834d83b61c99516f1.tar.gz
Import tools: 0373beacadd1b314e2da616acefc5804889eb92c from branch v4.0
ref: 7b16532d46..0373beacad for: 3.7.10 TOOLS-1817 Add 'preserveUUIDs' flag to restore collections with existing UUIDS TOOLS-2002 mongorestore --oplogReplay failed on createIndexes command TOOLS-2033 bsondump_broken_pipe.js fails intermittently in Evergreen
Diffstat (limited to 'src')
-rw-r--r--src/mongo/gotools/bsondump/bsondump.go13
-rw-r--r--src/mongo/gotools/common/db/command.go25
-rw-r--r--src/mongo/gotools/common/db/db.go4
-rw-r--r--src/mongo/gotools/common/failpoint/failpoints.go1
-rw-r--r--src/mongo/gotools/common/util/mongo.go24
-rw-r--r--src/mongo/gotools/import.data4
-rw-r--r--src/mongo/gotools/mongorestore/metadata.go75
-rw-r--r--src/mongo/gotools/mongorestore/mongorestore.go14
-rw-r--r--src/mongo/gotools/mongorestore/mongorestore_test.go163
-rw-r--r--src/mongo/gotools/mongorestore/oplog.go62
-rw-r--r--src/mongo/gotools/mongorestore/oplog_test.go50
-rw-r--r--src/mongo/gotools/mongorestore/options.go1
-rw-r--r--src/mongo/gotools/mongorestore/restore.go21
13 files changed, 411 insertions, 46 deletions
diff --git a/src/mongo/gotools/bsondump/bsondump.go b/src/mongo/gotools/bsondump/bsondump.go
index 96d0baef180..3d26d5d6a5e 100644
--- a/src/mongo/gotools/bsondump/bsondump.go
+++ b/src/mongo/gotools/bsondump/bsondump.go
@@ -10,16 +10,19 @@ package bsondump
import (
"bytes"
"fmt"
+ "io"
+ "os"
+ "strings"
+ "time"
+
"github.com/mongodb/mongo-tools/common/bsonutil"
"github.com/mongodb/mongo-tools/common/db"
+ "github.com/mongodb/mongo-tools/common/failpoint"
"github.com/mongodb/mongo-tools/common/json"
"github.com/mongodb/mongo-tools/common/log"
"github.com/mongodb/mongo-tools/common/options"
"github.com/mongodb/mongo-tools/common/util"
"gopkg.in/mgo.v2/bson"
- "io"
- "os"
- "strings"
)
// BSONDump is a container for the user-specified options and
@@ -130,6 +133,10 @@ func (bd *BSONDump) JSON() (int, error) {
}
}
numFound++
+ if failpoint.Enabled(failpoint.SlowBSONDump) {
+ time.Sleep(2 * time.Second)
+ }
+
}
if err := decodedStream.Err(); err != nil {
return numFound, err
diff --git a/src/mongo/gotools/common/db/command.go b/src/mongo/gotools/common/db/command.go
index 62fa834309b..d427b3b431b 100644
--- a/src/mongo/gotools/common/db/command.go
+++ b/src/mongo/gotools/common/db/command.go
@@ -8,9 +8,10 @@ package db
import (
"fmt"
+ "strings"
+
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
- "strings"
)
// Query flags
@@ -131,6 +132,28 @@ func (sp *SessionProvider) IsMongos() (bool, error) {
return nodeType == Mongos, nil
}
+// SupportsCollectionUUID returns true if the connected server identifies
+// collections with UUIDs
+func (sp *SessionProvider) SupportsCollectionUUID() (bool, error) {
+ session, err := sp.GetSession()
+ if err != nil {
+ return false, err
+ }
+ defer session.Close()
+
+ collInfo, err := GetCollectionInfo(session.DB("admin").C("system.version"))
+ if err != nil {
+ return false, err
+ }
+
+ // On FCV 3.6+, admin.system.version will have a UUID
+ if collInfo != nil && collInfo.GetUUID() != "" {
+ return true, nil
+ }
+
+ return false, nil
+}
+
// SupportsRepairCursor takes in an example db and collection name and
// returns true if the connected server supports the repairCursor command.
// It returns false and the error that occurred if it is not supported.
diff --git a/src/mongo/gotools/common/db/db.go b/src/mongo/gotools/common/db/db.go
index 888556b97ea..839db12d491 100644
--- a/src/mongo/gotools/common/db/db.go
+++ b/src/mongo/gotools/common/db/db.go
@@ -94,8 +94,8 @@ type Oplog struct {
Version int `bson:"v"`
Operation string `bson:"op"`
Namespace string `bson:"ns"`
- Object bson.RawD `bson:"o"`
- Query bson.RawD `bson:"o2"`
+ Object bson.D `bson:"o"`
+ Query bson.D `bson:"o2"`
UI *bson.Binary `bson:"ui,omitempty"`
}
diff --git a/src/mongo/gotools/common/failpoint/failpoints.go b/src/mongo/gotools/common/failpoint/failpoints.go
index be9aa752f10..0258ca1d2a0 100644
--- a/src/mongo/gotools/common/failpoint/failpoints.go
+++ b/src/mongo/gotools/common/failpoint/failpoints.go
@@ -9,4 +9,5 @@ package failpoint
// Supported failpoint names
const (
PauseBeforeDumping = "PauseBeforeDumping"
+ SlowBSONDump = "SlowBSONDump"
)
diff --git a/src/mongo/gotools/common/util/mongo.go b/src/mongo/gotools/common/util/mongo.go
index 80b5945d5e8..cf3168d2b68 100644
--- a/src/mongo/gotools/common/util/mongo.go
+++ b/src/mongo/gotools/common/util/mongo.go
@@ -62,14 +62,8 @@ func CreateConnectionAddrs(host, port string) []string {
}
// SplitNamespace splits a namespace path into a database and collection,
-// returned in that order. An error is returned if the namespace is invalid.
-func SplitAndValidateNamespace(namespace string) (string, string, error) {
-
- // first, run validation checks
- if err := ValidateFullNamespace(namespace); err != nil {
- return "", "", fmt.Errorf("namespace '%v' is not valid: %v",
- namespace, err)
- }
+// returned in that order.
+func SplitNamespace(namespace string) (string, string) {
// find the first instance of "." in the namespace
firstDotIndex := strings.Index(namespace, ".")
@@ -84,6 +78,20 @@ func SplitAndValidateNamespace(namespace string) (string, string, error) {
database = namespace
}
+ return database, collection
+}
+
+// SplitAndValidateNamespace splits a namespace path into a database and collection,
+// returned in that order. An error is returned if the namespace is invalid.
+func SplitAndValidateNamespace(namespace string) (string, string, error) {
+
+ // first, run validation checks
+ if err := ValidateFullNamespace(namespace); err != nil {
+ return "", "", fmt.Errorf("namespace '%v' is not valid: %v",
+ namespace, err)
+ }
+
+ database, collection := SplitNamespace(namespace)
return database, collection, nil
}
diff --git a/src/mongo/gotools/import.data b/src/mongo/gotools/import.data
index 19d8adae1e2..d4e8508aab4 100644
--- a/src/mongo/gotools/import.data
+++ b/src/mongo/gotools/import.data
@@ -1,6 +1,6 @@
{
- "commit": "7b16532d4653bcfcb5b3c919c7610cca8035af04",
+ "commit": "0373beacadd1b314e2da616acefc5804889eb92c",
"github": "mongodb/mongo-tools.git",
"vendor": "tools",
- "branch": "master"
+ "branch": "v4.0"
}
diff --git a/src/mongo/gotools/mongorestore/metadata.go b/src/mongo/gotools/mongorestore/metadata.go
index 12d99d858c0..7e644f3c047 100644
--- a/src/mongo/gotools/mongorestore/metadata.go
+++ b/src/mongo/gotools/mongorestore/metadata.go
@@ -7,6 +7,7 @@
package mongorestore
import (
+ "encoding/hex"
"fmt"
"github.com/mongodb/mongo-tools/common"
@@ -38,6 +39,7 @@ type authVersionPair struct {
type Metadata struct {
Options bson.D `json:"options,omitempty"`
Indexes []IndexDocument `json:"indexes"`
+ UUID string `json:"uuid"`
}
// this struct is used to read in the options of a set of indexes
@@ -54,17 +56,17 @@ type IndexDocument struct {
// MetadataFromJSON takes a slice of JSON bytes and unmarshals them into usable
// collection options and indexes for restoring collections.
-func (restore *MongoRestore) MetadataFromJSON(jsonBytes []byte) (bson.D, []IndexDocument, error) {
+func (restore *MongoRestore) MetadataFromJSON(jsonBytes []byte) (*Metadata, error) {
if len(jsonBytes) == 0 {
// skip metadata parsing if the file is empty
- return nil, nil, nil
+ return nil, nil
}
meta := &Metadata{}
err := json.Unmarshal(jsonBytes, meta)
if err != nil {
- return nil, nil, err
+ return nil, err
}
// first get the ordered key information for each index,
@@ -72,7 +74,7 @@ func (restore *MongoRestore) MetadataFromJSON(jsonBytes []byte) (bson.D, []Index
metaAsMap := metaDataMapIndex{}
err = json.Unmarshal(jsonBytes, &metaAsMap)
if err != nil {
- return nil, nil, fmt.Errorf("error unmarshalling metadata as map: %v", err)
+ return nil, fmt.Errorf("error unmarshalling metadata as map: %v", err)
}
for i := range meta.Indexes {
// remove "key", and "partialFilterExpression" from the map so we can decode them properly later
@@ -82,21 +84,21 @@ func (restore *MongoRestore) MetadataFromJSON(jsonBytes []byte) (bson.D, []Index
// parse extra index fields
meta.Indexes[i].Options = metaAsMap.Indexes[i]
if err := bsonutil.ConvertJSONDocumentToBSON(meta.Indexes[i].Options); err != nil {
- return nil, nil, fmt.Errorf("extended json error: %v", err)
+ return nil, fmt.Errorf("extended json error: %v", err)
}
// parse the values of the index keys, so we can support extended json
for pos, field := range meta.Indexes[i].Key {
meta.Indexes[i].Key[pos].Value, err = bsonutil.ParseJSONValue(field.Value)
if err != nil {
- return nil, nil, fmt.Errorf("extended json in 'key.%v' field: %v", field.Name, err)
+ return nil, fmt.Errorf("extended json in 'key.%v' field: %v", field.Name, err)
}
}
// parse the values of the index keys, so we can support extended json
for pos, field := range meta.Indexes[i].PartialFilterExpression {
meta.Indexes[i].PartialFilterExpression[pos].Value, err = bsonutil.ParseJSONValue(field.Value)
if err != nil {
- return nil, nil, fmt.Errorf("extended json in 'partialFilterExpression.%v' field: %v", field.Name, err)
+ return nil, fmt.Errorf("extended json in 'partialFilterExpression.%v' field: %v", field.Name, err)
}
}
}
@@ -104,10 +106,10 @@ func (restore *MongoRestore) MetadataFromJSON(jsonBytes []byte) (bson.D, []Index
// parse the values of options fields, to support extended json
meta.Options, err = bsonutil.GetExtendedBsonD(meta.Options)
if err != nil {
- return nil, nil, fmt.Errorf("extended json in 'options': %v", err)
+ return nil, fmt.Errorf("extended json in 'options': %v", err)
}
- return meta.Options, meta.Indexes, nil
+ return meta, nil
}
// LoadIndexesFromBSON reads indexes from the index BSON files and
@@ -258,17 +260,27 @@ func (restore *MongoRestore) LegacyInsertIndex(intent *intents.Intent, index Ind
// CreateCollection creates the collection specified in the intent with the
// given options.
-func (restore *MongoRestore) CreateCollection(intent *intents.Intent, options bson.D) error {
- command := append(bson.D{{"create", intent.C}}, options...)
-
+func (restore *MongoRestore) CreateCollection(intent *intents.Intent, options bson.D, uuid string) error {
session, err := restore.SessionProvider.GetSession()
if err != nil {
return fmt.Errorf("error establishing connection: %v", err)
}
defer session.Close()
+ switch {
+
+ case uuid != "":
+ return restore.createCollectionWithApplyOps(session, intent, options, uuid)
+ default:
+ return restore.createCollectionWithCommand(session, intent, options)
+ }
+
+}
+
+func (restore *MongoRestore) createCollectionWithCommand(session *mgo.Session, intent *intents.Intent, options bson.D) error {
+ command := createCollectionCommand(intent, options)
res := bson.M{}
- err = session.DB(intent.DB).Run(command, &res)
+ err := session.DB(intent.DB).Run(command, &res)
if err != nil {
return fmt.Errorf("error running create command: %v", err)
}
@@ -276,6 +288,43 @@ func (restore *MongoRestore) CreateCollection(intent *intents.Intent, options bs
return fmt.Errorf("create command: %v", res["errmsg"])
}
return nil
+
+}
+
+func (restore *MongoRestore) createCollectionWithApplyOps(session *mgo.Session, intent *intents.Intent, options bson.D, uuidHex string) error {
+ command := createCollectionCommand(intent, options)
+ uuid, err := hex.DecodeString(uuidHex)
+ if err != nil {
+ return fmt.Errorf("Couldn't restore UUID because UUID was invalid: %s", err)
+ }
+
+ b, err := bson.Marshal(command)
+ if err != nil {
+ return err
+ }
+ var rawCommand bson.RawD
+ err = bson.Unmarshal(b, &rawCommand)
+ if err != nil {
+ return err
+ }
+
+ createOp := struct {
+ Operation string `bson:"op"`
+ Namespace string `bson:"ns"`
+ Object bson.RawD `bson:"o"`
+ UI *bson.Binary `bson:"ui,omitempty"`
+ }{
+ Operation: "c",
+ Namespace: intent.DB + ".$cmd",
+ Object: rawCommand,
+ UI: &bson.Binary{Kind: 0x04, Data: uuid},
+ }
+
+ return restore.ApplyOps(session, []interface{}{createOp})
+}
+
+func createCollectionCommand(intent *intents.Intent, options bson.D) bson.D {
+ return append(bson.D{{"create", intent.C}}, options...)
}
// RestoreUsersOrRoles accepts a users intent and a roles intent, and restores
diff --git a/src/mongo/gotools/mongorestore/mongorestore.go b/src/mongo/gotools/mongorestore/mongorestore.go
index ad5bc33b327..bab85e31277 100644
--- a/src/mongo/gotools/mongorestore/mongorestore.go
+++ b/src/mongo/gotools/mongorestore/mongorestore.go
@@ -226,6 +226,20 @@ func (restore *MongoRestore) ParseAndValidateOptions() error {
"cannot specify a negative number of insertion workers per collection")
}
+ if restore.OutputOptions.PreserveUUID {
+ if !restore.OutputOptions.Drop {
+ return fmt.Errorf("cannot specify --preserveUUID without --drop")
+ }
+
+ ok, err := restore.SessionProvider.SupportsCollectionUUID()
+ if err != nil {
+ return err
+ }
+ if !ok {
+ return fmt.Errorf("target host does not support --preserveUUID")
+ }
+ }
+
// a single dash signals reading from stdin
if restore.TargetDirectory == "-" {
if restore.InputOptions.Archive != "" {
diff --git a/src/mongo/gotools/mongorestore/mongorestore_test.go b/src/mongo/gotools/mongorestore/mongorestore_test.go
index 0bafb739c6b..b2a312c005c 100644
--- a/src/mongo/gotools/mongorestore/mongorestore_test.go
+++ b/src/mongo/gotools/mongorestore/mongorestore_test.go
@@ -86,3 +86,166 @@ func TestMongorestore(t *testing.T) {
})
}
+
+func TestMongorestoreCantPreserveUUID(t *testing.T) {
+ testutil.VerifyTestType(t, testutil.IntegrationTestType)
+ session, err := testutil.GetBareSession()
+ if err != nil {
+ t.Fatalf("No server available")
+ }
+ defer session.Close()
+ fcv := testutil.GetFCV(session)
+ if cmp, err := testutil.CompareFCV(fcv, "3.6"); err != nil || cmp >= 0 {
+ t.Skip("Requires server with FCV less than 3.6")
+ }
+
+ Convey("With a test MongoRestore", t, func() {
+ nsOptions := &NSOptions{}
+ provider, toolOpts, err := testutil.GetBareSessionProvider()
+ if err != nil {
+ log.Logvf(log.Always, "error connecting to host: %v", err)
+ os.Exit(util.ExitError)
+ }
+
+ Convey("PreserveUUID restore with incompatible destination FCV errors", func() {
+ inputOptions := &InputOptions{}
+ outputOptions := &OutputOptions{
+ NumParallelCollections: 1,
+ NumInsertionWorkers: 1,
+ PreserveUUID: true,
+ Drop: true,
+ }
+ restore := MongoRestore{
+ ToolOptions: toolOpts,
+ OutputOptions: outputOptions,
+ InputOptions: inputOptions,
+ NSOptions: nsOptions,
+ SessionProvider: provider,
+ }
+ restore.TargetDirectory = "testdata/oplogdump"
+ err = restore.Restore()
+ So(err, ShouldNotBeNil)
+ So(err.Error(), ShouldContainSubstring, "target host does not support --preserveUUID")
+ })
+ })
+}
+
+func TestMongorestorePreserveUUID(t *testing.T) {
+ testutil.VerifyTestType(t, testutil.IntegrationTestType)
+ session, err := testutil.GetBareSession()
+ if err != nil {
+ t.Fatalf("No server available")
+ }
+ defer session.Close()
+ fcv := testutil.GetFCV(session)
+ if cmp, err := testutil.CompareFCV(fcv, "3.6"); err != nil || cmp < 0 {
+ t.Skip("Requires server with FCV 3.6 or later")
+ }
+
+ // From mongorestore/testdata/oplogdump/db1/c1.metadata.json
+ originalUUID := "699f503df64b4aa8a484a8052046fa3a"
+
+ Convey("With a test MongoRestore", t, func() {
+ nsOptions := &NSOptions{}
+ provider, toolOpts, err := testutil.GetBareSessionProvider()
+ if err != nil {
+ log.Logvf(log.Always, "error connecting to host: %v", err)
+ os.Exit(util.ExitError)
+ }
+
+ c1 := session.DB("db1").C("c1")
+ c1.DropCollection()
+
+ Convey("normal restore gives new UUID", func() {
+ inputOptions := &InputOptions{}
+ outputOptions := &OutputOptions{
+ NumParallelCollections: 1,
+ NumInsertionWorkers: 1,
+ }
+ restore := MongoRestore{
+ ToolOptions: toolOpts,
+ OutputOptions: outputOptions,
+ InputOptions: inputOptions,
+ NSOptions: nsOptions,
+ SessionProvider: provider,
+ }
+ restore.TargetDirectory = "testdata/oplogdump"
+ err = restore.Restore()
+ So(err, ShouldBeNil)
+ count, err := c1.Count()
+ So(err, ShouldBeNil)
+ So(count, ShouldEqual, 5)
+ info, err := db.GetCollectionInfo(c1)
+ So(err, ShouldBeNil)
+ So(info.GetUUID(), ShouldNotEqual, originalUUID)
+ })
+
+ Convey("PreserveUUID restore without drop errors", func() {
+ inputOptions := &InputOptions{}
+ outputOptions := &OutputOptions{
+ NumParallelCollections: 1,
+ NumInsertionWorkers: 1,
+ PreserveUUID: true,
+ }
+ restore := MongoRestore{
+ ToolOptions: toolOpts,
+ OutputOptions: outputOptions,
+ InputOptions: inputOptions,
+ NSOptions: nsOptions,
+ SessionProvider: provider,
+ }
+ restore.TargetDirectory = "testdata/oplogdump"
+ err = restore.Restore()
+ So(err, ShouldNotBeNil)
+ So(err.Error(), ShouldContainSubstring, "cannot specify --preserveUUID without --drop")
+ })
+
+ Convey("PreserveUUID with drop preserves UUID", func() {
+ inputOptions := &InputOptions{}
+ outputOptions := &OutputOptions{
+ NumParallelCollections: 1,
+ NumInsertionWorkers: 1,
+ PreserveUUID: true,
+ Drop: true,
+ }
+ restore := MongoRestore{
+ ToolOptions: toolOpts,
+ OutputOptions: outputOptions,
+ InputOptions: inputOptions,
+ NSOptions: nsOptions,
+ SessionProvider: provider,
+ }
+ restore.TargetDirectory = "testdata/oplogdump"
+ err = restore.Restore()
+ So(err, ShouldBeNil)
+ count, err := c1.Count()
+ So(err, ShouldBeNil)
+ So(count, ShouldEqual, 5)
+ info, err := db.GetCollectionInfo(c1)
+ So(err, ShouldBeNil)
+ So(info.GetUUID(), ShouldEqual, originalUUID)
+ })
+
+ Convey("PreserveUUID on a file without UUID metadata errors", func() {
+ inputOptions := &InputOptions{}
+ outputOptions := &OutputOptions{
+ NumParallelCollections: 1,
+ NumInsertionWorkers: 1,
+ PreserveUUID: true,
+ Drop: true,
+ }
+ restore := MongoRestore{
+ ToolOptions: toolOpts,
+ OutputOptions: outputOptions,
+ InputOptions: inputOptions,
+ NSOptions: nsOptions,
+ SessionProvider: provider,
+ }
+ restore.TargetDirectory = "testdata/testdirs"
+ err = restore.Restore()
+ So(err, ShouldNotBeNil)
+ So(err.Error(), ShouldContainSubstring, "--preserveUUID used but no UUID found")
+ })
+
+ })
+}
diff --git a/src/mongo/gotools/mongorestore/oplog.go b/src/mongo/gotools/mongorestore/oplog.go
index 94475f7dc62..00746cb8d15 100644
--- a/src/mongo/gotools/mongorestore/oplog.go
+++ b/src/mongo/gotools/mongorestore/oplog.go
@@ -87,8 +87,7 @@ func (restore *MongoRestore) RestoreOplog() error {
break
}
- // TODO: TOOLS-1817 will add support for conditionally keeping UUIDS
- entryAsOplog, err = filterUUIDs(entryAsOplog)
+ entryAsOplog, err = restore.filterUUIDs(entryAsOplog)
if err != nil {
return fmt.Errorf("error filtering UUIDs from oplog: %v", err)
}
@@ -168,13 +167,22 @@ func ParseTimestampFlag(ts string) (bson.MongoTimestamp, error) {
}
// filterUUIDs removes 'ui' entries from ops, including nested applyOps ops.
-func filterUUIDs(op db.Oplog) (db.Oplog, error) {
+// It also modifies ops that rely on 'ui'.
+func (restore *MongoRestore) filterUUIDs(op db.Oplog) (db.Oplog, error) {
// Remove UUIDs from oplog entries
- op.UI = nil
+ if !restore.OutputOptions.PreserveUUID {
+ op.UI = nil
+
+ // new createIndexes oplog command requires 'ui', so if we aren't
+ // preserving UUIDs, we must convert it to an old style index insert
+ if op.Operation == "c" && op.Object[0].Name == "createIndexes" {
+ return convertCreateIndexToIndexInsert(op)
+ }
+ }
// Check for and filter nested applyOps ops
if op.Operation == "c" && isApplyOpsCmd(op.Object) {
- filtered, err := newFilteredApplyOps(op.Object)
+ filtered, err := restore.newFilteredApplyOps(op.Object)
if err != nil {
return db.Oplog{}, err
}
@@ -184,8 +192,36 @@ func filterUUIDs(op db.Oplog) (db.Oplog, error) {
return op, nil
}
+// convertCreateIndexToIndexInsert converts from new-style create indexes
+// command to old style special index insert.
+func convertCreateIndexToIndexInsert(op db.Oplog) (db.Oplog, error) {
+ dbName, _ := util.SplitNamespace(op.Namespace)
+
+ cmdValue := op.Object[0].Value
+ collName, ok := cmdValue.(string)
+ if !ok {
+ return db.Oplog{}, fmt.Errorf("unknown format for createIndexes")
+ }
+
+ indexSpec := op.Object[1:]
+ if len(indexSpec) < 3 {
+ return db.Oplog{}, fmt.Errorf("unknown format for createIndexes, index spec " +
+ "must have at least \"v\", \"key\", and \"name\" fields")
+ }
+
+ // createIndexes does not include the "ns" field but index inserts
+ // do. Add it as the third field, after "v", "key", and "name".
+ ns := bson.D{{"ns", fmt.Sprintf("%s.%s", dbName, collName)}}
+ indexSpec = append(indexSpec[:3], append(ns, indexSpec[3:]...)...)
+ op.Object = indexSpec
+ op.Namespace = fmt.Sprintf("%s.system.indexes", dbName)
+ op.Operation = "i"
+
+ return op, nil
+}
+
// isApplyOpsCmd returns true if a document seems to be an applyOps command.
-func isApplyOpsCmd(cmd bson.RawD) bool {
+func isApplyOpsCmd(cmd bson.D) bool {
for _, v := range cmd {
if v.Name == "applyOps" {
return true
@@ -196,7 +232,7 @@ func isApplyOpsCmd(cmd bson.RawD) bool {
// newFilteredApplyOps iterates over nested ops in an applyOps document and
// returns a new applyOps document that omits the 'ui' field from nested ops.
-func newFilteredApplyOps(cmd bson.RawD) (bson.RawD, error) {
+func (restore *MongoRestore) newFilteredApplyOps(cmd bson.D) (bson.D, error) {
ops, err := unwrapNestedApplyOps(cmd)
if err != nil {
return nil, err
@@ -204,7 +240,7 @@ func newFilteredApplyOps(cmd bson.RawD) (bson.RawD, error) {
filtered := make([]db.Oplog, len(ops))
for i, v := range ops {
- filtered[i], err = filterUUIDs(v)
+ filtered[i], err = restore.filterUUIDs(v)
if err != nil {
return nil, err
}
@@ -223,10 +259,10 @@ type nestedApplyOps struct {
ApplyOps []db.Oplog `bson:"applyOps"`
}
-// unwrapNestedApplyOps converts a RawD to a typed data structure.
+// unwrapNestedApplyOps converts a bson.D to a typed data structure.
// Unfortunately, we're forced to convert by marshaling to bytes and
// unmarshaling.
-func unwrapNestedApplyOps(doc bson.RawD) ([]db.Oplog, error) {
+func unwrapNestedApplyOps(doc bson.D) ([]db.Oplog, error) {
// Doc to bytes
bs, err := bson.Marshal(doc)
if err != nil {
@@ -243,10 +279,10 @@ func unwrapNestedApplyOps(doc bson.RawD) ([]db.Oplog, error) {
return cmd.ApplyOps, nil
}
-// wrapNestedApplyOps converts a typed data structure to a RawD.
+// wrapNestedApplyOps converts a typed data structure to a bson.D.
// Unfortunately, we're forced to convert by marshaling to bytes and
// unmarshaling.
-func wrapNestedApplyOps(ops []db.Oplog) (bson.RawD, error) {
+func wrapNestedApplyOps(ops []db.Oplog) (bson.D, error) {
cmd := &nestedApplyOps{ApplyOps: ops}
// Typed data to bytes
@@ -256,7 +292,7 @@ func wrapNestedApplyOps(ops []db.Oplog) (bson.RawD, error) {
}
// Bytes to doc
- var doc bson.RawD
+ var doc bson.D
err = bson.Unmarshal(raw, &doc)
if err != nil {
return nil, fmt.Errorf("cannot reunmarshal nested applyOps op: %s", err)
diff --git a/src/mongo/gotools/mongorestore/oplog_test.go b/src/mongo/gotools/mongorestore/oplog_test.go
index fda9ebcc1c9..cfb52bdbf0a 100644
--- a/src/mongo/gotools/mongorestore/oplog_test.go
+++ b/src/mongo/gotools/mongorestore/oplog_test.go
@@ -131,6 +131,17 @@ func TestOplogRestore(t *testing.T) {
t.Fatalf("No server available")
}
+ session, err := testutil.GetBareSession()
+ if err != nil {
+ t.Fatalf("No server available")
+ }
+ defer session.Close()
+ fcv := testutil.GetFCV(session)
+ var shouldPreserveUUID bool
+ if cmp, err := testutil.CompareFCV(fcv, "3.6"); err != nil || cmp >= 0 {
+ shouldPreserveUUID = true
+ }
+
Convey("With a test MongoRestore", t, func() {
inputOptions := &InputOptions{
Directory: "testdata/oplogdump",
@@ -140,6 +151,7 @@ func TestOplogRestore(t *testing.T) {
NumParallelCollections: 1,
NumInsertionWorkers: 1,
Drop: true,
+ PreserveUUID: shouldPreserveUUID,
}
nsOptions := &NSOptions{}
provider, toolOpts, err := testutil.GetBareSessionProvider()
@@ -170,3 +182,41 @@ func TestOplogRestore(t *testing.T) {
So(count, ShouldEqual, 10)
})
}
+
+func TestOplogRestoreTools2002(t *testing.T) {
+ testutil.VerifyTestType(t, testutil.IntegrationTestType)
+ _, err := testutil.GetBareSession()
+ if err != nil {
+ t.Fatalf("No server available")
+ }
+
+ Convey("With a test MongoRestore", t, func() {
+ inputOptions := &InputOptions{
+ Directory: "testdata/tools-2002",
+ OplogReplay: true,
+ }
+ outputOptions := &OutputOptions{
+ NumParallelCollections: 1,
+ NumInsertionWorkers: 1,
+ Drop: true,
+ }
+ nsOptions := &NSOptions{}
+ provider, toolOpts, err := testutil.GetBareSessionProvider()
+ if err != nil {
+ log.Logvf(log.Always, "error connecting to host: %v", err)
+ os.Exit(util.ExitError)
+ }
+ restore := MongoRestore{
+ ToolOptions: toolOpts,
+ OutputOptions: outputOptions,
+ InputOptions: inputOptions,
+ NSOptions: nsOptions,
+ SessionProvider: provider,
+ TargetDirectory: inputOptions.Directory,
+ }
+
+ // Run mongorestore
+ err = restore.Restore()
+ So(err, ShouldBeNil)
+ })
+}
diff --git a/src/mongo/gotools/mongorestore/options.go b/src/mongo/gotools/mongorestore/options.go
index 20f57080042..4c2c28463a8 100644
--- a/src/mongo/gotools/mongorestore/options.go
+++ b/src/mongo/gotools/mongorestore/options.go
@@ -49,6 +49,7 @@ type OutputOptions struct {
NumInsertionWorkers int `long:"numInsertionWorkersPerCollection" description:"number of insert operations to run concurrently per collection (1 by default)" default:"1" default-mask:"-"`
StopOnError bool `long:"stopOnError" description:"stop restoring if an error is encountered on insert (off by default)"`
BypassDocumentValidation bool `long:"bypassDocumentValidation" description:"bypass document validation"`
+ PreserveUUID bool `long:"preserveUUID" description:"preserve original collection UUIDs (off by default, requires drop)"`
TempUsersColl string `long:"tempUsersColl" default:"tempusers" hidden:"true"`
TempRolesColl string `long:"tempRolesColl" default:"temproles" hidden:"true"`
BulkBufferSize int `long:"batchSize" default:"1000" hidden:"true"`
diff --git a/src/mongo/gotools/mongorestore/restore.go b/src/mongo/gotools/mongorestore/restore.go
index fda9c16f9a0..a0175807d37 100644
--- a/src/mongo/gotools/mongorestore/restore.go
+++ b/src/mongo/gotools/mongorestore/restore.go
@@ -117,6 +117,7 @@ func (restore *MongoRestore) RestoreIntent(intent *intents.Intent) error {
var options bson.D
var indexes []IndexDocument
+ var uuid string
// get indexes from system.indexes dump if we have it but don't have metadata files
if intent.MetadataFile == nil {
@@ -138,14 +139,24 @@ func (restore *MongoRestore) RestoreIntent(intent *intents.Intent) error {
defer intent.MetadataFile.Close()
log.Logvf(log.Always, "reading metadata for %v from %v", intent.Namespace(), intent.MetadataLocation)
- metadata, err := ioutil.ReadAll(intent.MetadataFile)
+ metadataJSON, err := ioutil.ReadAll(intent.MetadataFile)
if err != nil {
return fmt.Errorf("error reading metadata from %v: %v", intent.MetadataLocation, err)
}
- options, indexes, err = restore.MetadataFromJSON(metadata)
+ metadata, err := restore.MetadataFromJSON(metadataJSON)
if err != nil {
return fmt.Errorf("error parsing metadata from %v: %v", intent.MetadataLocation, err)
}
+ if metadata != nil {
+ options = metadata.Options
+ indexes = metadata.Indexes
+ if restore.OutputOptions.PreserveUUID {
+ if metadata.UUID == "" {
+ return fmt.Errorf("--preserveUUID used but no UUID found in %v", intent.MetadataLocation)
+ }
+ uuid = metadata.UUID
+ }
+ }
// The only way to specify options on the idIndex is at collection creation time.
// This loop pulls out the idIndex from `indexes` and sets it in `options`.
@@ -153,7 +164,9 @@ func (restore *MongoRestore) RestoreIntent(intent *intents.Intent) error {
// The index with the name "_id_" will always be the idIndex.
if index.Options["name"].(string) == "_id_" {
// Remove the index version (to use the default) unless otherwise specified.
- if !restore.OutputOptions.KeepIndexVersion {
+ // If preserving UUID, we have to create a collection via
+ // applyops, which requires the "v" key.
+ if !restore.OutputOptions.KeepIndexVersion && !restore.OutputOptions.PreserveUUID {
delete(index.Options, "v")
}
index.Options["ns"] = intent.Namespace()
@@ -180,7 +193,7 @@ func (restore *MongoRestore) RestoreIntent(intent *intents.Intent) error {
if !collectionExists {
log.Logvf(log.Info, "creating collection %v %s", intent.Namespace(), logMessageSuffix)
log.Logvf(log.DebugHigh, "using collection options: %#v", options)
- err = restore.CreateCollection(intent, options)
+ err = restore.CreateCollection(intent, options, uuid)
if err != nil {
return fmt.Errorf("error creating collection %v: %v", intent.Namespace(), err)
}