/**
* 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 .
*
* 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/replica_set_config_checks.h"
#include
#include "mongo/db/repl/repl_coordinator_external_state.h"
#include "mongo/db/repl/replica_set_config.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 findSelfInConfig(
ReplicationCoordinatorExternalState* externalState,
const ReplicaSetConfig& newConfig) {
std::vector meConfigs;
for (ReplicaSetConfig::MemberIterator iter = newConfig.membersBegin();
iter != newConfig.membersEnd();
++iter) {
if (externalState->isSelf(iter->getHostAndPort())) {
meConfigs.push_back(iter);
}
}
if (meConfigs.empty()) {
return StatusWith(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(ErrorCodes::DuplicateKey, message);
}
int myIndex = std::distance(newConfig.membersBegin(), meConfigs.front());
invariant(myIndex >= 0 && myIndex < newConfig.getNumMembers());
return StatusWith(myIndex);
}
/**
* Checks if the node with the given config index is electable, returning a useful
* status message if not.
*/
Status checkElectable(const ReplicaSetConfig& 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 findSelfInConfigIfElectable(
ReplicationCoordinatorExternalState* externalState,
const ReplicaSetConfig& newConfig) {
StatusWith result = findSelfInConfig(externalState, newConfig);
if (result.isOK()) {
Status status = checkElectable(newConfig, result.getValue());
if (!status.isOK()) {
return StatusWith(status);
}
}
return result;
}
/**
* 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 ReplicaSetConfig& oldConfig,
const ReplicaSetConfig& 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());
}
//
// 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 (ReplicaSetConfig::MemberIterator mNew = newConfig.membersBegin();
mNew != newConfig.membersEnd();
++mNew) {
for (ReplicaSetConfig::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 validateConfigForStartUp(
ReplicationCoordinatorExternalState* externalState,
const ReplicaSetConfig& oldConfig,
const ReplicaSetConfig& newConfig) {
Status status = newConfig.validate();
if (!status.isOK()) {
return StatusWith(status);
}
if (oldConfig.isInitialized()) {
status = validateOldAndNewConfigsCompatible(oldConfig, newConfig);
if (!status.isOK()) {
return StatusWith(status);
}
}
return findSelfInConfig(externalState, newConfig);
}
StatusWith validateConfigForInitiate(
ReplicationCoordinatorExternalState* externalState,
const ReplicaSetConfig& newConfig) {
Status status = newConfig.validate();
if (!status.isOK()) {
return StatusWith(status);
}
if (newConfig.getConfigVersion() != 1) {
return StatusWith(
ErrorCodes::NewReplicaSetConfigurationIncompatible,
str::stream() << "Configuration used to initiate a replica set must " <<
" have version 1, but found " << newConfig.getConfigVersion());
}
return findSelfInConfigIfElectable(externalState, newConfig);
}
StatusWith validateConfigForReconfig(
ReplicationCoordinatorExternalState* externalState,
const ReplicaSetConfig& oldConfig,
const ReplicaSetConfig& newConfig,
bool force) {
Status status = newConfig.validate();
if (!status.isOK()) {
return StatusWith(status);
}
status = validateOldAndNewConfigsCompatible(oldConfig, newConfig);
if (!status.isOK()) {
return StatusWith(status);
}
if (force) {
return findSelfInConfig(externalState, newConfig);
}
return findSelfInConfigIfElectable(externalState, newConfig);
}
StatusWith validateConfigForHeartbeatReconfig(
ReplicationCoordinatorExternalState* externalState,
const ReplicaSetConfig& newConfig) {
Status status = newConfig.validate();
if (!status.isOK()) {
return StatusWith(status);
}
return findSelfInConfig(externalState, newConfig);
}
} // namespace repl
} // namespace mongo