Skip to content

Commit 5bf4ecd

Browse files
authored
REP-5972 Version metadata to prevent breakage across verifier builds. (#113)
This adds a metadata version to the persisted generation and makes migration-verifier fail if it starts up and finds a persisted metadata version that mismatches its own.
1 parent 2e5723d commit 5bf4ecd

File tree

5 files changed

+146
-95
lines changed

5 files changed

+146
-95
lines changed

internal/verifier/check_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package verifier
2+
3+
func (suite *IntegrationTestSuite) TestPersistedMetadataVersionMismatch() {
4+
ctx := suite.Context()
5+
6+
verifier := suite.BuildVerifier()
7+
verifier.SetNamespaceMap()
8+
9+
genColl := verifier.metaClient.
10+
Database(verifier.metaDBName).
11+
Collection(generationCollName)
12+
13+
_, err := genColl.InsertOne(
14+
ctx,
15+
generationDoc{
16+
MetadataVersion: verifierMetadataVersion - 1,
17+
},
18+
)
19+
suite.Require().NoError(err, "should write old generation doc")
20+
21+
runner := RunVerifierCheck(ctx, suite.T(), verifier)
22+
err = runner.AwaitGenerationEnd()
23+
24+
mme := metadataMismatchErr{}
25+
suite.Require().ErrorAs(err, &mme)
26+
}

internal/verifier/generation.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@ const (
1616
generationFieldName = "generation"
1717
)
1818

19+
type generationDoc struct {
20+
Generation int
21+
MetadataVersion int
22+
}
23+
24+
type metadataMismatchErr struct {
25+
persistedVersion int
26+
}
27+
28+
func (mme metadataMismatchErr) Error() string {
29+
return fmt.Sprintf("persisted metadata (version: %d) predates this migration-verifier build (metadata version: %d); please discard prior verification progress by restarting with the `--clean` flag",
30+
mme.persistedVersion,
31+
verifierMetadataVersion,
32+
)
33+
}
34+
1935
func (v *Verifier) persistGenerationWhileLocked(ctx context.Context) error {
2036
generation, _ := v.getGenerationWhileLocked()
2137

@@ -24,7 +40,10 @@ func (v *Verifier) persistGenerationWhileLocked(ctx context.Context) error {
2440
result, err := db.Collection(generationCollName).ReplaceOne(
2541
ctx,
2642
bson.D{},
27-
bson.D{{generationFieldName, generation}},
43+
generationDoc{
44+
Generation: generation,
45+
MetadataVersion: verifierMetadataVersion,
46+
},
2847
options.Replace().SetUpsert(true),
2948
)
3049

@@ -43,9 +62,7 @@ func (v *Verifier) readGeneration(ctx context.Context) (option.Option[int], erro
4362
bson.D{},
4463
)
4564

46-
parsed := struct {
47-
Generation int `bson:"generation"`
48-
}{}
65+
parsed := generationDoc{}
4966

5067
err := result.Decode(&parsed)
5168

@@ -59,5 +76,10 @@ func (v *Verifier) readGeneration(ctx context.Context) (option.Option[int], erro
5976
return option.None[int](), err
6077
}
6178

79+
if parsed.MetadataVersion != verifierMetadataVersion {
80+
return option.None[int](), metadataMismatchErr{parsed.MetadataVersion}
81+
82+
}
83+
6284
return option.Some(parsed.Generation), nil
6385
}

internal/verifier/metadata.go

Lines changed: 1 addition & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,3 @@
11
package verifier
22

3-
import (
4-
"bytes"
5-
"context"
6-
"fmt"
7-
8-
"github.com/10gen/migration-verifier/internal/util"
9-
"github.com/10gen/migration-verifier/option"
10-
"github.com/pkg/errors"
11-
"github.com/samber/lo"
12-
"go.mongodb.org/mongo-driver/mongo"
13-
)
14-
15-
// This is the Field for a VerificationResult for shard key mismatches.
16-
const ShardKeyField = "Shard Key"
17-
18-
func (verifier *Verifier) verifyShardingIfNeeded(
19-
ctx context.Context,
20-
srcColl, dstColl *mongo.Collection,
21-
) ([]VerificationResult, error) {
22-
23-
// We only need to compare if both clusters are sharded
24-
srcSharded := verifier.srcClusterInfo.Topology == util.TopologySharded
25-
dstSharded := verifier.dstClusterInfo.Topology == util.TopologySharded
26-
27-
if !srcSharded || !dstSharded {
28-
return nil, nil
29-
}
30-
31-
srcShardOpt, err := util.GetShardKey(ctx, srcColl)
32-
if err != nil {
33-
return nil, errors.Wrapf(
34-
err,
35-
"failed to fetch %#q's shard key on source",
36-
FullName(srcColl),
37-
)
38-
}
39-
40-
dstShardOpt, err := util.GetShardKey(ctx, dstColl)
41-
if err != nil {
42-
return nil, errors.Wrapf(
43-
err,
44-
"failed to fetch %#q's shard key on destination",
45-
FullName(dstColl),
46-
)
47-
}
48-
49-
srcKey, srcIsSharded := srcShardOpt.Get()
50-
dstKey, dstIsSharded := dstShardOpt.Get()
51-
52-
if !srcIsSharded && !dstIsSharded {
53-
return nil, nil
54-
}
55-
56-
if srcIsSharded != dstIsSharded {
57-
return []VerificationResult{{
58-
Field: ShardKeyField,
59-
Cluster: lo.Ternary(srcIsSharded, ClusterTarget, ClusterSource),
60-
Details: Missing,
61-
NameSpace: FullName(srcColl),
62-
}}, nil
63-
}
64-
65-
if bytes.Equal(srcKey, dstKey) {
66-
return nil, nil
67-
}
68-
69-
areEqual, err := util.ServerThinksTheseMatch(
70-
ctx,
71-
verifier.metaClient,
72-
srcKey, dstKey,
73-
option.None[mongo.Pipeline](),
74-
)
75-
if err != nil {
76-
return nil, errors.Wrapf(
77-
err,
78-
"failed to ask server if shard keys (src %v; dst: %v) match",
79-
srcKey,
80-
dstKey,
81-
)
82-
}
83-
84-
if !areEqual {
85-
return []VerificationResult{{
86-
Field: ShardKeyField,
87-
Details: fmt.Sprintf("%s: src=%v; dst=%v", Mismatch, srcKey, dstKey),
88-
NameSpace: FullName(srcColl),
89-
}}, nil
90-
}
91-
92-
return nil, nil
93-
}
3+
const verifierMetadataVersion = 1

internal/verifier/sharding.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package verifier
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
8+
"github.com/10gen/migration-verifier/internal/util"
9+
"github.com/10gen/migration-verifier/option"
10+
"github.com/pkg/errors"
11+
"github.com/samber/lo"
12+
"go.mongodb.org/mongo-driver/mongo"
13+
)
14+
15+
// This is the Field for a VerificationResult for shard key mismatches.
16+
const ShardKeyField = "Shard Key"
17+
18+
func (verifier *Verifier) verifyShardingIfNeeded(
19+
ctx context.Context,
20+
srcColl, dstColl *mongo.Collection,
21+
) ([]VerificationResult, error) {
22+
23+
// We only need to compare if both clusters are sharded
24+
srcSharded := verifier.srcClusterInfo.Topology == util.TopologySharded
25+
dstSharded := verifier.dstClusterInfo.Topology == util.TopologySharded
26+
27+
if !srcSharded || !dstSharded {
28+
return nil, nil
29+
}
30+
31+
srcShardOpt, err := util.GetShardKey(ctx, srcColl)
32+
if err != nil {
33+
return nil, errors.Wrapf(
34+
err,
35+
"failed to fetch %#q's shard key on source",
36+
FullName(srcColl),
37+
)
38+
}
39+
40+
dstShardOpt, err := util.GetShardKey(ctx, dstColl)
41+
if err != nil {
42+
return nil, errors.Wrapf(
43+
err,
44+
"failed to fetch %#q's shard key on destination",
45+
FullName(dstColl),
46+
)
47+
}
48+
49+
srcKey, srcIsSharded := srcShardOpt.Get()
50+
dstKey, dstIsSharded := dstShardOpt.Get()
51+
52+
if !srcIsSharded && !dstIsSharded {
53+
return nil, nil
54+
}
55+
56+
if srcIsSharded != dstIsSharded {
57+
return []VerificationResult{{
58+
Field: ShardKeyField,
59+
Cluster: lo.Ternary(srcIsSharded, ClusterTarget, ClusterSource),
60+
Details: Missing,
61+
NameSpace: FullName(srcColl),
62+
}}, nil
63+
}
64+
65+
if bytes.Equal(srcKey, dstKey) {
66+
return nil, nil
67+
}
68+
69+
areEqual, err := util.ServerThinksTheseMatch(
70+
ctx,
71+
verifier.metaClient,
72+
srcKey, dstKey,
73+
option.None[mongo.Pipeline](),
74+
)
75+
if err != nil {
76+
return nil, errors.Wrapf(
77+
err,
78+
"failed to ask server if shard keys (src %v; dst: %v) match",
79+
srcKey,
80+
dstKey,
81+
)
82+
}
83+
84+
if !areEqual {
85+
return []VerificationResult{{
86+
Field: ShardKeyField,
87+
Details: fmt.Sprintf("%s: src=%v; dst=%v", Mismatch, srcKey, dstKey),
88+
NameSpace: FullName(srcColl),
89+
}}, nil
90+
}
91+
92+
return nil, nil
93+
}

0 commit comments

Comments
 (0)