diff options
author | Kaloian Manassiev <kaloian.manassiev@mongodb.com> | 2022-01-20 09:13:17 +0100 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-01-24 16:51:41 +0000 |
commit | 3bc48c6fd422822c849ad3c82b780122c64ec423 (patch) | |
tree | 79590a2d52fd8e01d8152dbbec3669bb1561d066 /src/mongo | |
parent | a94feb562cd9ffb42c10770fbf5b5d489875cc2f (diff) | |
download | mongo-3bc48c6fd422822c849ad3c82b780122c64ec423.tar.gz |
SERVER-62783 Make parsing of new and legacy formats more explicit
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/s/chunk_version.cpp | 95 | ||||
-rw-r--r-- | src/mongo/s/chunk_version.h | 8 | ||||
-rw-r--r-- | src/mongo/s/chunk_version_test.cpp | 65 |
3 files changed, 107 insertions, 61 deletions
diff --git a/src/mongo/s/chunk_version.cpp b/src/mongo/s/chunk_version.cpp index fd71e7270f8..0de0352f3b9 100644 --- a/src/mongo/s/chunk_version.cpp +++ b/src/mongo/s/chunk_version.cpp @@ -56,28 +56,26 @@ StatusWith<ChunkVersion> ChunkVersion::fromBSON(const BSONObj& obj) { if (!it.more()) return {ErrorCodes::BadValue, "Unexpected empty version array"}; - ChunkVersion version; - - // Expect the major and minor versions + // Expect the major and minor versions (must be present) + uint64_t combined; { BSONElement tsPart = it.next(); if (tsPart.type() != bsonTimestamp) return {ErrorCodes::TypeMismatch, str::stream() << "Invalid type " << tsPart.type() << " for version major and minor part."}; - - version._combined = tsPart.timestamp().asULL(); + combined = tsPart.timestamp().asULL(); } - // Expect the epoch OID + // Expect the epoch OID (must be present) + boost::optional<OID> epoch; { BSONElement epochPart = it.next(); if (epochPart.type() != jstOID) return {ErrorCodes::TypeMismatch, str::stream() << "Invalid type " << epochPart.type() << " for version epoch part."}; - - version._epoch = epochPart.OID(); + epoch = epochPart.OID(); } BSONElement nextElem = it.next(); @@ -90,33 +88,37 @@ StatusWith<ChunkVersion> ChunkVersion::fromBSON(const BSONObj& obj) { } // Check for timestamp + boost::optional<Timestamp> timestamp; if (nextElem.type() == bsonTimestamp) { - version._timestamp = nextElem.timestamp(); - } else if (nextElem.eoo() && version.is50IgnoredOrUnsharded()) { + timestamp = nextElem.timestamp(); + } else if (nextElem.eoo() && (epoch == UNSHARDED().epoch() || epoch == IGNORED().epoch())) { // In 5.0 binaries, the timestamp is not present in UNSHARDED and IGNORED versions - version._timestamp = - (version.epoch() == UNSHARDED().epoch()) ? Timestamp() : Timestamp::max(); + timestamp = + (epoch == UNSHARDED().epoch() ? UNSHARDED().getTimestamp() : IGNORED().getTimestamp()); } else { return {ErrorCodes::TypeMismatch, str::stream() << "Invalid type " << nextElem.type() << " for version timestamp part."}; } + ChunkVersion version; + version._combined = combined; + version._epoch = *epoch; + version._timestamp = *timestamp; return version; } StatusWith<ChunkVersion> ChunkVersion::parseLegacyWithField(const BSONObj& obj, StringData field) { - auto versionElem = obj[field]; - if (versionElem.eoo()) - return {ErrorCodes::NoSuchKey, - str::stream() << "Expected field " << field << " not found."}; - - ChunkVersion version; - - // Expect the major and minor + // Expect the major and minor (must always exist) + uint64_t combined; { + auto versionElem = obj[field]; + if (versionElem.eoo()) + return {ErrorCodes::NoSuchKey, + str::stream() << "Expected field " << field << " not found."}; + if (versionElem.type() == bsonTimestamp || versionElem.type() == Date) { - version._combined = versionElem._numberLong(); + combined = versionElem._numberLong(); } else { return {ErrorCodes::TypeMismatch, str::stream() << "Invalid type " << versionElem.type() @@ -124,14 +126,16 @@ StatusWith<ChunkVersion> ChunkVersion::parseLegacyWithField(const BSONObj& obj, } } - bool fullVersion = false; // Expect the epoch OID + // + // TODO: Confirm whether the epoch can still be missing in upgrade chains that started from + // pre-2.4 versions anymore (after FCV 4.4 -> 5.0 upgrade) ? + boost::optional<OID> epoch; { const auto epochField = field + "Epoch"; auto epochElem = obj[epochField]; if (epochElem.type() == jstOID) { - version._epoch = epochElem.OID(); - fullVersion = true; + epoch = epochElem.OID(); } else if (!epochElem.eoo()) { return {ErrorCodes::TypeMismatch, str::stream() << "Invalid type " << epochElem.type() @@ -139,27 +143,42 @@ StatusWith<ChunkVersion> ChunkVersion::parseLegacyWithField(const BSONObj& obj, } } - // Expect the timestamp + // Expect the timestamp (can be missing only in the case of pre-5.0 UNSHARDED and IGNORED + // versions) + boost::optional<Timestamp> timestamp; { const auto timestampField = field + "Timestamp"; auto timestampElem = obj[timestampField]; - if (fullVersion) { - if (timestampElem.type() == bsonTimestamp) { - version._timestamp = timestampElem.timestamp(); - } else if (timestampElem.eoo() && version.is50IgnoredOrUnsharded()) { - // In 5.0 binaries, the timestamp is not present in UNSHARDED and IGNORED versions - version._timestamp = - (version.epoch() == UNSHARDED().epoch()) ? Timestamp() : Timestamp::max(); - } else { - return {ErrorCodes::TypeMismatch, - str::stream() << "Invalid type " << timestampElem.type() - << " for version timestamp part."}; - } + if (timestampElem.type() == bsonTimestamp) { + timestamp = timestampElem.timestamp(); + } else if (!timestampElem.eoo()) { + return {ErrorCodes::TypeMismatch, + str::stream() << "Invalid type " << timestampElem.type() + << " for version timestamp part."}; + } + } + + if (epoch && timestamp) { + // Expected situation + } else if (epoch && !timestamp) { + if (epoch == UNSHARDED().epoch() || epoch == IGNORED().epoch()) { + // In 5.0 binaries, the timestamp is not present in UNSHARDED and IGNORED versions + timestamp = (epoch == UNSHARDED().epoch() ? UNSHARDED().getTimestamp() + : IGNORED().getTimestamp()); } else { - invariant(timestampElem.eoo()); + uasserted(6278300, "Timestamp must be present if epoch exists."); } + } else if (!epoch && timestamp) { + uasserted(6278301, "Epoch must be present if timestamp exists."); + } else { + // Can happen in upgrade chains that started from pre-2.4 versions or in the case of + // persistence for ShardCollectionType } + ChunkVersion version; + version._combined = combined; + version._epoch = epoch.value_or(OID()); + version._timestamp = timestamp.value_or(Timestamp()); return version; } diff --git a/src/mongo/s/chunk_version.h b/src/mongo/s/chunk_version.h index 96ea3864b35..be1c25f6094 100644 --- a/src/mongo/s/chunk_version.h +++ b/src/mongo/s/chunk_version.h @@ -123,14 +123,6 @@ public: version.getTimestamp() == IGNORED().getTimestamp(); } - /** - * Needed for parsing IGNORED and UNSHARDED from 5.0 that didn't include a timestamp. Should be - * removed after 6.0 is last-lts. - */ - bool is50IgnoredOrUnsharded() { - return _combined == 0 && (_epoch == UNSHARDED().epoch() || _epoch == IGNORED().epoch()); - } - void incMajor() { uassert( 31180, diff --git a/src/mongo/s/chunk_version_test.cpp b/src/mongo/s/chunk_version_test.cpp index 624ff4ae81f..e3095e0186f 100644 --- a/src/mongo/s/chunk_version_test.cpp +++ b/src/mongo/s/chunk_version_test.cpp @@ -104,27 +104,60 @@ TEST(ChunkVersionParsing, FromBSONMissingMajorAndMinor) { ErrorCodes::TypeMismatch); } -TEST(ChunkVersionParsing, FromBSONLegacy) { +TEST(ChunkVersionParsing, FromBSONLegacy_WithTimestamp_WithEpoch) { const OID oid = OID::gen(); - const Timestamp timestamp(42); ChunkVersion chunkVersionComplete = assertGet(ChunkVersion::parseLegacyWithField( BSON("lastmod" << Timestamp(Seconds(2), 3) << "lastmodEpoch" << oid << "lastmodTimestamp" - << timestamp), + << Timestamp(42)), "lastmod")); - - ASSERT(chunkVersionComplete.epoch().isSet()); + ASSERT_EQ(Timestamp(42), chunkVersionComplete.getTimestamp()); ASSERT_EQ(oid, chunkVersionComplete.epoch()); ASSERT_EQ(2u, chunkVersionComplete.majorVersion()); ASSERT_EQ(3u, chunkVersionComplete.minorVersion()); } -TEST(ChunkVersionParsing, FromBSONLegacyEpochAndTimestampOptional) { - ChunkVersion chunkVersionNoEpoch = assertGet( - ChunkVersion::parseLegacyWithField(BSON("lastmod" << Timestamp(Seconds(3), 4)), "lastmod")); +TEST(ChunkVersionParsing, FromBSONLegacy_NoTimestamp_WithUnshardedEpoch) { + ChunkVersion chunkVersion = assertGet(ChunkVersion::parseLegacyWithField( + BSON("lastmod" << Timestamp() << "lastmodEpoch" << ChunkVersion::UNSHARDED().epoch()), + "lastmod")); + ASSERT_EQ(ChunkVersion::UNSHARDED().getTimestamp(), chunkVersion.getTimestamp()); + ASSERT_EQ(ChunkVersion::UNSHARDED().epoch(), chunkVersion.epoch()); + ASSERT_EQ(0u, chunkVersion.majorVersion()); + ASSERT_EQ(0u, chunkVersion.minorVersion()); +} + +TEST(ChunkVersionParsing, FromBSONLegacy_NoTimestamp_WithIgnoredEpoch) { + ChunkVersion chunkVersion = assertGet(ChunkVersion::parseLegacyWithField( + BSON("lastmod" << Timestamp() << "lastmodEpoch" << ChunkVersion::IGNORED().epoch()), + "lastmod")); + ASSERT_EQ(ChunkVersion::IGNORED().getTimestamp(), chunkVersion.getTimestamp()); + ASSERT_EQ(ChunkVersion::IGNORED().epoch(), chunkVersion.epoch()); + ASSERT_EQ(0u, chunkVersion.majorVersion()); + ASSERT_EQ(0u, chunkVersion.minorVersion()); +} + +TEST(ChunkVersionParsing, FromBSONLegacy_NoTimestamp_WithShardedEpoch_Throws) { + ASSERT_THROWS(uassertStatusOK(ChunkVersion::parseLegacyWithField( + BSON("lastmod" << Timestamp(Seconds(3), 4) << "lastmodEpoch" << OID::gen()), + "lastmod")), + DBException); +} - ASSERT(!chunkVersionNoEpoch.epoch().isSet()); - ASSERT_EQ(3u, chunkVersionNoEpoch.majorVersion()); - ASSERT_EQ(4u, chunkVersionNoEpoch.minorVersion()); +TEST(ChunkVersionParsing, FromBSONLegacy_WithTimestamp_NoEpoch_Throws) { + ASSERT_THROWS( + uassertStatusOK(ChunkVersion::parseLegacyWithField( + BSON("lastmod" << Timestamp(Seconds(3), 4) << "lastmodTimestamp" << Timestamp(42)), + "lastmod")), + DBException); +} + +TEST(ChunkVersionParsing, FromBSONLegacy_NoTimestamp_NoEpoch) { + ChunkVersion chunkVersion = assertGet( + ChunkVersion::parseLegacyWithField(BSON("lastmod" << Timestamp(Seconds(3), 4)), "lastmod")); + ASSERT_EQ(Timestamp(), chunkVersion.getTimestamp()); + ASSERT(!chunkVersion.epoch().isSet()); + ASSERT_EQ(3u, chunkVersion.majorVersion()); + ASSERT_EQ(4u, chunkVersion.minorVersion()); } TEST(ChunkVersionComparison, EqualityOperators) { @@ -161,19 +194,20 @@ TEST(ChunkVersionComparison, OlderThan) { } TEST(ChunkVersionConstruction, CreateWithLargeValues) { - const auto minorVersion = std::numeric_limits<uint32_t>::max(); - const uint32_t majorVersion = 1 << 24; + const uint32_t majorVersion = std::numeric_limits<uint32_t>::max(); + const uint32_t minorVersion = std::numeric_limits<uint32_t>::max(); const auto epoch = OID::gen(); ChunkVersion version(majorVersion, minorVersion, epoch, Timestamp(1, 1)); ASSERT_EQ(majorVersion, version.majorVersion()); ASSERT_EQ(minorVersion, version.minorVersion()); ASSERT_EQ(epoch, version.epoch()); + ASSERT_EQ(Timestamp(1, 1), version.getTimestamp()); } TEST(ChunkVersionManipulation, ThrowsErrorIfOverflowIsAttemptedForMajorVersion) { - const uint32_t minorVersion = 0; const uint32_t majorVersion = std::numeric_limits<uint32_t>::max(); + const uint32_t minorVersion = 0; const auto epoch = OID::gen(); ChunkVersion version(majorVersion, minorVersion, epoch, Timestamp(1, 1)); @@ -185,8 +219,8 @@ TEST(ChunkVersionManipulation, ThrowsErrorIfOverflowIsAttemptedForMajorVersion) } TEST(ChunkVersionManipulation, ThrowsErrorIfOverflowIsAttemptedForMinorVersion) { - const uint32_t minorVersion = std::numeric_limits<uint32_t>::max(); const uint32_t majorVersion = 0; + const uint32_t minorVersion = std::numeric_limits<uint32_t>::max(); const auto epoch = OID::gen(); ChunkVersion version(majorVersion, minorVersion, epoch, Timestamp(1, 1)); @@ -196,5 +230,6 @@ TEST(ChunkVersionManipulation, ThrowsErrorIfOverflowIsAttemptedForMinorVersion) ASSERT_THROWS_CODE(version.incMinor(), DBException, 31181); } + } // namespace } // namespace mongo |