diff --git a/.github/workflows/all.yml b/.github/workflows/all.yml index 458e6abd..47b9de00 100644 --- a/.github/workflows/all.yml +++ b/.github/workflows/all.yml @@ -41,8 +41,14 @@ jobs: srcConnStr: mongodb://localhost:27020,localhost:27021,localhost:27022 dstConnStr: mongodb://localhost:27030,localhost:27031,localhost:27032 + - name: replset-to-sharded + dstArgs: --sharded 2 + srcConnStr: mongodb://localhost:27020,localhost:27021,localhost:27022 + dstConnStr: mongodb://localhost:27030 + - name: sharded - args: --sharded 2 + srcArgs: --sharded 2 + dstArgs: --sharded 2 srcConnStr: mongodb://localhost:27020 dstConnStr: mongodb://localhost:27030 @@ -82,8 +88,8 @@ jobs: - name: Start clusters run: |- { - echo mlaunch init --binarypath $(cat .srcpath) --port 27020 --dir src --replicaset ${{ matrix.topology.args }} - echo mlaunch init --binarypath $(cat .dstpath) --port 27030 --dir dst --replicaset ${{ matrix.topology.args }} + echo mlaunch init --binarypath $(cat .srcpath) --port 27020 --dir src --replicaset ${{ matrix.topology.srcArgs }} + echo mlaunch init --binarypath $(cat .dstpath) --port 27030 --dir dst --replicaset ${{ matrix.topology.dstArgs }} echo mlaunch init --binarypath $(cat .metapath) --port 27040 --dir meta --replicaset --nodes 1 } | parallel diff --git a/internal/util/askserver.go b/internal/util/askserver.go new file mode 100644 index 00000000..ba03aebe --- /dev/null +++ b/internal/util/askserver.go @@ -0,0 +1,66 @@ +package util + +import ( + "context" + "slices" + + "github.com/10gen/migration-verifier/mslices" + "github.com/10gen/migration-verifier/option" + "github.com/pkg/errors" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +// ServerThinksTheseMatch runs an aggregation on the server that determines +// whether the server thinks a & b are equal. This allows you, e.g., to +// ignore BSON type differences for equivalent numbers. +// +// tinker is an optional pipeline that operates on the documents in the +// pipeline (`a` and `b`, respectively) before they're compared. +// +// In migration-verifier the client is generally expected to be for +// the metadata cluster. +func ServerThinksTheseMatch( + ctx context.Context, + client *mongo.Client, + a, b any, + tinker option.Option[mongo.Pipeline], +) (bool, error) { + pipeline := mongo.Pipeline{ + {{"$documents", []bson.D{ + { + {"a", bson.D{{"$literal", a}}}, + {"b", bson.D{{"$literal", b}}}, + }, + }}}, + + // Now check to be sure that those specs match. + {{"$match", bson.D{ + {"$expr", bson.D{ + {"$eq", mslices.Of("$a", "$b")}, + }}, + }}}, + } + + if extra, hasExtra := tinker.Get(); hasExtra { + pipeline = slices.Insert( + pipeline, + 1, + extra..., + ) + } + + cursor, err := client.Database("admin").Aggregate(ctx, pipeline) + + if err == nil { + defer cursor.Close(ctx) + + if cursor.Next(ctx) { + return true, nil + } + + err = cursor.Err() + } + + return false, errors.Wrapf(err, "failed to ask server if a (%v) matches b (%v)", a, b) +} diff --git a/internal/util/sharding.go b/internal/util/sharding.go new file mode 100644 index 00000000..66e0f566 --- /dev/null +++ b/internal/util/sharding.go @@ -0,0 +1,54 @@ +package util + +import ( + "context" + + "github.com/10gen/migration-verifier/option" + "github.com/pkg/errors" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +const ( + configDBName = "config" + collsCollName = "collections" +) + +// GetShardKey returns the collection's shard key, or an empty option +// if the collection is unsharded. +func GetShardKey( + ctx context.Context, + coll *mongo.Collection, +) (option.Option[bson.Raw], error) { + namespace := coll.Database().Name() + "." + coll.Name() + + configCollectionsColl := coll.Database().Client(). + Database(configDBName). + Collection(collsCollName) + + decoded := struct { + Key option.Option[bson.Raw] + }{} + + err := configCollectionsColl. + FindOne(ctx, bson.D{{"_id", namespace}}). + Decode(&decoded) + + if errors.Is(err, mongo.ErrNoDocuments) { + return option.None[bson.Raw](), nil + } else if err != nil { + return option.None[bson.Raw](), errors.Wrapf( + err, + "failed to find sharding info for %#q", + namespace, + ) + } + + key, hasKey := decoded.Key.Get() + + if !hasKey { + return option.None[bson.Raw](), nil + } + + return option.Some(key), nil +} diff --git a/internal/verifier/check.go b/internal/verifier/check.go index d65fd955..54a1609a 100644 --- a/internal/verifier/check.go +++ b/internal/verifier/check.go @@ -20,7 +20,7 @@ const ( GenerationComplete GenerationStatus = "complete" ) -var failedStatus = mapset.NewSet( +var failedStatuses = mapset.NewSet( verificationTaskFailed, verificationTaskMetadataMismatch, ) @@ -406,7 +406,7 @@ func FetchFailedAndIncompleteTasks(ctx context.Context, coll *mongo.Collection, return FailedTasks, IncompleteTasks, err } for _, t := range allTasks { - if failedStatus.Contains(t.Status) { + if failedStatuses.Contains(t.Status) { FailedTasks = append(FailedTasks, t) } else if t.Status != verificationTaskCompleted { IncompleteTasks = append(IncompleteTasks, t) diff --git a/internal/verifier/integration_test_suite.go b/internal/verifier/integration_test_suite.go index 805d84b8..06025d68 100644 --- a/internal/verifier/integration_test_suite.go +++ b/internal/verifier/integration_test_suite.go @@ -5,9 +5,9 @@ import ( "strings" "time" + "github.com/10gen/migration-verifier/internal/util" mapset "github.com/deckarep/golang-set/v2" "github.com/pkg/errors" - "github.com/samber/lo" "github.com/stretchr/testify/suite" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" @@ -16,13 +16,8 @@ import ( "go.mongodb.org/mongo-driver/mongo/writeconcern" ) -type TestTopology string - const ( - metaDBName = "VERIFIER_TEST_META" - topologyEnvVar = "MVTEST_TOPOLOGY" - TopologyReplset TestTopology = "replset" - TopologySharded TestTopology = "sharded" + metaDBName = "VERIFIER_TEST_META" ) type IntegrationTestSuite struct { @@ -139,22 +134,14 @@ func (suite *IntegrationTestSuite) TearDownTest() { } } -func (suite *IntegrationTestSuite) GetSrcTopology() TestTopology { - hello := struct { - Msg string - }{} - - resp := suite.srcMongoClient.Database("admin").RunCommand( +func (suite *IntegrationTestSuite) GetTopology(client *mongo.Client) util.ClusterTopology { + clusterInfo, err := util.GetClusterInfo( suite.Context(), - bson.D{{"hello", 1}}, - ) - - suite.Require().NoError( - resp.Decode(&hello), - "should fetch & decode hello", + client, ) + suite.Require().NoError(err, "should fetch src cluster info") - return lo.Ternary(hello.Msg == "isdbgrid", TopologySharded, "") + return clusterInfo.Topology } func (suite *IntegrationTestSuite) BuildVerifier() *Verifier { diff --git a/internal/verifier/metadata.go b/internal/verifier/metadata.go new file mode 100644 index 00000000..9202a7f2 --- /dev/null +++ b/internal/verifier/metadata.go @@ -0,0 +1,93 @@ +package verifier + +import ( + "bytes" + "context" + "fmt" + + "github.com/10gen/migration-verifier/internal/util" + "github.com/10gen/migration-verifier/option" + "github.com/pkg/errors" + "github.com/samber/lo" + "go.mongodb.org/mongo-driver/mongo" +) + +// This is the Field for a VerificationResult for shard key mismatches. +const ShardKeyField = "Shard Key" + +func (verifier *Verifier) verifyShardingIfNeeded( + ctx context.Context, + srcColl, dstColl *mongo.Collection, +) ([]VerificationResult, error) { + + // We only need to compare if both clusters are sharded + srcSharded := verifier.srcClusterInfo.Topology == util.TopologySharded + dstSharded := verifier.dstClusterInfo.Topology == util.TopologySharded + + if !srcSharded || !dstSharded { + return nil, nil + } + + srcShardOpt, err := util.GetShardKey(ctx, srcColl) + if err != nil { + return nil, errors.Wrapf( + err, + "failed to fetch %#q's shard key on source", + FullName(srcColl), + ) + } + + dstShardOpt, err := util.GetShardKey(ctx, dstColl) + if err != nil { + return nil, errors.Wrapf( + err, + "failed to fetch %#q's shard key on destination", + FullName(dstColl), + ) + } + + srcKey, srcIsSharded := srcShardOpt.Get() + dstKey, dstIsSharded := dstShardOpt.Get() + + if !srcIsSharded && !dstIsSharded { + return nil, nil + } + + if srcIsSharded != dstIsSharded { + return []VerificationResult{{ + Field: ShardKeyField, + Cluster: lo.Ternary(srcIsSharded, ClusterTarget, ClusterSource), + Details: Missing, + NameSpace: FullName(srcColl), + }}, nil + } + + if bytes.Equal(srcKey, dstKey) { + return nil, nil + } + + areEqual, err := util.ServerThinksTheseMatch( + ctx, + verifier.metaClient, + srcKey, dstKey, + option.None[mongo.Pipeline](), + ) + if err != nil { + return nil, errors.Wrapf( + err, + "failed to ask server if shard keys (src %v; dst: %v) match", + srcKey, + dstKey, + ) + } + + if !areEqual { + return []VerificationResult{{ + Field: ShardKeyField, + Details: fmt.Sprintf("%s: src=%v; dst=%v", Mismatch, srcKey, dstKey), + NameSpace: FullName(srcColl), + }}, nil + } + + return nil, nil +} diff --git a/internal/verifier/metadata_test.go b/internal/verifier/metadata_test.go new file mode 100644 index 00000000..16d53573 --- /dev/null +++ b/internal/verifier/metadata_test.go @@ -0,0 +1,250 @@ +package verifier + +import ( + "fmt" + + "github.com/10gen/migration-verifier/internal/util" + "github.com/10gen/migration-verifier/mslices" + "github.com/samber/lo" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "golang.org/x/exp/maps" +) + +func (suite *IntegrationTestSuite) TestShardingMismatch() { + ctx := suite.Context() + srcInfo, err := util.GetClusterInfo(ctx, suite.srcMongoClient) + suite.Require().NoError(err, "should fetch src cluster info") + + dstInfo, err := util.GetClusterInfo(ctx, suite.dstMongoClient) + suite.Require().NoError(err, "should fetch dst cluster info") + + dbname := suite.DBNameForTest() + + shardCollection := func(client *mongo.Client, collName string, key bson.D, label string) { + suite.Require().NoError( + client.Database("admin").RunCommand( + ctx, + bson.D{ + {"shardCollection", dbname + "." + collName}, + {"key", key}, + }, + ).Err(), + fmt.Sprintf("%s: should shard %#q", label, dbname+"."+collName), + ) + } + + // Create indexes as needed to ensure that we only + // check for sharding mismatches. + allIndexes := map[string]map[string]bson.D{ + "numtype": { + "foo_1": {{"foo", 1}}, + }, + "id_and_foo": { + "foo_1__id_1": {{"foo", 1}, {"_id", 1}}, + "_id_1_foo_1": {{"_id", 1}, {"foo", 1}}, + }, + "idonly": {}, + "sharded_dst": {}, + } + + for c, client := range mslices.Of(suite.srcMongoClient, suite.dstMongoClient) { + for collName, indexMap := range allIndexes { + suite.Require().NoError( + client.Database(dbname).CreateCollection( + ctx, + collName, + ), + "should create %#q on "+lo.Ternary(c == 0, "source", "destinatinon"), + collName, + ) + + if len(indexMap) > 0 { + + suite.Require().NoError( + client.Database(dbname).RunCommand( + ctx, + bson.D{ + {"createIndexes", collName}, + {"indexes", lo.Map( + maps.Keys(indexMap), + func(idxName string, _ int) bson.D { + return bson.D{ + {"name", idxName}, + {"key", indexMap[idxName]}, + } + }, + )}, + }, + ).Err(), + ) + } + } + } + + if srcInfo.Topology == util.TopologySharded { + suite.Require().NoError( + suite.srcMongoClient.Database("admin").RunCommand( + ctx, + bson.D{{"enableSharding", dbname}}, + ).Err(), + ) + + shardCollection( + suite.srcMongoClient, + "idonly", + bson.D{{"_id", 1}}, + "src", + ) + shardCollection( + suite.srcMongoClient, + "numtype", + bson.D{{"foo", 1}}, + "src", + ) + shardCollection( + suite.srcMongoClient, + "id_and_foo", + bson.D{{"_id", 1}, {"foo", 1}}, + "src", + ) + } + + if dstInfo.Topology == util.TopologySharded { + suite.Require().NoError( + suite.dstMongoClient.Database("admin").RunCommand( + ctx, + bson.D{{"enableSharding", dbname}}, + ).Err(), + ) + + shardCollection( + suite.dstMongoClient, + "idonly", + bson.D{{"_id", 1}}, + "dst", + ) + shardCollection( + suite.dstMongoClient, + "numtype", + bson.D{{"foo", float64(1)}}, + "dst", + ) + shardCollection( + suite.dstMongoClient, + "id_and_foo", + bson.D{{"foo", 1}, {"_id", 1}}, + "dst", + ) + shardCollection( + suite.dstMongoClient, + "sharded_dst", + bson.D{{"_id", 1}}, + "dst", + ) + } else { + suite.Require().NoError( + suite.dstMongoClient.Database(dbname).RunCommand( + ctx, + bson.D{ + {"createIndexes", "numtype"}, + {"indexes", []bson.D{ + { + {"name", "foo_1"}, + {"key", bson.D{{"foo", float64(1)}}}, + }, + }}, + }, + ).Err(), + ) + + suite.Require().NoError( + suite.dstMongoClient.Database(dbname).RunCommand( + ctx, + bson.D{ + {"createIndexes", "id_and_foo"}, + {"indexes", []bson.D{ + { + {"name", "foo_1__id_1"}, + {"key", bson.D{{"foo", 1}, {"_id", 1}}}, + }, + { + {"name", "_id_1_foo_1"}, + {"key", bson.D{{"_id", 1}, {"foo", 1}}}, + }, + }}, + }, + ).Err(), + ) + } + + verifier := suite.BuildVerifier() + + namespaces := lo.Map( + maps.Keys(allIndexes), + func(collName string, _ int) string { + return dbname + "." + collName + }, + ) + verifier.SetSrcNamespaces(namespaces) + verifier.SetDstNamespaces(namespaces) + verifier.SetNamespaceMap() + + runner := RunVerifierCheck(ctx, suite.T(), verifier) + suite.Require().NoError(runner.AwaitGenerationEnd()) + + cursor, err := verifier.verificationTaskCollection().Find( + ctx, + bson.M{ + "generation": 0, + "type": verificationTaskVerifyCollection, + }, + ) + suite.Require().NoError(err) + + var tasks []VerificationTask + suite.Require().NoError(cursor.All(ctx, &tasks)) + + suite.Require().Len(tasks, len(allIndexes)) + + if srcInfo.Topology == util.TopologySharded && dstInfo.Topology == util.TopologySharded { + taskMap := mslices.ToMap( + tasks, + func(task VerificationTask) string { + return task.QueryFilter.Namespace + }, + ) + + suite.Assert().Equal( + verificationTaskCompleted, + taskMap[dbname+".idonly"].Status, + "full match", + ) + + suite.Assert().Equal( + verificationTaskCompleted, + taskMap[dbname+".numtype"].Status, + "number type differences are ignored", + ) + + suite.Assert().Equal( + verificationTaskMetadataMismatch, + taskMap[dbname+".id_and_foo"].Status, + "catch field order difference", + ) + + suite.Assert().Equal( + verificationTaskMetadataMismatch, + taskMap[dbname+".sharded_dst"].Status, + "catch dst-only sharded", + ) + } else { + for _, task := range tasks { + suite.Assert().Equal( + verificationTaskCompleted, + task.Status, + "mismatched topologies, so task should have succeeded: %+v", task, + ) + } + } +} diff --git a/internal/verifier/migration_verifier.go b/internal/verifier/migration_verifier.go index 4f8fa431..813f91a2 100644 --- a/internal/verifier/migration_verifier.go +++ b/internal/verifier/migration_verifier.go @@ -8,7 +8,6 @@ import ( "math/rand" _ "net/http/pprof" "os" - "sort" "strconv" "strings" "sync" @@ -23,7 +22,6 @@ import ( "github.com/10gen/migration-verifier/internal/util" "github.com/10gen/migration-verifier/internal/uuidutil" "github.com/10gen/migration-verifier/mbson" - "github.com/10gen/migration-verifier/mslices" "github.com/10gen/migration-verifier/option" "github.com/olekukonko/tablewriter" "github.com/pkg/errors" @@ -35,7 +33,6 @@ import ( "go.mongodb.org/mongo-driver/mongo/readconcern" "go.mongodb.org/mongo-driver/mongo/readpref" "go.mongodb.org/mongo-driver/mongo/writeconcern" - "golang.org/x/exp/maps" ) // ReadConcernSetting describes the verifier’s handling of read @@ -66,9 +63,6 @@ const ( // is unworkable. ReadConcernIgnore ReadConcernSetting = "ignore" - configDBName = "config" - collsCollName = "collections" - DefaultFailureDisplaySize = 20 okSymbol = "\u2705" // white heavy check mark @@ -694,42 +688,44 @@ func (verifier *Verifier) logChunkInfo(ctx context.Context, namespaceAndUUID *uu } } -func (verifier *Verifier) getShardingInfo(ctx context.Context, namespaceAndUUID *uuidutil.NamespaceAndUUID) ([]string, error) { - uuid := namespaceAndUUID.UUID - namespace := namespaceAndUUID.DBName + "." + namespaceAndUUID.CollName - configCollectionsColl := verifier.srcClientDatabase(configDBName).Collection(collsCollName) - cursor, err := configCollectionsColl.Find(ctx, bson.D{{"uuid", uuid}}) +func (verifier *Verifier) getShardKeyFields( + ctx context.Context, + namespaceAndUUID *uuidutil.NamespaceAndUUID, +) ([]string, error) { + coll := verifier.srcClient.Database(namespaceAndUUID.DBName). + Collection(namespaceAndUUID.CollName) + + shardKeyOpt, err := util.GetShardKey(ctx, coll) if err != nil { - return nil, fmt.Errorf("Failed to read sharding info for %s: %v", namespace, err) + return nil, errors.Wrapf( + err, + "failed to fetch %#q's shard key", + FullName(coll), + ) } - defer cursor.Close(ctx) - collectionSharded := false - - shardKeys := []string{} - - for cursor.Next(ctx) { - collectionSharded = true - var result struct { - Key bson.M - } - if err = cursor.Decode(&result); err != nil { - return nil, fmt.Errorf("Failed to decode sharding info for %s: %v", namespace, err) - } + shardKeyRaw, isSharded := shardKeyOpt.Get() + if !isSharded { + return []string{}, nil + } - verifier.logger.Debug().Msgf("Collection %s is sharded with shard key %v", namespace, result.Key) + verifier.logChunkInfo(ctx, namespaceAndUUID) - shardKeys = maps.Keys(result.Key) - sort.Strings(shardKeys) - } - if err = cursor.Err(); err != nil { - verifier.logger.Error().Msgf("Error reading sharding info for %s: %v", namespace, err) - } - if collectionSharded { - verifier.logChunkInfo(ctx, namespaceAndUUID) + els, err := shardKeyRaw.Elements() + if err != nil { + return nil, errors.Wrapf( + err, + "failed to parse %#q's shard key", + FullName(coll), + ) } - return shardKeys, nil + return lo.Map( + els, + func(el bson.RawElement, _ int) string { + return el.Key() + }, + ), nil } // partitionAndInspectNamespace does a few preliminary tasks for the @@ -745,7 +741,7 @@ func (verifier *Verifier) partitionAndInspectNamespace(ctx context.Context, name if err != nil { return nil, nil, 0, 0, err } - shardKeys, err := verifier.getShardingInfo(ctx, namespaceAndUUID) + shardKeys, err := verifier.getShardKeyFields(ctx, namespaceAndUUID) if err != nil { return nil, nil, 0, 0, err } @@ -860,7 +856,7 @@ func (verifier *Verifier) compareCollectionSpecifications( return results, canCompareData, nil } -func (verifier *Verifier) doIndexSpecsMatch(ctx context.Context, srcSpec bson.Raw, dstSpec bson.Raw) (bool, error) { +func (verifier *Verifier) doIndexSpecsMatch(ctx context.Context, srcSpec, dstSpec bson.Raw) (bool, error) { // If the byte buffers match, then we’re done. if bytes.Equal(srcSpec, dstSpec) { return true, nil @@ -874,56 +870,21 @@ func (verifier *Verifier) doIndexSpecsMatch(ctx context.Context, srcSpec bson.Ra "background", } - // Next check to see if the only differences are type differences. - // (We can safely use $documents here since this is against the metadata - // cluster, which we can require to be v5+.) - db := verifier.metaClient.Database(verifier.metaDBName) - cursor, err := db.Aggregate( + return util.ServerThinksTheseMatch( ctx, - mongo.Pipeline{ - {{"$documents", []bson.D{ - { - {"spec", bson.D{{"$literal", srcSpec}}}, - {"dstSpec", bson.D{{"$literal", dstSpec}}}, - }, - }}}, - + verifier.metaClient, + srcSpec, + dstSpec, + option.Some(mongo.Pipeline{ {{"$unset", lo.Reduce( fieldsToRemove, func(cur []string, field string, _ int) []string { - return append(cur, "spec."+field, "dstSpec."+field) + return append(cur, "a."+field, "b."+field) }, []string{}, )}}, - - // Now check to be sure that those specs match. - {{"$match", bson.D{ - {"$expr", bson.D{ - {"$eq", mslices.Of("$spec", "$dstSpec")}, - }}, - }}}, - }, + }), ) - - if err != nil { - return false, errors.Wrap(err, "failed to check index specification match in metadata") - } - var docs []bson.Raw - err = cursor.All(ctx, &docs) - if err != nil { - return false, errors.Wrap(err, "failed to parse index specification match’s result") - } - - count := len(docs) - - switch count { - case 0: - return false, nil - case 1: - return true, nil - } - - return false, errors.Errorf("weirdly received %d matching index docs (should be 0 or 1)", count) } func (verifier *Verifier) ProcessCollectionVerificationTask( @@ -983,14 +944,6 @@ func getIndexesMap( var name string has, err := mbson.RawLookup(spec, &name, "name") - if !has { - return nil, errors.Errorf( - "%#q has an unnamed index (%+v)", - FullName(coll), - spec, - ) - } - if err != nil { return nil, errors.Wrapf( err, @@ -1001,6 +954,14 @@ func getIndexesMap( ) } + if !has { + return nil, errors.Errorf( + "%#q has an unnamed index (%+v)", + FullName(coll), + spec, + ) + } + specsMap[name] = spec } @@ -1195,6 +1156,27 @@ func (verifier *Verifier) verifyMetadataAndPartitionCollection( task.Status = verificationTaskMetadataMismatch } + shardingProblems, err := verifier.verifyShardingIfNeeded(ctx, srcColl, dstColl) + if err != nil { + return errors.Wrapf( + err, + "failed to compare namespace %#q's sharding", + srcNs, + ) + } + if len(shardingProblems) > 0 { + // don't insert a failed collection unless we did not insert one above + if len(specificationProblems)+len(indexProblems) == 0 { + err = insertFailedCollection() + if err != nil { + return err + } + } + + task.FailedDocs = append(task.FailedDocs, shardingProblems...) + task.Status = verificationTaskMetadataMismatch + } + // We’ve confirmed that the collection metadata (including indices) // matches between soruce & destination. Now we can partition the collection. diff --git a/internal/verifier/migration_verifier_test.go b/internal/verifier/migration_verifier_test.go index 6528a523..f2a5edf0 100644 --- a/internal/verifier/migration_verifier_test.go +++ b/internal/verifier/migration_verifier_test.go @@ -18,6 +18,7 @@ import ( "github.com/10gen/migration-verifier/internal/partitions" "github.com/10gen/migration-verifier/internal/testutil" + "github.com/10gen/migration-verifier/internal/util" "github.com/10gen/migration-verifier/mslices" "github.com/cespare/permute/v2" "github.com/rs/zerolog" @@ -1182,7 +1183,7 @@ func (suite *IntegrationTestSuite) TestVerifierNamespaceList() { err = suite.dstMongoClient.Database("testDb4").CreateCollection(ctx, "testColl6") suite.Require().NoError(err) - if suite.GetSrcTopology() != TopologySharded { + if suite.GetTopology(suite.dstMongoClient) != util.TopologySharded { err = suite.dstMongoClient.Database("local").CreateCollection(ctx, "testColl7") suite.Require().NoError(err) } @@ -1223,9 +1224,12 @@ func (suite *IntegrationTestSuite) TestVerifierNamespaceList() { suite.ElementsMatch([]string{"testDb1.testColl1", "testDb1.testColl2", "testDb1.testView1"}, verifier.dstNamespaces) // Collections in admin, config, and local should not be found - if suite.GetSrcTopology() != TopologySharded { + if suite.GetTopology(suite.srcMongoClient) != util.TopologySharded { err = suite.srcMongoClient.Database("local").CreateCollection(ctx, "islocalSrc") suite.Require().NoError(err) + } + + if suite.GetTopology(suite.dstMongoClient) != util.TopologySharded { err = suite.dstMongoClient.Database("local").CreateCollection(ctx, "islocalDest") suite.Require().NoError(err) } diff --git a/internal/verifier/summary.go b/internal/verifier/summary.go index 4c86ae67..0df302c5 100644 --- a/internal/verifier/summary.go +++ b/internal/verifier/summary.go @@ -40,7 +40,7 @@ func (verifier *Verifier) reportCollectionMetadataMismatches(ctx context.Context if len(failedTasks) != 0 { table := tablewriter.NewWriter(strBuilder) - table.SetHeader([]string{"Index", "Cluster", "Type", "Field", "Namespace", "Details"}) + table.SetHeader([]string{"Index", "Cluster", "Field", "Namespace", "Details"}) for _, v := range failedTasks { for _, f := range v.FailedDocs { diff --git a/internal/verifier/uri.go b/internal/verifier/uri.go index 25470af4..abd71e18 100644 --- a/internal/verifier/uri.go +++ b/internal/verifier/uri.go @@ -19,7 +19,7 @@ func (verifier *Verifier) SetSrcURI(ctx context.Context, uri string) error { clusterInfo, err := util.GetClusterInfo(ctx, verifier.srcClient) if err != nil { - return errors.Wrap(err, "failed to read source build info") + return errors.Wrap(err, "failed to read source cluster info") } verifier.srcClusterInfo = &clusterInfo diff --git a/mslices/slices.go b/mslices/slices.go index f9f0a25a..1ea106ce 100644 --- a/mslices/slices.go +++ b/mslices/slices.go @@ -9,3 +9,14 @@ package mslices func Of[T any](pieces ...T) []T { return append([]T{}, pieces...) } + +// ToMap outputs a map that “indexes” the given slice. +func ToMap[S ~[]E, E any, K comparable](s S, cb func(el E) K) map[K]E { + theMap := make(map[K]E, len(s)) + + for _, el := range s { + theMap[cb(el)] = el + } + + return theMap +} diff --git a/mslices/slices_test.go b/mslices/slices_test.go index 4264d76f..64dee56f 100644 --- a/mslices/slices_test.go +++ b/mslices/slices_test.go @@ -25,3 +25,26 @@ func (s *mySuite) Test_Of() { s.Assert().Equal(1, b[0], "should copy slice") } + +func (s *mySuite) Test_ToMap() { + type stuff struct { + Name string + } + + mySlice := []stuff{{"foo"}, {"bar"}, {"bar"}} + + myMap := ToMap( + mySlice, + func(el stuff) string { + return el.Name + }, + ) + + s.Assert().Equal( + map[string]stuff{ + "foo": {"foo"}, + "bar": {"bar"}, + }, + myMap, + ) +}