/**
* 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/collection_options.h"
#include "mongo/db/commands/feature_compatibility_version_command_parser.h"
#include "mongo/db/concurrency/write_conflict_exception.h"
#include "mongo/db/curop.h"
#include "mongo/db/db_raii.h"
#include "mongo/db/dbdirectclient.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/index_builder.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/repl/replication_coordinator.h"
#include "mongo/db/server_parameters.h"
#include "mongo/db/service_context.h"
#include "mongo/db/storage/storage_engine.h"
#include "mongo/db/write_concern_options.h"
#include "mongo/rpc/get_status_from_command_result.h"
#include "mongo/util/log.h"
namespace mongo {
using repl::UnreplicatedWritesBlock;
constexpr StringData FeatureCompatibilityVersion::k32IncompatibleIndexName;
constexpr StringData FeatureCompatibilityVersion::kCollection;
constexpr StringData FeatureCompatibilityVersion::kCommandName;
constexpr StringData FeatureCompatibilityVersion::kDatabase;
constexpr StringData FeatureCompatibilityVersion::kParameterName;
constexpr StringData FeatureCompatibilityVersion::kVersionField;
namespace {
const BSONObj k32IncompatibleIndexSpec =
BSON(IndexDescriptor::kIndexVersionFieldName
<< static_cast(IndexDescriptor::IndexVersion::kV2)
<< IndexDescriptor::kKeyPatternFieldName
<< BSON(FeatureCompatibilityVersion::kVersionField << 1)
<< IndexDescriptor::kNamespaceFieldName
<< FeatureCompatibilityVersion::kCollection
<< IndexDescriptor::kIndexNameFieldName
<< FeatureCompatibilityVersion::k32IncompatibleIndexName);
BSONObj makeUpdateCommand(StringData newVersion, BSONObj writeConcern) {
BSONObjBuilder updateCmd;
NamespaceString nss(FeatureCompatibilityVersion::kCollection);
updateCmd.append("update", nss.coll());
{
BSONArrayBuilder updates(updateCmd.subarrayStart("updates"));
{
BSONObjBuilder updateSpec(updates.subobjStart());
{
BSONObjBuilder queryFilter(updateSpec.subobjStart("q"));
queryFilter.append("_id", FeatureCompatibilityVersion::kParameterName);
}
{
BSONObjBuilder updateMods(updateSpec.subobjStart("u"));
updateMods.append(FeatureCompatibilityVersion::kVersionField, newVersion);
}
updateSpec.appendBool("upsert", true);
}
}
if (!writeConcern.isEmpty()) {
updateCmd.append(WriteConcernOptions::kWriteConcernField, writeConcern);
}
return updateCmd.obj();
}
StringData getFeatureCompatibilityVersionString(
ServerGlobalParams::FeatureCompatibility::Version version) {
switch (version) {
case ServerGlobalParams::FeatureCompatibility::Version::k34:
return FeatureCompatibilityVersionCommandParser::kVersion34;
case ServerGlobalParams::FeatureCompatibility::Version::k32:
return FeatureCompatibilityVersionCommandParser::kVersion32;
default:
MONGO_UNREACHABLE;
}
}
} // namespace
StatusWith FeatureCompatibilityVersion::parse(
const BSONObj& featureCompatibilityVersionDoc) {
bool foundVersionField = false;
ServerGlobalParams::FeatureCompatibility::Version version;
for (auto&& elem : featureCompatibilityVersionDoc) {
auto fieldName = elem.fieldNameStringData();
if (fieldName == "_id") {
continue;
} else if (fieldName == FeatureCompatibilityVersion::kVersionField) {
foundVersionField = true;
if (elem.type() != BSONType::String) {
return Status(
ErrorCodes::TypeMismatch,
str::stream()
<< FeatureCompatibilityVersion::kVersionField
<< " must be of type String, but was of type "
<< typeName(elem.type())
<< ". Contents of "
<< FeatureCompatibilityVersion::kParameterName
<< " document in "
<< FeatureCompatibilityVersion::kCollection
<< ": "
<< featureCompatibilityVersionDoc
<< ". See http://dochub.mongodb.org/core/3.4-feature-compatibility.");
}
if (elem.String() == FeatureCompatibilityVersionCommandParser::kVersion34) {
version = ServerGlobalParams::FeatureCompatibility::Version::k34;
} else if (elem.String() == FeatureCompatibilityVersionCommandParser::kVersion32) {
version = ServerGlobalParams::FeatureCompatibility::Version::k32;
} else {
return Status(
ErrorCodes::BadValue,
str::stream()
<< "Invalid value for "
<< FeatureCompatibilityVersion::kVersionField
<< ", found "
<< elem.String()
<< ", expected '"
<< FeatureCompatibilityVersionCommandParser::kVersion34
<< "' or '"
<< FeatureCompatibilityVersionCommandParser::kVersion32
<< "'. Contents of "
<< FeatureCompatibilityVersion::kParameterName
<< " document in "
<< FeatureCompatibilityVersion::kCollection
<< ": "
<< featureCompatibilityVersionDoc
<< ". See http://dochub.mongodb.org/core/3.4-feature-compatibility.");
}
} else {
return Status(
ErrorCodes::BadValue,
str::stream() << "Unrecognized field '" << elem.fieldName() << "''. Contents of "
<< FeatureCompatibilityVersion::kParameterName
<< " document in "
<< FeatureCompatibilityVersion::kCollection
<< ": "
<< featureCompatibilityVersionDoc
<< ". See http://dochub.mongodb.org/core/3.4-feature-compatibility.");
}
}
if (!foundVersionField) {
return Status(ErrorCodes::BadValue,
str::stream()
<< "Missing required field '"
<< FeatureCompatibilityVersion::kVersionField
<< "''. Contents of "
<< FeatureCompatibilityVersion::kParameterName
<< " document in "
<< FeatureCompatibilityVersion::kCollection
<< ": "
<< featureCompatibilityVersionDoc
<< ". See http://dochub.mongodb.org/core/3.4-feature-compatibility.");
}
return version;
}
void FeatureCompatibilityVersion::set(OperationContext* txn, StringData version) {
uassert(40284,
"featureCompatibilityVersion must be '3.4' or '3.2'. See "
"http://dochub.mongodb.org/core/3.4-feature-compatibility.",
version == FeatureCompatibilityVersionCommandParser::kVersion34 ||
version == FeatureCompatibilityVersionCommandParser::kVersion32);
DBDirectClient client(txn);
NamespaceString nss(FeatureCompatibilityVersion::kCollection);
if (version == FeatureCompatibilityVersionCommandParser::kVersion34) {
// We build a v=2 index on the "admin.system.version" collection as part of setting the
// featureCompatibilityVersion to 3.4. This is a new index version that isn't supported by
// versions of MongoDB earlier than 3.4 that will cause 3.2 secondaries to crash when it is
// replicated.
std::vector indexSpecs{k32IncompatibleIndexSpec};
{
ScopedTransaction transaction(txn, MODE_IX);
AutoGetOrCreateDb autoDB(txn, nss.db(), MODE_X);
uassert(ErrorCodes::NotMaster,
str::stream() << "Cannot set featureCompatibilityVersion to '" << version
<< "'. Not primary while attempting to create index on: "
<< nss.ns(),
repl::ReplicationCoordinator::get(txn->getServiceContext())
->canAcceptWritesFor(txn, nss));
IndexBuilder builder(k32IncompatibleIndexSpec, false);
auto status = builder.buildInForeground(txn, autoDB.getDb());
uassertStatusOK(status);
MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN {
WriteUnitOfWork wuow(txn);
getGlobalServiceContext()->getOpObserver()->onCreateIndex(
txn, autoDB.getDb()->getSystemIndexesName(), k32IncompatibleIndexSpec, false);
wuow.commit();
}
MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "FeatureCompatibilityVersion::set", nss.ns());
}
// We then update the featureCompatibilityVersion document stored in the
// "admin.system.version" collection. We do this after creating the v=2 index in order to
// maintain the invariant that if the featureCompatibilityVersion is 3.4, then
// 'k32IncompatibleIndexSpec' index exists on the "admin.system.version" collection.
BSONObj updateResult;
client.runCommand(nss.db().toString(),
makeUpdateCommand(version, WriteConcernOptions::Majority),
updateResult);
uassertStatusOK(getStatusFromCommandResult(updateResult));
uassertStatusOK(getWriteConcernStatusFromCommandResult(updateResult));
// We then update the value of the featureCompatibilityVersion server parameter.
serverGlobalParams.featureCompatibility.version.store(
ServerGlobalParams::FeatureCompatibility::Version::k34);
} else if (version == FeatureCompatibilityVersionCommandParser::kVersion32) {
// We update the featureCompatibilityVersion document stored in the "admin.system.version"
// collection. We do this before dropping the v=2 index in order to maintain the invariant
// that if the featureCompatibilityVersion is 3.4, then 'k32IncompatibleIndexSpec' index
// exists on the "admin.system.version" collection. We don't attach a "majority" write
// concern to this update because we're going to do so anyway for the "dropIndexes" command.
BSONObj updateResult;
client.runCommand(nss.db().toString(), makeUpdateCommand(version, BSONObj()), updateResult);
uassertStatusOK(getStatusFromCommandResult(updateResult));
// We then drop the v=2 index on the "admin.system.version" collection to enable 3.2
// secondaries to sync from this mongod.
BSONObjBuilder dropIndexesCmd;
dropIndexesCmd.append("dropIndexes", nss.coll());
dropIndexesCmd.append("index", FeatureCompatibilityVersion::k32IncompatibleIndexName);
dropIndexesCmd.append("writeConcern", WriteConcernOptions::Majority);
BSONObj dropIndexesResult;
client.runCommand(nss.db().toString(), dropIndexesCmd.done(), dropIndexesResult);
auto status = getStatusFromCommandResult(dropIndexesResult);
if (status != ErrorCodes::IndexNotFound) {
uassertStatusOK(status);
}
uassertStatusOK(getWriteConcernStatusFromCommandResult(dropIndexesResult));
// We then update the value of the featureCompatibilityVersion server parameter.
serverGlobalParams.featureCompatibility.version.store(
ServerGlobalParams::FeatureCompatibility::Version::k32);
}
}
void FeatureCompatibilityVersion::setIfCleanStartup(OperationContext* txn,
repl::StorageInterface* storageInterface) {
if (serverGlobalParams.clusterRole != ClusterRole::ShardServer) {
std::vector dbNames;
StorageEngine* storageEngine = getGlobalServiceContext()->getGlobalStorageEngine();
storageEngine->listDatabases(&dbNames);
for (auto&& dbName : dbNames) {
if (dbName != "local") {
return;
}
}
UnreplicatedWritesBlock unreplicatedWritesBlock(txn);
NamespaceString nss(FeatureCompatibilityVersion::kCollection);
// We build a v=2 index on the "admin.system.version" collection as part of setting the
// featureCompatibilityVersion to 3.4. This is a new index version that isn't supported by
// versions of MongoDB earlier than 3.4 that will cause 3.2 secondaries to crash when it is
// cloned.
std::vector indexSpecs{k32IncompatibleIndexSpec};
{
ScopedTransaction transaction(txn, MODE_IX);
AutoGetOrCreateDb autoDB(txn, nss.db(), MODE_X);
IndexBuilder builder(k32IncompatibleIndexSpec, false);
auto status = builder.buildInForeground(txn, autoDB.getDb());
uassertStatusOK(status);
}
// We then insert the featureCompatibilityVersion document into the "admin.system.version"
// collection. We do this after creating the v=2 index in order to maintain the invariant
// that if the featureCompatibilityVersion is 3.4, then 'k32IncompatibleIndexSpec' index
// exists on the "admin.system.version" collection. If we happened to fail to insert the
// document when starting up, then on a subsequent start-up we'd no longer consider the data
// files "clean" and would instead be in featureCompatibilityVersion=3.2.
uassertStatusOK(storageInterface->insertDocument(
txn,
nss,
BSON("_id" << FeatureCompatibilityVersion::kParameterName
<< FeatureCompatibilityVersion::kVersionField
<< FeatureCompatibilityVersionCommandParser::kVersion34)));
// We then update the value of the featureCompatibilityVersion server parameter.
serverGlobalParams.featureCompatibility.version.store(
ServerGlobalParams::FeatureCompatibility::Version::k34);
}
}
void FeatureCompatibilityVersion::onInsertOrUpdate(const BSONObj& doc) {
auto idElement = doc["_id"];
if (idElement.type() != BSONType::String ||
idElement.String() != FeatureCompatibilityVersion::kParameterName) {
return;
}
auto newVersion = uassertStatusOK(FeatureCompatibilityVersion::parse(doc));
log() << "setting featureCompatibilityVersion to "
<< getFeatureCompatibilityVersionString(newVersion);
serverGlobalParams.featureCompatibility.version.store(newVersion);
}
void FeatureCompatibilityVersion::onDelete(const BSONObj& doc) {
auto idElement = doc["_id"];
if (idElement.type() != BSONType::String ||
idElement.String() != FeatureCompatibilityVersion::kParameterName) {
return;
}
log() << "setting featureCompatibilityVersion to "
<< FeatureCompatibilityVersionCommandParser::kVersion32;
serverGlobalParams.featureCompatibility.version.store(
ServerGlobalParams::FeatureCompatibility::Version::k32);
}
void FeatureCompatibilityVersion::onDropCollection() {
log() << "setting featureCompatibilityVersion to "
<< FeatureCompatibilityVersionCommandParser::kVersion32;
serverGlobalParams.featureCompatibility.version.store(
ServerGlobalParams::FeatureCompatibility::Version::k32);
}
/**
* Read-only server parameter for featureCompatibilityVersion.
*/
class FeatureCompatibilityVersionParameter : public ServerParameter {
public:
FeatureCompatibilityVersionParameter()
: ServerParameter(ServerParameterSet::getGlobal(),
FeatureCompatibilityVersion::kParameterName.toString(),
false, // allowedToChangeAtStartup
false // allowedToChangeAtRuntime
) {}
virtual void append(OperationContext* txn, BSONObjBuilder& b, const std::string& name) {
b.append(name,
getFeatureCompatibilityVersionString(
serverGlobalParams.featureCompatibility.version.load()));
}
virtual Status set(const BSONElement& newValueElement) {
return Status(ErrorCodes::IllegalOperation,
str::stream() << FeatureCompatibilityVersion::kParameterName
<< " cannot be set via setParameter. See "
"http://dochub.mongodb.org/core/3.4-feature-compatibility.");
}
virtual Status setFromString(const std::string& str) {
return Status(ErrorCodes::IllegalOperation,
str::stream() << FeatureCompatibilityVersion::kParameterName
<< " cannot be set via setParameter. See "
"http://dochub.mongodb.org/core/3.4-feature-compatibility.");
}
} featureCompatibilityVersionParameter;
MONGO_EXPORT_STARTUP_SERVER_PARAMETER(internalValidateFeaturesAsMaster, bool, true);
} // namespace mongo