diff options
author | Judah Schvimer <judah@mongodb.com> | 2017-03-06 16:02:10 -0500 |
---|---|---|
committer | Judah Schvimer <judah@mongodb.com> | 2017-03-06 16:02:10 -0500 |
commit | 5b5cf72ed9e52d71ed9f41b2219080fe46f62a3a (patch) | |
tree | 9fb58b23f66748b64369316c766a4fa693fd76e2 /src/mongo/db/repl/repl_set_config_checks.cpp | |
parent | 59681ee6603fc43f0f3209ec0f9c6a09476edfcc (diff) | |
download | mongo-5b5cf72ed9e52d71ed9f41b2219080fe46f62a3a.tar.gz |
SERVER-27995 make repl_set* naming convention consistent
Diffstat (limited to 'src/mongo/db/repl/repl_set_config_checks.cpp')
-rw-r--r-- | src/mongo/db/repl/repl_set_config_checks.cpp | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/src/mongo/db/repl/repl_set_config_checks.cpp b/src/mongo/db/repl/repl_set_config_checks.cpp new file mode 100644 index 00000000000..078ed2b77ab --- /dev/null +++ b/src/mongo/db/repl/repl_set_config_checks.cpp @@ -0,0 +1,353 @@ +/** + * Copyright 2014 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 <http://www.gnu.org/licenses/>. + * + * 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/repl/repl_set_config_checks.h" + +#include <iterator> + +#include "mongo/db/repl/repl_set_config.h" +#include "mongo/db/repl/replication_coordinator_external_state.h" +#include "mongo/db/service_context.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace repl { + +namespace { +/** + * Finds the index of the one member configuration in "newConfig" that corresponds + * to the current node (as identified by "externalState"). + * + * Returns an error if the current node does not appear or appears multiple times in + * "newConfig". + */ +StatusWith<int> findSelfInConfig(ReplicationCoordinatorExternalState* externalState, + const ReplSetConfig& newConfig, + ServiceContext* ctx) { + std::vector<ReplSetConfig::MemberIterator> meConfigs; + for (ReplSetConfig::MemberIterator iter = newConfig.membersBegin(); + iter != newConfig.membersEnd(); + ++iter) { + if (externalState->isSelf(iter->getHostAndPort(), ctx)) { + meConfigs.push_back(iter); + } + } + if (meConfigs.empty()) { + return StatusWith<int>(ErrorCodes::NodeNotFound, + str::stream() << "No host described in new configuration " + << newConfig.getConfigVersion() + << " for replica set " + << newConfig.getReplSetName() + << " maps to this node"); + } + if (meConfigs.size() > 1) { + str::stream message; + message << "The hosts " << meConfigs.front()->getHostAndPort().toString(); + for (size_t i = 1; i < meConfigs.size() - 1; ++i) { + message << ", " << meConfigs[i]->getHostAndPort().toString(); + } + message << " and " << meConfigs.back()->getHostAndPort().toString() + << " all map to this node in new configuration version " + << newConfig.getConfigVersion() << " for replica set " + << newConfig.getReplSetName(); + return StatusWith<int>(ErrorCodes::DuplicateKey, message); + } + + int myIndex = std::distance(newConfig.membersBegin(), meConfigs.front()); + invariant(myIndex >= 0 && myIndex < newConfig.getNumMembers()); + return StatusWith<int>(myIndex); +} + +/** + * Checks if the node with the given config index is electable, returning a useful + * status message if not. + */ +Status checkElectable(const ReplSetConfig& newConfig, int configIndex) { + const MemberConfig& myConfig = newConfig.getMemberAt(configIndex); + if (!myConfig.isElectable()) { + return Status(ErrorCodes::NodeNotElectable, + str::stream() << "This node, " << myConfig.getHostAndPort().toString() + << ", with _id " + << myConfig.getId() + << " is not electable under the new configuration version " + << newConfig.getConfigVersion() + << " for replica set " + << newConfig.getReplSetName()); + } + return Status::OK(); +} + +/** + * Like findSelfInConfig, above, but also returns an error if the member configuration + * for this node is not electable, as this is a requirement for nodes accepting + * reconfig or initiate commands. + */ +StatusWith<int> findSelfInConfigIfElectable(ReplicationCoordinatorExternalState* externalState, + const ReplSetConfig& newConfig, + ServiceContext* ctx) { + StatusWith<int> result = findSelfInConfig(externalState, newConfig, ctx); + if (result.isOK()) { + Status status = checkElectable(newConfig, result.getValue()); + if (!status.isOK()) { + return StatusWith<int>(status); + } + } + return result; +} + +/** + * Checks that the priorities of all the arbiters in the configuration are 0. If they were 1, + * they should have been set to 0 in MemberConfig::initialize(). Otherwise, they are illegal. + */ +Status validateArbiterPriorities(const ReplSetConfig& config) { + for (ReplSetConfig::MemberIterator iter = config.membersBegin(); iter != config.membersEnd(); + ++iter) { + if (iter->isArbiter() && iter->getPriority() != 0) { + return Status(ErrorCodes::InvalidReplicaSetConfig, + str::stream() << "Member " << iter->getHostAndPort().toString() + << " is an arbiter but has priority " + << iter->getPriority() + << ". Arbiter priority must be 0."); + } + } + return Status::OK(); +} + +/** + * Compares two initialized and validated replica set configurations, and checks to + * see if "newConfig" is a legal successor configuration to "oldConfig". + * + * Returns Status::OK() if "newConfig" may replace "oldConfig", or an indicative error + * otherwise. + * + * The checks performed by this test are necessary, but may not be sufficient for + * ensuring that "newConfig" is a legal successor to "oldConfig". For example, + * a legal reconfiguration must typically be executed on a node that is currently + * primary under "oldConfig" and is electable under "newConfig". Such checks that + * require knowledge of which node is executing the configuration are out of scope + * for this function. + */ +Status validateOldAndNewConfigsCompatible(const ReplSetConfig& oldConfig, + const ReplSetConfig& newConfig) { + invariant(newConfig.isInitialized()); + invariant(oldConfig.isInitialized()); + + if (oldConfig.getConfigVersion() >= newConfig.getConfigVersion()) { + return Status(ErrorCodes::NewReplicaSetConfigurationIncompatible, + str::stream() + << "New replica set configuration version must be greater than old, but " + << newConfig.getConfigVersion() + << " is not greater than " + << oldConfig.getConfigVersion() + << " for replica set " + << newConfig.getReplSetName()); + } + + if (oldConfig.getReplSetName() != newConfig.getReplSetName()) { + return Status(ErrorCodes::NewReplicaSetConfigurationIncompatible, + str::stream() << "New and old configurations differ in replica set name; " + "old was " + << oldConfig.getReplSetName() + << ", and new is " + << newConfig.getReplSetName()); + } + + if (oldConfig.getReplicaSetId() != newConfig.getReplicaSetId()) { + return Status(ErrorCodes::NewReplicaSetConfigurationIncompatible, + str::stream() << "New and old configurations differ in replica set ID; " + "old was " + << oldConfig.getReplicaSetId() + << ", and new is " + << newConfig.getReplicaSetId()); + } + + if (oldConfig.isConfigServer() && !newConfig.isConfigServer()) { + return Status(ErrorCodes::NewReplicaSetConfigurationIncompatible, + str::stream() << "Cannot remove \"" << ReplSetConfig::kConfigServerFieldName + << "\" from replica set configuration on reconfig"); + } + + // + // For every member config mNew in newConfig, if there exists member config mOld + // in oldConfig such that mNew.getHostAndPort() == mOld.getHostAndPort(), it is required + // that mNew.getId() == mOld.getId(). + // + // Also, one may not use reconfig to change the value of the buildIndexes or + // arbiterOnly flags. + // + for (ReplSetConfig::MemberIterator mNew = newConfig.membersBegin(); + mNew != newConfig.membersEnd(); + ++mNew) { + for (ReplSetConfig::MemberIterator mOld = oldConfig.membersBegin(); + mOld != oldConfig.membersEnd(); + ++mOld) { + const bool idsEqual = mOld->getId() == mNew->getId(); + const bool hostsEqual = mOld->getHostAndPort() == mNew->getHostAndPort(); + if (!idsEqual && !hostsEqual) { + continue; + } + if (hostsEqual && !idsEqual) { + return Status(ErrorCodes::NewReplicaSetConfigurationIncompatible, + str::stream() << "New and old configurations both have members with " + << MemberConfig::kHostFieldName + << " of " + << mOld->getHostAndPort().toString() + << " but in the new configuration the " + << MemberConfig::kIdFieldName + << " field is " + << mNew->getId() + << " and in the old configuration it is " + << mOld->getId() + << " for replica set " + << newConfig.getReplSetName()); + } + // At this point, the _id and host fields are equal, so we're looking at the old and + // new configurations for the same member node. + const bool buildIndexesFlagsEqual = + mOld->shouldBuildIndexes() == mNew->shouldBuildIndexes(); + if (!buildIndexesFlagsEqual) { + return Status(ErrorCodes::NewReplicaSetConfigurationIncompatible, + str::stream() + << "New and old configurations differ in the setting of the " + "buildIndexes field for member " + << mOld->getHostAndPort().toString() + << "; to make this change, remove then re-add the member"); + } + const bool arbiterFlagsEqual = mOld->isArbiter() == mNew->isArbiter(); + if (!arbiterFlagsEqual) { + return Status(ErrorCodes::NewReplicaSetConfigurationIncompatible, + str::stream() + << "New and old configurations differ in the setting of the " + "arbiterOnly field for member " + << mOld->getHostAndPort().toString() + << "; to make this change, remove then re-add the member"); + } + } + } + return Status::OK(); +} +} // namespace + +StatusWith<int> validateConfigForStartUp(ReplicationCoordinatorExternalState* externalState, + const ReplSetConfig& oldConfig, + const ReplSetConfig& newConfig, + ServiceContext* ctx) { + Status status = newConfig.validate(); + if (!status.isOK()) { + return StatusWith<int>(status); + } + if (oldConfig.isInitialized()) { + status = validateOldAndNewConfigsCompatible(oldConfig, newConfig); + if (!status.isOK()) { + return StatusWith<int>(status); + } + } + return findSelfInConfig(externalState, newConfig, ctx); +} + +StatusWith<int> validateConfigForInitiate(ReplicationCoordinatorExternalState* externalState, + const ReplSetConfig& newConfig, + ServiceContext* ctx) { + Status status = newConfig.validate(); + if (!status.isOK()) { + return StatusWith<int>(status); + } + + status = newConfig.checkIfWriteConcernCanBeSatisfied(newConfig.getDefaultWriteConcern()); + if (!status.isOK()) { + return StatusWith<int>( + status.code(), + str::stream() << "Found invalid default write concern in 'getLastErrorDefaults' field" + << causedBy(status.reason())); + } + + status = validateArbiterPriorities(newConfig); + if (!status.isOK()) { + return StatusWith<int>(status); + } + + if (newConfig.getConfigVersion() != 1) { + return StatusWith<int>(ErrorCodes::NewReplicaSetConfigurationIncompatible, + str::stream() << "Configuration used to initiate a replica set must " + << " have version 1, but found " + << newConfig.getConfigVersion()); + } + return findSelfInConfigIfElectable(externalState, newConfig, ctx); +} + +StatusWith<int> validateConfigForReconfig(ReplicationCoordinatorExternalState* externalState, + const ReplSetConfig& oldConfig, + const ReplSetConfig& newConfig, + ServiceContext* ctx, + bool force) { + Status status = newConfig.validate(); + if (!status.isOK()) { + return StatusWith<int>(status); + } + + status = newConfig.checkIfWriteConcernCanBeSatisfied(newConfig.getDefaultWriteConcern()); + if (!status.isOK()) { + return StatusWith<int>( + status.code(), + str::stream() << "Found invalid default write concern in 'getLastErrorDefaults' field" + << causedBy(status.reason())); + } + + status = validateOldAndNewConfigsCompatible(oldConfig, newConfig); + if (!status.isOK()) { + return StatusWith<int>(status); + } + + status = validateArbiterPriorities(newConfig); + if (!status.isOK()) { + return StatusWith<int>(status); + } + + if (force) { + return findSelfInConfig(externalState, newConfig, ctx); + } + + return findSelfInConfigIfElectable(externalState, newConfig, ctx); +} + +StatusWith<int> validateConfigForHeartbeatReconfig( + ReplicationCoordinatorExternalState* externalState, + const ReplSetConfig& newConfig, + ServiceContext* ctx) { + Status status = newConfig.validate(); + if (!status.isOK()) { + return StatusWith<int>(status); + } + + return findSelfInConfig(externalState, newConfig, ctx); +} + +} // namespace repl +} // namespace mongo |