/** * Copyright (C) 2016 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * 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 * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * 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 GNU Affero General 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. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand #include "mongo/platform/basic.h" #include "mongo/db/commands/feature_compatibility_version.h" #include "mongo/base/status.h" #include "mongo/db/catalog_raii.h" #include "mongo/db/commands/feature_compatibility_version_documentation.h" #include "mongo/db/commands/feature_compatibility_version_parser.h" #include "mongo/db/dbdirectclient.h" #include "mongo/db/kill_sessions_local.h" #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" #include "mongo/db/repl/optime.h" #include "mongo/db/repl/storage_interface.h" #include "mongo/db/s/collection_sharding_state.h" #include "mongo/db/s/sharding_state.h" #include "mongo/db/server_parameters.h" #include "mongo/db/service_context.h" #include "mongo/db/storage/storage_engine.h" #include "mongo/db/wire_version.h" #include "mongo/db/write_concern_options.h" #include "mongo/executor/egress_tag_closer_manager.h" #include "mongo/rpc/get_status_from_command_result.h" #include "mongo/s/catalog_cache.h" #include "mongo/s/grid.h" #include "mongo/transport/service_entry_point.h" #include "mongo/util/log.h" namespace mongo { using repl::UnreplicatedWritesBlock; Lock::ResourceMutex FeatureCompatibilityVersion::fcvLock("featureCompatibilityVersionLock"); void FeatureCompatibilityVersion::setTargetUpgrade(OperationContext* opCtx) { // Sets both 'version' and 'targetVersion' fields. _runUpdateCommand(opCtx, [](auto updateMods) { updateMods.append(FeatureCompatibilityVersionParser::kVersionField, FeatureCompatibilityVersionParser::kVersion40); updateMods.append(FeatureCompatibilityVersionParser::kTargetVersionField, FeatureCompatibilityVersionParser::kVersion42); }); } void FeatureCompatibilityVersion::setTargetDowngrade(OperationContext* opCtx) { // Sets both 'version' and 'targetVersion' fields. _runUpdateCommand(opCtx, [](auto updateMods) { updateMods.append(FeatureCompatibilityVersionParser::kVersionField, FeatureCompatibilityVersionParser::kVersion40); updateMods.append(FeatureCompatibilityVersionParser::kTargetVersionField, FeatureCompatibilityVersionParser::kVersion40); }); } void FeatureCompatibilityVersion::unsetTargetUpgradeOrDowngrade(OperationContext* opCtx, StringData version) { _validateVersion(version); // Updates 'version' field, while also unsetting the 'targetVersion' field. _runUpdateCommand(opCtx, [version](auto updateMods) { updateMods.append(FeatureCompatibilityVersionParser::kVersionField, version); }); } void FeatureCompatibilityVersion::setIfCleanStartup(OperationContext* opCtx, repl::StorageInterface* storageInterface) { if (!isCleanStartUp()) return; // If the server was not started with --shardsvr, the default featureCompatibilityVersion on // clean startup is the upgrade version. If it was started with --shardsvr, the default // featureCompatibilityVersion is the downgrade version, so that it can be safely added to a // downgrade version cluster. The config server will run setFeatureCompatibilityVersion as part // of addShard. const bool storeUpgradeVersion = serverGlobalParams.clusterRole != ClusterRole::ShardServer; UnreplicatedWritesBlock unreplicatedWritesBlock(opCtx); NamespaceString nss(NamespaceString::kServerConfigurationNamespace); { CollectionOptions options; options.uuid = CollectionUUID::gen(); uassertStatusOK(storageInterface->createCollection(opCtx, nss, options)); } // 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::kVersion42 : FeatureCompatibilityVersionParser::kVersion40)), Timestamp()}, repl::OpTime::kUninitializedTerm)); // No timestamp or term because this write is not // replicated. } bool FeatureCompatibilityVersion::isCleanStartUp() { std::vector dbNames; StorageEngine* storageEngine = getGlobalServiceContext()->getStorageEngine(); storageEngine->listDatabases(&dbNames); for (auto&& dbName : dbNames) { if (dbName != "local") { return false; } } return true; } void FeatureCompatibilityVersion::onInsertOrUpdate(OperationContext* opCtx, const BSONObj& doc) { auto idElement = doc["_id"]; if (idElement.type() != BSONType::String || idElement.String() != FeatureCompatibilityVersionParser::kParameterName) { return; } auto newVersion = uassertStatusOK(FeatureCompatibilityVersionParser::parse(doc)); // To avoid extra log messages when the targetVersion is set/unset, only log when the version // changes. bool isDifferent = serverGlobalParams.featureCompatibility.isVersionInitialized() ? serverGlobalParams.featureCompatibility.getVersion() != newVersion : true; if (isDifferent) { log() << "setting featureCompatibilityVersion to " << FeatureCompatibilityVersionParser::toString(newVersion); } opCtx->recoveryUnit()->onCommit([opCtx, newVersion](boost::optional) { serverGlobalParams.featureCompatibility.setVersion(newVersion); updateMinWireVersion(); if (newVersion != ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo40) { // Close all incoming connections from internal clients with binary versions lower than // ours. opCtx->getServiceContext()->getServiceEntryPoint()->endAllSessions( transport::Session::kLatestVersionInternalClientKeepOpen | transport::Session::kExternalClientKeepOpen); // Close all outgoing connections to servers with binary versions lower than ours. executor::EgressTagCloserManager::get(opCtx->getServiceContext()) .dropConnections(transport::Session::kKeepOpen); } if (newVersion != ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo42) { // Abort all open transactions when downgrading the featureCompatibilityVersion. SessionKiller::Matcher matcherAllSessions( KillAllSessionsByPatternSet{makeKillAllSessionsByPattern(opCtx)}); killSessionsLocalKillTransactions(opCtx, matcherAllSessions); } }); } void FeatureCompatibilityVersion::updateMinWireVersion() { WireSpec& spec = WireSpec::instance(); switch (serverGlobalParams.featureCompatibility.getVersion()) { case ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo42: case ServerGlobalParams::FeatureCompatibility::Version::kUpgradingTo42: case ServerGlobalParams::FeatureCompatibility::Version::kDowngradingTo40: spec.incomingInternalClient.minWireVersion = LATEST_WIRE_VERSION; spec.outgoing.minWireVersion = LATEST_WIRE_VERSION; return; case ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo40: spec.incomingInternalClient.minWireVersion = LATEST_WIRE_VERSION - 1; spec.outgoing.minWireVersion = LATEST_WIRE_VERSION - 1; return; case ServerGlobalParams::FeatureCompatibility::Version::kUnsetDefault40Behavior: // getVersion() does not return this value. MONGO_UNREACHABLE; } } void FeatureCompatibilityVersion::_validateVersion(StringData version) { uassert(40284, str::stream() << "featureCompatibilityVersion must be '" << FeatureCompatibilityVersionParser::kVersion42 << "' or '" << FeatureCompatibilityVersionParser::kVersion40 << "'. See " << feature_compatibility_version_documentation::kCompatibilityLink << ".", version == FeatureCompatibilityVersionParser::kVersion42 || version == FeatureCompatibilityVersionParser::kVersion40); } 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. */ class FeatureCompatibilityVersionParameter : public ServerParameter { public: FeatureCompatibilityVersionParameter() : ServerParameter(ServerParameterSet::getGlobal(), FeatureCompatibilityVersionParser::kParameterName.toString(), false, // allowedToChangeAtStartup false // allowedToChangeAtRuntime ) {} virtual void append(OperationContext* opCtx, BSONObjBuilder& b, const std::string& name) { BSONObjBuilder featureCompatibilityVersionBuilder(b.subobjStart(name)); uassert(ErrorCodes::UnknownFeatureCompatibilityVersion, str::stream() << FeatureCompatibilityVersionParser::kParameterName << " is not yet known.", serverGlobalParams.featureCompatibility.isVersionInitialized()); switch (serverGlobalParams.featureCompatibility.getVersion()) { case ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo42: featureCompatibilityVersionBuilder.append( FeatureCompatibilityVersionParser::kVersionField, FeatureCompatibilityVersionParser::kVersion42); return; case ServerGlobalParams::FeatureCompatibility::Version::kUpgradingTo42: featureCompatibilityVersionBuilder.append( FeatureCompatibilityVersionParser::kVersionField, FeatureCompatibilityVersionParser::kVersion40); featureCompatibilityVersionBuilder.append( FeatureCompatibilityVersionParser::kTargetVersionField, FeatureCompatibilityVersionParser::kVersion42); return; case ServerGlobalParams::FeatureCompatibility::Version::kDowngradingTo40: featureCompatibilityVersionBuilder.append( FeatureCompatibilityVersionParser::kVersionField, FeatureCompatibilityVersionParser::kVersion40); featureCompatibilityVersionBuilder.append( FeatureCompatibilityVersionParser::kTargetVersionField, FeatureCompatibilityVersionParser::kVersion40); return; case ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo40: featureCompatibilityVersionBuilder.append( FeatureCompatibilityVersionParser::kVersionField, FeatureCompatibilityVersionParser::kVersion40); return; case ServerGlobalParams::FeatureCompatibility::Version::kUnsetDefault40Behavior: // getVersion() does not return this value. MONGO_UNREACHABLE; } } virtual Status set(const BSONElement& newValueElement) { return Status(ErrorCodes::IllegalOperation, str::stream() << FeatureCompatibilityVersionParser::kParameterName << " cannot be set via setParameter. See " << feature_compatibility_version_documentation::kCompatibilityLink << "."); } virtual Status setFromString(const std::string& str) { return Status(ErrorCodes::IllegalOperation, str::stream() << FeatureCompatibilityVersionParser::kParameterName << " cannot be set via setParameter. See " << feature_compatibility_version_documentation::kCompatibilityLink << "."); } } featureCompatibilityVersionParameter; MONGO_EXPORT_STARTUP_SERVER_PARAMETER(internalValidateFeaturesAsMaster, bool, true); } // namespace mongo