diff options
author | Lingzhi Deng <lingzhi.deng@mongodb.com> | 2020-07-06 14:36:42 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-07-15 20:07:53 +0000 |
commit | 5446d926d8d21f3dd583b26980308519a2b67708 (patch) | |
tree | 9c9c05dcc1b669a587db47e57ba9aa47d84b0c10 /src/mongo | |
parent | c26b0c167e8db6506e547e1daaa51f09a6e6c1bd (diff) | |
download | mongo-5446d926d8d21f3dd583b26980308519a2b67708.tar.gz |
SERVER-49269: Add 'previousVersion' field to FCV document when in downgrading states
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/commands/SConscript | 4 | ||||
-rw-r--r-- | src/mongo/db/commands/feature_compatibility_version.cpp | 180 | ||||
-rw-r--r-- | src/mongo/db/commands/feature_compatibility_version.h | 37 | ||||
-rw-r--r-- | src/mongo/db/commands/feature_compatibility_version_document.idl | 69 | ||||
-rw-r--r-- | src/mongo/db/commands/feature_compatibility_version_parser.cpp | 181 | ||||
-rw-r--r-- | src/mongo/db/commands/feature_compatibility_version_parser.h | 13 | ||||
-rw-r--r-- | src/mongo/db/commands/set_feature_compatibility_version_command.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/repair_database_and_check_version.cpp | 18 | ||||
-rw-r--r-- | src/mongo/db/repl/initial_syncer_test.cpp | 39 | ||||
-rw-r--r-- | src/mongo/db/server_options.h | 10 | ||||
-rw-r--r-- | src/mongo/shell/feature_compatibility_version.js | 19 |
11 files changed, 357 insertions, 225 deletions
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 27bafef5119..5e3591ea3f1 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -81,12 +81,16 @@ env.Library( source=[ "feature_compatibility_version_parser.cpp", "feature_compatibility_version_command_parser.cpp", + env.Idlc('feature_compatibility_version_document.idl')[0], ], LIBDEPS=[ '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/db/command_generic_argument', '$BUILD_DIR/mongo/db/namespace_string', ], + LIBDEPS_PRIVATE=[ + '$BUILD_DIR/mongo/idl/idl_parser', + ], ) # Commands available in every process that executes commands diff --git a/src/mongo/db/commands/feature_compatibility_version.cpp b/src/mongo/db/commands/feature_compatibility_version.cpp index d7f3aef5d42..82acfd8f676 100644 --- a/src/mongo/db/commands/feature_compatibility_version.cpp +++ b/src/mongo/db/commands/feature_compatibility_version.cpp @@ -35,6 +35,7 @@ #include "mongo/base/status.h" #include "mongo/db/catalog_raii.h" +#include "mongo/db/commands/feature_compatibility_version_document_gen.h" #include "mongo/db/commands/feature_compatibility_version_documentation.h" #include "mongo/db/commands/feature_compatibility_version_gen.h" #include "mongo/db/commands/feature_compatibility_version_parser.h" @@ -66,34 +67,70 @@ Lock::ResourceMutex FeatureCompatibilityVersion::fcvLock("featureCompatibilityVe MONGO_FAIL_POINT_DEFINE(hangBeforeAbortingRunningTransactionsOnFCVDowngrade); -void FeatureCompatibilityVersion::setTargetUpgrade(OperationContext* opCtx) { - // Sets both 'version' and 'targetVersion' fields. - _runUpdateCommand(opCtx, [](auto updateMods) { - updateMods.append(FeatureCompatibilityVersionParser::kVersionField, - FeatureCompatibilityVersionParser::kVersion44); - updateMods.append(FeatureCompatibilityVersionParser::kTargetVersionField, - FeatureCompatibilityVersionParser::kVersion451); - }); +/** + * Build update command for featureCompatibilityVersion document updates. + */ +void runUpdateCommand(OperationContext* opCtx, const FeatureCompatibilityVersionDocument& fcvDoc) { + DBDirectClient client(opCtx); + NamespaceString nss(NamespaceString::kServerConfigurationNamespace); + + BSONObjBuilder updateCmd; + updateCmd.append("update", nss.coll()); + { + BSONArrayBuilder updates(updateCmd.subarrayStart("updates")); + { + BSONObjBuilder updateSpec(updates.subobjStart()); + { + BSONObjBuilder queryFilter(updateSpec.subobjStart("q")); + queryFilter.append("_id", FeatureCompatibilityVersionParser::kParameterName); + } + { + BSONObjBuilder updateMods(updateSpec.subobjStart("u")); + fcvDoc.serialize(&updateMods); + } + updateSpec.appendBool("upsert", true); + } + } + auto timeout = opCtx->getWriteConcern().usedDefault ? WriteConcernOptions::kNoTimeout + : opCtx->getWriteConcern().wTimeout; + auto newWC = WriteConcernOptions( + WriteConcernOptions::kMajority, WriteConcernOptions::SyncMode::UNSET, timeout); + updateCmd.append(WriteConcernOptions::kWriteConcernField, newWC.toBSON()); + + // Update the featureCompatibilityVersion document stored in the server configuration + // collection. + BSONObj updateResult; + client.runCommand(nss.db().toString(), updateCmd.obj(), updateResult); + uassertStatusOK(getStatusFromWriteCommandReply(updateResult)); } -void FeatureCompatibilityVersion::setTargetDowngrade(OperationContext* opCtx) { + +void FeatureCompatibilityVersion::setTargetUpgradeFrom( + OperationContext* opCtx, ServerGlobalParams::FeatureCompatibility::Version fromVersion) { // Sets both 'version' and 'targetVersion' fields. - _runUpdateCommand(opCtx, [](auto updateMods) { - updateMods.append(FeatureCompatibilityVersionParser::kVersionField, - FeatureCompatibilityVersionParser::kVersion44); - updateMods.append(FeatureCompatibilityVersionParser::kTargetVersionField, - FeatureCompatibilityVersionParser::kVersion44); - }); + FeatureCompatibilityVersionDocument fcvDoc; + fcvDoc.setVersion(fromVersion); + fcvDoc.setTargetVersion(ServerGlobalParams::FeatureCompatibility::kLatest); + runUpdateCommand(opCtx, fcvDoc); } -void FeatureCompatibilityVersion::unsetTargetUpgradeOrDowngrade(OperationContext* opCtx, - StringData version) { - _validateVersion(version); +void FeatureCompatibilityVersion::setTargetDowngrade( + OperationContext* opCtx, ServerGlobalParams::FeatureCompatibility::Version version) { + // Sets 'version', 'targetVersion' and 'previousVersion' fields. + FeatureCompatibilityVersionDocument fcvDoc; + fcvDoc.setVersion(version); + fcvDoc.setTargetVersion(version); + fcvDoc.setPreviousVersion(ServerGlobalParams::FeatureCompatibility::kLatest); + runUpdateCommand(opCtx, fcvDoc); +} - // Updates 'version' field, while also unsetting the 'targetVersion' field. - _runUpdateCommand(opCtx, [version](auto updateMods) { - updateMods.append(FeatureCompatibilityVersionParser::kVersionField, version); - }); +void FeatureCompatibilityVersion::unsetTargetUpgradeOrDowngrade( + OperationContext* opCtx, ServerGlobalParams::FeatureCompatibility::Version version) { + // Updates 'version' field, while also unsetting the 'targetVersion' field and the + // 'previousVersion' field. + FeatureCompatibilityVersionDocument fcvDoc; + fcvDoc.setVersion(version); + runUpdateCommand(opCtx, fcvDoc); } void FeatureCompatibilityVersion::setIfCleanStartup(OperationContext* opCtx, @@ -117,17 +154,19 @@ void FeatureCompatibilityVersion::setIfCleanStartup(OperationContext* opCtx, uassertStatusOK(storageInterface->createCollection(opCtx, nss, options)); } + FeatureCompatibilityVersionDocument fcvDoc; + if (storeUpgradeVersion) { + fcvDoc.setVersion(ServerGlobalParams::FeatureCompatibility::kLatest); + } else { + fcvDoc.setVersion(ServerGlobalParams::FeatureCompatibility::kLastLTS); + } + // We then insert the featureCompatibilityVersion document into the server configuration // collection. The server parameter will be updated on commit by the op observer. uassertStatusOK(storageInterface->insertDocument( opCtx, nss, - repl::TimestampedBSONObj{ - BSON("_id" << FeatureCompatibilityVersionParser::kParameterName - << FeatureCompatibilityVersionParser::kVersionField - << (storeUpgradeVersion ? FeatureCompatibilityVersionParser::kVersion451 - : FeatureCompatibilityVersionParser::kVersion44)), - Timestamp()}, + repl::TimestampedBSONObj{fcvDoc.toBSON(), Timestamp()}, repl::OpTime::kUninitializedTerm)); // No timestamp or term because this write is not // replicated. } @@ -257,51 +296,6 @@ void FeatureCompatibilityVersion::onReplicationRollback(OperationContext* opCtx) } } -void FeatureCompatibilityVersion::_validateVersion(StringData version) { - uassert(40284, - str::stream() << "featureCompatibilityVersion must be '" - << FeatureCompatibilityVersionParser::kVersion451 << "' or '" - << FeatureCompatibilityVersionParser::kVersion44 << "'. See " - << feature_compatibility_version_documentation::kCompatibilityLink << ".", - version == FeatureCompatibilityVersionParser::kVersion451 || - version == FeatureCompatibilityVersionParser::kVersion44); -} - -void FeatureCompatibilityVersion::_runUpdateCommand(OperationContext* opCtx, - UpdateBuilder builder) { - DBDirectClient client(opCtx); - NamespaceString nss(NamespaceString::kServerConfigurationNamespace); - - BSONObjBuilder updateCmd; - updateCmd.append("update", nss.coll()); - { - BSONArrayBuilder updates(updateCmd.subarrayStart("updates")); - { - BSONObjBuilder updateSpec(updates.subobjStart()); - { - BSONObjBuilder queryFilter(updateSpec.subobjStart("q")); - queryFilter.append("_id", FeatureCompatibilityVersionParser::kParameterName); - } - { - BSONObjBuilder updateMods(updateSpec.subobjStart("u")); - builder(std::move(updateMods)); - } - updateSpec.appendBool("upsert", true); - } - } - auto timeout = opCtx->getWriteConcern().usedDefault ? WriteConcernOptions::kNoTimeout - : opCtx->getWriteConcern().wTimeout; - auto newWC = WriteConcernOptions( - WriteConcernOptions::kMajority, WriteConcernOptions::SyncMode::UNSET, timeout); - updateCmd.append(WriteConcernOptions::kWriteConcernField, newWC.toBSON()); - - // Update the featureCompatibilityVersion document stored in the server configuration - // collection. - BSONObj updateResult; - client.runCommand(nss.db().toString(), updateCmd.obj(), updateResult); - uassertStatusOK(getStatusFromWriteCommandReply(updateResult)); -} - /** * Read-only server parameter for featureCompatibilityVersion. */ @@ -318,38 +312,28 @@ void FeatureCompatibilityVersionParameter::append(OperationContext* opCtx, str::stream() << name << " is not yet known.", serverGlobalParams.featureCompatibility.isVersionInitialized()); + FeatureCompatibilityVersionDocument fcvDoc; BSONObjBuilder featureCompatibilityVersionBuilder(b.subobjStart(name)); - switch (serverGlobalParams.featureCompatibility.getVersion()) { + auto version = serverGlobalParams.featureCompatibility.getVersion(); + switch (version) { case ServerGlobalParams::FeatureCompatibility::kLatest: - featureCompatibilityVersionBuilder.append( - FeatureCompatibilityVersionParser::kVersionField, - FeatureCompatibilityVersionParser::kVersion451); - return; - case ServerGlobalParams::FeatureCompatibility::Version::kUpgradingFrom44To451: - featureCompatibilityVersionBuilder.append( - FeatureCompatibilityVersionParser::kVersionField, - FeatureCompatibilityVersionParser::kVersion44); - featureCompatibilityVersionBuilder.append( - FeatureCompatibilityVersionParser::kTargetVersionField, - FeatureCompatibilityVersionParser::kVersion451); - return; - case ServerGlobalParams::FeatureCompatibility::Version::kDowngradingFrom451To44: - featureCompatibilityVersionBuilder.append( - FeatureCompatibilityVersionParser::kVersionField, - FeatureCompatibilityVersionParser::kVersion44); - featureCompatibilityVersionBuilder.append( - FeatureCompatibilityVersionParser::kTargetVersionField, - FeatureCompatibilityVersionParser::kVersion44); - return; case ServerGlobalParams::FeatureCompatibility::kLastLTS: - featureCompatibilityVersionBuilder.append( - FeatureCompatibilityVersionParser::kVersionField, - FeatureCompatibilityVersionParser::kVersion44); - return; + fcvDoc.setVersion(version); + break; + case ServerGlobalParams::FeatureCompatibility::kUpgradingFromLastLTSToLatest: + fcvDoc.setVersion(ServerGlobalParams::FeatureCompatibility::kLastLTS); + fcvDoc.setTargetVersion(ServerGlobalParams::FeatureCompatibility::kLatest); + break; + case ServerGlobalParams::FeatureCompatibility::kDowngradingFromLatestToLastLTS: + fcvDoc.setVersion(ServerGlobalParams::FeatureCompatibility::kLastLTS); + fcvDoc.setTargetVersion(ServerGlobalParams::FeatureCompatibility::kLastLTS); + fcvDoc.setPreviousVersion(ServerGlobalParams::FeatureCompatibility::kLatest); + break; case ServerGlobalParams::FeatureCompatibility::Version::kUnsetDefault44Behavior: // getVersion() does not return this value. MONGO_UNREACHABLE; } + featureCompatibilityVersionBuilder.appendElements(fcvDoc.toBSON().removeField("_id")); } Status FeatureCompatibilityVersionParameter::setFromString(const std::string&) { diff --git a/src/mongo/db/commands/feature_compatibility_version.h b/src/mongo/db/commands/feature_compatibility_version.h index f4d0263f029..b2076f6de7b 100644 --- a/src/mongo/db/commands/feature_compatibility_version.h +++ b/src/mongo/db/commands/feature_compatibility_version.h @@ -52,25 +52,29 @@ public: static Lock::ResourceMutex fcvLock; /** - * Records intent to perform a 4.4 -> 4.5.1 upgrade by updating the on-disk feature - * compatibility version document to have 'version'=4.4, 'targetVersion'=4.5.1. - * Should be called before schemas are modified. + * Records intent to perform a currentVersion -> kLatest upgrade by updating the on-disk + * feature compatibility version document to have 'version'=currentVersion, + * 'targetVersion'=kLatest. Should be called before schemas are modified. */ - static void setTargetUpgrade(OperationContext* opCtx); + static void setTargetUpgradeFrom(OperationContext* opCtx, + ServerGlobalParams::FeatureCompatibility::Version fromVersion); /** - * Records intent to perform a 4.5.1 -> 4.4 downgrade by updating the on-disk feature - * compatibility version document to have 'version'=4.4, 'targetVersion'=4.4. - * Should be called before schemas are modified. + * Records intent to perform a downgrade from the latest version by updating the on-disk feature + * compatibility version document to have 'version'=version, 'targetVersion'=version and + * 'previousVersion'=kLatest. Should be called before schemas are modified. */ - static void setTargetDowngrade(OperationContext* opCtx); + static void setTargetDowngrade(OperationContext* opCtx, + ServerGlobalParams::FeatureCompatibility::Version version); /** - * Records the completion of a 4.4 <-> 4.5.1 upgrade or downgrade by updating the on-disk + * Records the completion of a upgrade or downgrade by updating the on-disk * feature compatibility version document to have 'version'=version and unsetting the - * 'targetVersion' field. Should be called after schemas are modified. + * 'targetVersion' field and the 'previousVersion' field. Should be called after schemas are + * modified. */ - static void unsetTargetUpgradeOrDowngrade(OperationContext* opCtx, StringData version); + static void unsetTargetUpgradeOrDowngrade( + OperationContext* opCtx, ServerGlobalParams::FeatureCompatibility::Version version); /** * If there are no non-local databases, store the featureCompatibilityVersion document. If we @@ -106,17 +110,6 @@ public: private: /** - * Validate version. Uasserts if invalid. - */ - static void _validateVersion(StringData version); - - /** - * Build update command. - */ - typedef std::function<void(BSONObjBuilder)> UpdateBuilder; - static void _runUpdateCommand(OperationContext* opCtx, UpdateBuilder callback); - - /** * Set the FCV to newVersion, making sure to close any outgoing connections with incompatible * servers and closing open transactions if necessary. Increments the server TopologyVersion. */ diff --git a/src/mongo/db/commands/feature_compatibility_version_document.idl b/src/mongo/db/commands/feature_compatibility_version_document.idl new file mode 100644 index 00000000000..bf4fc87bc36 --- /dev/null +++ b/src/mongo/db/commands/feature_compatibility_version_document.idl @@ -0,0 +1,69 @@ +#Copyright(C) 2020 - present MongoDB, Inc. +# +#This program is free software : you can redistribute it and / or modify +#it under the terms of the Server Side Public License, version 1, +#as published by MongoDB, Inc. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +#Server Side Public License for more details. +# +#You should have received a copy of the Server Side Public License +#along with this program.If not, see +# <http: //www.mongodb.com/licensing/server-side-public-license>. +# +#As a special exception, the copyright holders give permission to link the +#code of portions of this program with the OpenSSL library under certain +#conditions as described in each individual source file and distribute +#linked combinations including the program with the OpenSSL library.You +#must comply with the Server Side Public License in all respects for +#all of the code used other than as permitted herein.If you modify file(s) +#with this exception, you may extend this exception to your version of the +#file(s), but you are not obligated to do so.If you do not wish to do so, +#delete this exception statement from your version.If you delete this +#exception statement from all source files in the program, then also delete +#it in the license file. +# +global: + cpp_namespace: "mongo" + cpp_includes: + - "mongo/db/commands/feature_compatibility_version_parser.h" + + +imports: + - "mongo/idl/basic_types.idl" + +types: + version_string: + bson_serialization_type: string + description: >- + Ensures that the version strings in featureCompatibilityVersion documents + serialize/deserialize to a fixed set of string values + cpp_type: "ServerGlobalParams::FeatureCompatibility::Version" + serializer: "::mongo::FeatureCompatibilityVersionParser::serializeVersion" + deserializer: "mongo::FeatureCompatibilityVersionParser::parseVersion" + +structs: + featureCompatibilityVersionDocument: + description: "featureCompatibilityVersion on-disk document format" + fields: + _id: + type: string + description: "Key of the featureCompatibilityVersion state singleton document" + default: '"featureCompatibilityVersion"' + version: + description: "Effective featureCompatibilityVersion" + type: version_string + targetVersion: + description: "Target featureCompatibilityVersion for upgrade/downgrade" + type: version_string + optional: true + previousVersion: + description: >- + Previous featureCompatibilityVersion for downgrading states, will always be kLatest when + present + type: version_string + optional: true + validator: + callback: "FeatureCompatibilityVersionParser::validatePreviousVersionField" diff --git a/src/mongo/db/commands/feature_compatibility_version_parser.cpp b/src/mongo/db/commands/feature_compatibility_version_parser.cpp index 97fc21eb67e..650557ca644 100644 --- a/src/mongo/db/commands/feature_compatibility_version_parser.cpp +++ b/src/mongo/db/commands/feature_compatibility_version_parser.cpp @@ -33,107 +33,150 @@ #include "mongo/base/status.h" #include "mongo/bson/bsonobj.h" +#include "mongo/db/commands/feature_compatibility_version_document_gen.h" #include "mongo/db/commands/feature_compatibility_version_documentation.h" #include "mongo/db/namespace_string.h" namespace mongo { -constexpr StringData FeatureCompatibilityVersionParser::kVersion44; -constexpr StringData FeatureCompatibilityVersionParser::kVersion451; -constexpr StringData FeatureCompatibilityVersionParser::kVersionDowngradingFrom451To44; -constexpr StringData FeatureCompatibilityVersionParser::kVersionUpgradingFrom44To451; -constexpr StringData FeatureCompatibilityVersionParser::kVersionUnset; - constexpr StringData FeatureCompatibilityVersionParser::kParameterName; -constexpr StringData FeatureCompatibilityVersionParser::kVersionField; -constexpr StringData FeatureCompatibilityVersionParser::kTargetVersionField; + +constexpr StringData FeatureCompatibilityVersionParser::kLastLTS; +constexpr StringData FeatureCompatibilityVersionParser::kLastContinuous; +constexpr StringData FeatureCompatibilityVersionParser::kLatest; + +ServerGlobalParams::FeatureCompatibility::Version FeatureCompatibilityVersionParser::parseVersion( + StringData versionString) { + if (versionString == kLastLTS) { + return ServerGlobalParams::FeatureCompatibility::kLastLTS; + } + if (versionString == kLastContinuous) { + return ServerGlobalParams::FeatureCompatibility::kLastContinuous; + } + if (versionString == kLatest) { + return ServerGlobalParams::FeatureCompatibility::kLatest; + } + uasserted(4926900, + str::stream() << "Invalid value for " << kParameterName << "document in " + << NamespaceString::kServerConfigurationNamespace.toString() + << ", found " << versionString << ", expected '" << kLastLTS << "' or '" + << kLastContinuous << "' or '" << kLatest << ". See " + << feature_compatibility_version_documentation::kCompatibilityLink + << "."); +} + +StringData FeatureCompatibilityVersionParser::serializeVersion( + ServerGlobalParams::FeatureCompatibility::Version version) { + if (version == ServerGlobalParams::FeatureCompatibility::kLastLTS) { + return kLastLTS; + } + if (version == ServerGlobalParams::FeatureCompatibility::kLastContinuous) { + return kLastContinuous; + } + if (version == ServerGlobalParams::FeatureCompatibility::kLatest) { + return kLatest; + } + // It is a bug if we hit here. + invariant(false, "Invalid version value for featureCompatibilityVersion documents"); + MONGO_UNREACHABLE +} + +Status FeatureCompatibilityVersionParser::validatePreviousVersionField( + ServerGlobalParams::FeatureCompatibility::Version version) { + if (version == ServerGlobalParams::FeatureCompatibility::kLatest) { + return Status::OK(); + } + return Status(ErrorCodes::Error(4926901), + "when present, 'previousVersion' field must be the latest binary version"); +} StatusWith<ServerGlobalParams::FeatureCompatibility::Version> FeatureCompatibilityVersionParser::parse(const BSONObj& featureCompatibilityVersionDoc) { - ServerGlobalParams::FeatureCompatibility::Version version = - ServerGlobalParams::FeatureCompatibility::Version::kUnsetDefault44Behavior; - std::string versionString; - std::string targetVersionString; - - for (auto&& elem : featureCompatibilityVersionDoc) { - auto fieldName = elem.fieldNameStringData(); - if (fieldName == "_id") { - continue; - } else if (fieldName == kVersionField || fieldName == kTargetVersionField) { - if (elem.type() != BSONType::String) { - return Status(ErrorCodes::TypeMismatch, + try { + auto fcvDoc = FeatureCompatibilityVersionDocument::parse( + IDLParserErrorContext("FeatureCompatibilityVersionParser"), + featureCompatibilityVersionDoc); + auto version = fcvDoc.getVersion(); + auto targetVersion = fcvDoc.getTargetVersion(); + auto previousVersion = fcvDoc.getPreviousVersion(); + + // Downgrading FCV. + if ((version == ServerGlobalParams::FeatureCompatibility::kLastLTS || + version == ServerGlobalParams::FeatureCompatibility::kLastContinuous) && + version == targetVersion) { + // Downgrading FCV must have a "previousVersion" field. + if (!previousVersion) { + return Status(ErrorCodes::Error(4926902), str::stream() - << fieldName << " must be of type String, but was of type " - << typeName(elem.type()) << ". Contents of " << kParameterName + << "Missing field " + << FeatureCompatibilityVersionDocument::kPreviousVersionFieldName + << " in downgrading states for " << kParameterName << " document in " << NamespaceString::kServerConfigurationNamespace.toString() << ": " << featureCompatibilityVersionDoc << ". See " << feature_compatibility_version_documentation::kCompatibilityLink << "."); } + if (version == ServerGlobalParams::FeatureCompatibility::kLastLTS) { + // Downgrading to last-lts. + return ServerGlobalParams::FeatureCompatibility::kDowngradingFromLatestToLastLTS; + } else { + return ServerGlobalParams::FeatureCompatibility:: + kDowngradingFromLatestToLastContinuous; + } + } + + // Non-downgrading FCV must not have a "previousVersion" field. + if (previousVersion) { + return Status(ErrorCodes::Error(4926903), + str::stream() + << "Unexpected field " + << FeatureCompatibilityVersionDocument::kPreviousVersionFieldName + << " in non-downgrading states for " << kParameterName + << " document in " + << NamespaceString::kServerConfigurationNamespace.toString() << ": " + << featureCompatibilityVersionDoc << ". See " + << feature_compatibility_version_documentation::kCompatibilityLink + << "."); + } - if (elem.String() != kVersion451 && elem.String() != kVersion44) { - return Status(ErrorCodes::BadValue, + // Upgrading FCV. + if (targetVersion) { + // For upgrading FCV, "targetVersion" must be kLatest and "version" must be + // kLastContinuous or kLastLTS. + if (targetVersion != ServerGlobalParams::FeatureCompatibility::kLatest || + version == ServerGlobalParams::FeatureCompatibility::kLatest) { + return Status(ErrorCodes::Error(4926904), str::stream() - << "Invalid value for " << fieldName << ", found " - << elem.String() << ", expected '" << kVersion451 << "' or '" - << kVersion44 << "'. Contents of " << kParameterName - << " document in " + << "Invalid " << kParameterName << " document in " << NamespaceString::kServerConfigurationNamespace.toString() << ": " << featureCompatibilityVersionDoc << ". See " << feature_compatibility_version_documentation::kCompatibilityLink << "."); } - if (fieldName == kVersionField) { - versionString = elem.String(); - } else if (fieldName == kTargetVersionField) { - targetVersionString = elem.String(); + if (version == ServerGlobalParams::FeatureCompatibility::kLastLTS) { + return ServerGlobalParams::FeatureCompatibility::kUpgradingFromLastLTSToLatest; + } else { + invariant(version == ServerGlobalParams::FeatureCompatibility::kLastContinuous); + return ServerGlobalParams::FeatureCompatibility:: + kUpgradingFromLastContinuousToLatest; } - } else { - return Status(ErrorCodes::BadValue, - str::stream() - << "Unrecognized field '" << fieldName << "'. Contents of " - << kParameterName << " document in " - << NamespaceString::kServerConfigurationNamespace.toString() << ": " - << featureCompatibilityVersionDoc << ". See " - << feature_compatibility_version_documentation::kCompatibilityLink - << "."); } - } - if (versionString == kVersion44) { - if (targetVersionString == kVersion451) { - version = ServerGlobalParams::FeatureCompatibility::Version::kUpgradingFrom44To451; - } else if (targetVersionString == kVersion44) { - version = ServerGlobalParams::FeatureCompatibility::Version::kDowngradingFrom451To44; - } else { - version = ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo44; - } - } else if (versionString == kVersion451) { - if (targetVersionString == kVersion451 || targetVersionString == kVersion44) { - return Status(ErrorCodes::BadValue, - str::stream() - << "Invalid state for " << kParameterName << " document in " - << NamespaceString::kServerConfigurationNamespace.toString() << ": " - << featureCompatibilityVersionDoc << ". See " - << feature_compatibility_version_documentation::kCompatibilityLink - << "."); - } else { - version = ServerGlobalParams::FeatureCompatibility::Version::kVersion451; - } - } else { - return Status(ErrorCodes::BadValue, - str::stream() - << "Missing required field '" << kVersionField << "''. Contents of " - << kParameterName << " document in " + // No "targetVersion" or "previousVersion" field. + return version; + } catch (const DBException& e) { + auto status = e.toStatus(); + status.addContext(str::stream() + << "Invalid " << kParameterName << " document in " << NamespaceString::kServerConfigurationNamespace.toString() << ": " << featureCompatibilityVersionDoc << ". See " << feature_compatibility_version_documentation::kCompatibilityLink << "."); + return status; } - - return version; + MONGO_UNREACHABLE } } // namespace mongo diff --git a/src/mongo/db/commands/feature_compatibility_version_parser.h b/src/mongo/db/commands/feature_compatibility_version_parser.h index 01f25fe6f5d..2fdaf962c49 100644 --- a/src/mongo/db/commands/feature_compatibility_version_parser.h +++ b/src/mongo/db/commands/feature_compatibility_version_parser.h @@ -47,8 +47,17 @@ public: static constexpr StringData kVersionUnset = "Unset"_sd; static constexpr StringData kParameterName = "featureCompatibilityVersion"_sd; - static constexpr StringData kVersionField = "version"_sd; - static constexpr StringData kTargetVersionField = "targetVersion"_sd; + + static constexpr StringData kLastLTS = kVersion44; + static constexpr StringData kLastContinuous = kVersion44; + static constexpr StringData kLatest = kVersion451; + + static ServerGlobalParams::FeatureCompatibility::Version parseVersion(StringData versionString); + + static StringData serializeVersion(ServerGlobalParams::FeatureCompatibility::Version version); + + static Status validatePreviousVersionField( + ServerGlobalParams::FeatureCompatibility::Version version); /** * Parses the featureCompatibilityVersion document from the server configuration collection diff --git a/src/mongo/db/commands/set_feature_compatibility_version_command.cpp b/src/mongo/db/commands/set_feature_compatibility_version_command.cpp index f34a64d157c..87b0540ebea 100644 --- a/src/mongo/db/commands/set_feature_compatibility_version_command.cpp +++ b/src/mongo/db/commands/set_feature_compatibility_version_command.cpp @@ -200,7 +200,8 @@ public: return true; } - FeatureCompatibilityVersion::setTargetUpgrade(opCtx); + FeatureCompatibilityVersion::setTargetUpgradeFrom( + opCtx, ServerGlobalParams::FeatureCompatibility::kLastLTS); { // Take the global lock in S mode to create a barrier for operations taking the @@ -237,7 +238,8 @@ public: } hangWhileUpgrading.pauseWhileSet(opCtx); - FeatureCompatibilityVersion::unsetTargetUpgradeOrDowngrade(opCtx, requestedVersion); + FeatureCompatibilityVersion::unsetTargetUpgradeOrDowngrade( + opCtx, FeatureCompatibilityVersionParser::parseVersion(requestedVersion)); } else if (requestedVersion == FeatureCompatibilityVersionParser::kVersion44) { uassert(ErrorCodes::IllegalOperation, "cannot initiate setting featureCompatibilityVersion to 4.4 while a previous " @@ -279,7 +281,8 @@ public: "nodes"); LOGV2(4637905, "The current replica set config has been propagated to all nodes."); - FeatureCompatibilityVersion::setTargetDowngrade(opCtx); + FeatureCompatibilityVersion::setTargetDowngrade( + opCtx, FeatureCompatibilityVersionParser::parseVersion(requestedVersion)); { // Take the global lock in S mode to create a barrier for operations taking the @@ -317,7 +320,8 @@ public: } hangWhileDowngrading.pauseWhileSet(opCtx); - FeatureCompatibilityVersion::unsetTargetUpgradeOrDowngrade(opCtx, requestedVersion); + FeatureCompatibilityVersion::unsetTargetUpgradeOrDowngrade( + opCtx, FeatureCompatibilityVersionParser::parseVersion(requestedVersion)); } return true; diff --git a/src/mongo/db/repair_database_and_check_version.cpp b/src/mongo/db/repair_database_and_check_version.cpp index b7a56e42764..3624ce8b6f3 100644 --- a/src/mongo/db/repair_database_and_check_version.cpp +++ b/src/mongo/db/repair_database_and_check_version.cpp @@ -41,6 +41,7 @@ #include "mongo/db/catalog/database_holder.h" #include "mongo/db/catalog/multi_index_block.h" #include "mongo/db/commands/feature_compatibility_version.h" +#include "mongo/db/commands/feature_compatibility_version_document_gen.h" #include "mongo/db/commands/feature_compatibility_version_documentation.h" #include "mongo/db/commands/feature_compatibility_version_parser.h" #include "mongo/db/concurrency/write_conflict_exception.h" @@ -115,22 +116,19 @@ Status restoreMissingFeatureCompatibilityVersionDocument(OperationContext* opCtx fcvColl, BSON("_id" << FeatureCompatibilityVersionParser::kParameterName), featureCompatibilityVersion)) { - LOGV2(21000, + LOGV2(4926905, "Re-creating featureCompatibilityVersion document that was deleted. Creating new " - "document with version " - "{FeatureCompatibilityVersionParser_kVersion44}.", - "Re-creating featureCompatibilityVersion document that was deleted", - "version"_attr = FeatureCompatibilityVersionParser::kVersion44); + "document with last LTS version.", + "version"_attr = FeatureCompatibilityVersionParser::kLastLTS); - BSONObj fcvObj = BSON("_id" << FeatureCompatibilityVersionParser::kParameterName - << FeatureCompatibilityVersionParser::kVersionField - << FeatureCompatibilityVersionParser::kVersion44); + FeatureCompatibilityVersionDocument fcvDoc; + fcvDoc.setVersion(ServerGlobalParams::FeatureCompatibility::kLastLTS); writeConflictRetry(opCtx, "insertFCVDocument", fcvNss.ns(), [&] { WriteUnitOfWork wunit(opCtx); OpDebug* const nullOpDebug = nullptr; - uassertStatusOK( - fcvColl->insertDocument(opCtx, InsertStatement(fcvObj), nullOpDebug, false)); + uassertStatusOK(fcvColl->insertDocument( + opCtx, InsertStatement(fcvDoc.toBSON()), nullOpDebug, false)); wunit.commit(); }); } diff --git a/src/mongo/db/repl/initial_syncer_test.cpp b/src/mongo/db/repl/initial_syncer_test.cpp index 8adea565621..b4ddd113e96 100644 --- a/src/mongo/db/repl/initial_syncer_test.cpp +++ b/src/mongo/db/repl/initial_syncer_test.cpp @@ -36,6 +36,7 @@ #include <ostream> #include "mongo/db/client.h" +#include "mongo/db/commands/feature_compatibility_version_document_gen.h" #include "mongo/db/commands/feature_compatibility_version_parser.h" #include "mongo/db/index_builds_coordinator_mongod.h" #include "mongo/db/json.h" @@ -676,9 +677,9 @@ void assertFCVRequest(RemoteCommandRequest request) { } void InitialSyncerTest::processSuccessfulFCVFetcherResponseLastStable() { - auto docs = {BSON("_id" << FeatureCompatibilityVersionParser::kParameterName << "version" - << FeatureCompatibilityVersionParser::kVersion44)}; - processSuccessfulFCVFetcherResponse(docs); + FeatureCompatibilityVersionDocument fcvDoc; + fcvDoc.setVersion(ServerGlobalParams::FeatureCompatibility::kLastLTS); + processSuccessfulFCVFetcherResponse({fcvDoc.toBSON()}); } void InitialSyncerTest::processSuccessfulFCVFetcherResponse(std::vector<BSONObj> docs) { @@ -1895,8 +1896,9 @@ TEST_F(InitialSyncerTest, TEST_F(InitialSyncerTest, InitialSyncerReturnsTooManyMatchingDocumentsWhenFCVFetcherReturnsMultipleDocuments) { - auto docs = {BSON("_id" << FeatureCompatibilityVersionParser::kParameterName << "version" - << FeatureCompatibilityVersionParser::kVersion44), + FeatureCompatibilityVersionDocument fcvDoc; + fcvDoc.setVersion(ServerGlobalParams::FeatureCompatibility::kLastLTS); + auto docs = {fcvDoc.toBSON(), BSON("_id" << "other")}; runInitialSyncWithBadFCVResponse(docs, ErrorCodes::TooManyMatchingDocuments); @@ -1904,24 +1906,25 @@ TEST_F(InitialSyncerTest, TEST_F(InitialSyncerTest, InitialSyncerReturnsIncompatibleServerVersionWhenFCVFetcherReturnsUpgradeTargetVersion) { - auto docs = {BSON("_id" << FeatureCompatibilityVersionParser::kParameterName << "version" - << FeatureCompatibilityVersionParser::kVersion44 << "targetVersion" - << FeatureCompatibilityVersionParser::kVersion451)}; - runInitialSyncWithBadFCVResponse(docs, ErrorCodes::IncompatibleServerVersion); + FeatureCompatibilityVersionDocument fcvDoc; + fcvDoc.setVersion(ServerGlobalParams::FeatureCompatibility::kLastLTS); + fcvDoc.setTargetVersion(ServerGlobalParams::FeatureCompatibility::kLatest); + runInitialSyncWithBadFCVResponse({fcvDoc.toBSON()}, ErrorCodes::IncompatibleServerVersion); } TEST_F(InitialSyncerTest, InitialSyncerReturnsIncompatibleServerVersionWhenFCVFetcherReturnsDowngradeTargetVersion) { - auto docs = {BSON("_id" << FeatureCompatibilityVersionParser::kParameterName << "version" - << FeatureCompatibilityVersionParser::kVersion44 << "targetVersion" - << FeatureCompatibilityVersionParser::kVersion44)}; - runInitialSyncWithBadFCVResponse(docs, ErrorCodes::IncompatibleServerVersion); + FeatureCompatibilityVersionDocument fcvDoc; + fcvDoc.setVersion(ServerGlobalParams::FeatureCompatibility::kLastLTS); + fcvDoc.setTargetVersion(ServerGlobalParams::FeatureCompatibility::kLastLTS); + fcvDoc.setPreviousVersion(ServerGlobalParams::FeatureCompatibility::kLatest); + runInitialSyncWithBadFCVResponse({fcvDoc.toBSON()}, ErrorCodes::IncompatibleServerVersion); } -TEST_F(InitialSyncerTest, InitialSyncerReturnsBadValueWhenFCVFetcherReturnsNoVersion) { +TEST_F(InitialSyncerTest, InitialSyncerReturnsParseErrorWhenFCVFetcherReturnsNoVersion) { auto docs = {BSON("_id" << FeatureCompatibilityVersionParser::kParameterName << "targetVersion" << FeatureCompatibilityVersionParser::kVersion44)}; - runInitialSyncWithBadFCVResponse(docs, ErrorCodes::BadValue); + runInitialSyncWithBadFCVResponse(docs, ((ErrorCodes::Error)40414)); } TEST_F(InitialSyncerTest, InitialSyncerSucceedsWhenFCVFetcherReturnsOldVersion) { @@ -1951,9 +1954,9 @@ TEST_F(InitialSyncerTest, InitialSyncerSucceedsWhenFCVFetcherReturnsOldVersion) // Oplog entry associated with the beginApplyingTimestamp. processSuccessfulLastOplogEntryFetcherResponse({makeOplogEntryObj(1)}); - auto docs = {BSON("_id" << FeatureCompatibilityVersionParser::kParameterName << "version" - << FeatureCompatibilityVersionParser::kVersion44)}; - processSuccessfulFCVFetcherResponse(docs); + FeatureCompatibilityVersionDocument fcvDoc; + fcvDoc.setVersion(ServerGlobalParams::FeatureCompatibility::kLastLTS); + processSuccessfulFCVFetcherResponse({fcvDoc.toBSON()}); } // We shut it down so we do not have to finish initial sync. If the fCV fetcher got an error, diff --git a/src/mongo/db/server_options.h b/src/mongo/db/server_options.h index 6577d946c32..05130d67482 100644 --- a/src/mongo/db/server_options.h +++ b/src/mongo/db/server_options.h @@ -204,6 +204,16 @@ struct ServerGlobalParams { static constexpr Version kLastContinuous = Version::kFullyDowngradedTo44; static constexpr Version kLastLTS = Version::kFullyDowngradedTo44; + // These constants should only be used for generic FCV references. Generic references are + // FCV references that are expected to exist across LTS binary versions. + // NOTE: DO NOT USE THEM FOR REGULAR FCV CHECKS. + static constexpr Version kUpgradingFromLastLTSToLatest = Version::kUpgradingFrom44To451; + static constexpr Version kUpgradingFromLastContinuousToLatest = + Version::kUpgradingFrom44To451; + static constexpr Version kDowngradingFromLatestToLastLTS = Version::kDowngradingFrom451To44; + static constexpr Version kDowngradingFromLatestToLastContinuous = + Version::kDowngradingFrom451To44; + /** * On startup, the featureCompatibilityVersion may not have been explicitly set yet. This * exposes the actual state of the featureCompatibilityVersion if it is uninitialized. diff --git a/src/mongo/shell/feature_compatibility_version.js b/src/mongo/shell/feature_compatibility_version.js index b014a3b90d5..a126ae9d165 100644 --- a/src/mongo/shell/feature_compatibility_version.js +++ b/src/mongo/shell/feature_compatibility_version.js @@ -16,14 +16,24 @@ var lastStableFCV = "4.4"; /** * Checks the featureCompatibilityVersion document and server parameter. The * featureCompatibilityVersion document is of the form {_id: "featureCompatibilityVersion", version: - * <required>, targetVersion: <optional>}. The getParameter result is of the form - * {featureCompatibilityVersion: {version: <required>, targetVersion: <optional>}, ok: 1}. + * <required>, targetVersion: <optional>, previousVersion: <optional>}. The getParameter result is + * of the form {featureCompatibilityVersion: {version: <required>, targetVersion: <optional>, + * previousVersion: <optional>}, ok: 1}. */ function checkFCV(adminDB, version, targetVersion) { let res = adminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); assert.commandWorked(res); assert.eq(res.featureCompatibilityVersion.version, version, tojson(res)); assert.eq(res.featureCompatibilityVersion.targetVersion, targetVersion, tojson(res)); + // When both version and targetVersion are equal to lastStableFCV, downgrade is in progress. + // This tests that previousVersion is always equal to latestFCV in downgrading states or + // undefined otherwise. + const isDowngrading = (version === lastStableFCV && targetVersion === lastStableFCV); + if (isDowngrading) { + assert.eq(res.featureCompatibilityVersion.previousVersion, latestFCV, tojson(res)); + } else { + assert.eq(res.featureCompatibilityVersion.previousVersion, undefined, tojson(res)); + } // This query specifies an explicit readConcern because some FCV tests pass a connection that // has manually run isMaster with internalClient, and mongod expects internalClients (ie. other @@ -34,6 +44,11 @@ function checkFCV(adminDB, version, targetVersion) { .next(); assert.eq(doc.version, version, tojson(doc)); assert.eq(doc.targetVersion, targetVersion, tojson(doc)); + if (isDowngrading) { + assert.eq(doc.previousVersion, latestFCV, tojson(doc)); + } else { + assert.eq(doc.previousVersion, undefined, tojson(doc)); + } } /** |