/** * 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/member_config.h" #include #include "mongo/bson/util/bson_check.h" #include "mongo/bson/util/bson_extract.h" #include "mongo/db/jsobj.h" #include "mongo/util/mongoutils/str.h" namespace mongo { namespace repl { const std::string MemberConfig::kIdFieldName = "_id"; const std::string MemberConfig::kVotesFieldName = "votes"; const std::string MemberConfig::kPriorityFieldName = "priority"; const std::string MemberConfig::kHostFieldName = "host"; const std::string MemberConfig::kHiddenFieldName = "hidden"; const std::string MemberConfig::kSlaveDelayFieldName = "slaveDelay"; const std::string MemberConfig::kArbiterOnlyFieldName = "arbiterOnly"; const std::string MemberConfig::kBuildIndexesFieldName = "buildIndexes"; const std::string MemberConfig::kTagsFieldName = "tags"; const std::string MemberConfig::kInternalVoterTagName = "$voter"; const std::string MemberConfig::kInternalElectableTagName = "$electable"; const std::string MemberConfig::kInternalAllTagName = "$all"; namespace { const std::string kLegalMemberConfigFieldNames[] = {MemberConfig::kIdFieldName, MemberConfig::kVotesFieldName, MemberConfig::kPriorityFieldName, MemberConfig::kHostFieldName, MemberConfig::kHiddenFieldName, MemberConfig::kSlaveDelayFieldName, MemberConfig::kArbiterOnlyFieldName, MemberConfig::kBuildIndexesFieldName, MemberConfig::kTagsFieldName}; const int kVotesFieldDefault = 1; const double kPriorityFieldDefault = 1.0; const Seconds kSlaveDelayFieldDefault(0); const bool kArbiterOnlyFieldDefault = false; const bool kHiddenFieldDefault = false; const bool kBuildIndexesFieldDefault = true; const Seconds kMaxSlaveDelay(3600 * 24 * 366); } // namespace Status MemberConfig::initialize(const BSONObj& mcfg, ReplSetTagConfig* tagConfig) { Status status = bsonCheckOnlyHasFields( "replica set member configuration", mcfg, kLegalMemberConfigFieldNames); if (!status.isOK()) return status; // // Parse _id field. // BSONElement idElement = mcfg[kIdFieldName]; if (idElement.eoo()) { return Status(ErrorCodes::NoSuchKey, str::stream() << kIdFieldName << " field is missing"); } if (!idElement.isNumber()) { return Status(ErrorCodes::TypeMismatch, str::stream() << kIdFieldName << " field has non-numeric type " << typeName(idElement.type())); } _id = idElement.numberInt(); // // Parse h field. // std::string hostAndPortString; status = bsonExtractStringField(mcfg, kHostFieldName, &hostAndPortString); if (!status.isOK()) return status; boost::trim(hostAndPortString); status = _host.initialize(hostAndPortString); if (!status.isOK()) return status; if (!_host.hasPort()) { // make port explicit even if default. _host = HostAndPort(_host.host(), _host.port()); } // // Parse votes field. // BSONElement votesElement = mcfg[kVotesFieldName]; if (votesElement.eoo()) { _votes = kVotesFieldDefault; } else if (votesElement.isNumber()) { _votes = votesElement.numberInt(); } else { return Status(ErrorCodes::TypeMismatch, str::stream() << kVotesFieldName << " field value has non-numeric type " << typeName(votesElement.type())); } // // Parse arbiterOnly field. // status = bsonExtractBooleanFieldWithDefault( mcfg, kArbiterOnlyFieldName, kArbiterOnlyFieldDefault, &_arbiterOnly); if (!status.isOK()) return status; // // Parse priority field. // BSONElement priorityElement = mcfg[kPriorityFieldName]; if (priorityElement.eoo() || (priorityElement.isNumber() && priorityElement.numberDouble() == kPriorityFieldDefault)) { _priority = _arbiterOnly ? 0.0 : kPriorityFieldDefault; } else if (priorityElement.isNumber()) { _priority = priorityElement.numberDouble(); } else { return Status(ErrorCodes::TypeMismatch, str::stream() << kPriorityFieldName << " field has non-numeric type " << typeName(priorityElement.type())); } // // Parse slaveDelay field. // BSONElement slaveDelayElement = mcfg[kSlaveDelayFieldName]; if (slaveDelayElement.eoo()) { _slaveDelay = kSlaveDelayFieldDefault; } else if (slaveDelayElement.isNumber()) { _slaveDelay = Seconds(slaveDelayElement.numberInt()); } else { return Status(ErrorCodes::TypeMismatch, str::stream() << kSlaveDelayFieldName << " field value has non-numeric type " << typeName(slaveDelayElement.type())); } // // Parse hidden field. // status = bsonExtractBooleanFieldWithDefault(mcfg, kHiddenFieldName, kHiddenFieldDefault, &_hidden); if (!status.isOK()) return status; // // Parse buildIndexes field. // status = bsonExtractBooleanFieldWithDefault( mcfg, kBuildIndexesFieldName, kBuildIndexesFieldDefault, &_buildIndexes); if (!status.isOK()) return status; // // Parse "tags" field. // _tags.clear(); BSONElement tagsElement; status = bsonExtractTypedField(mcfg, kTagsFieldName, Object, &tagsElement); if (status.isOK()) { for (auto&& tag : tagsElement.Obj()) { if (tag.type() != String) { return Status(ErrorCodes::TypeMismatch, str::stream() << "tags." << tag.fieldName() << " field has non-string value of type " << typeName(tag.type())); } _tags.push_back(tagConfig->makeTag(tag.fieldNameStringData(), tag.valueStringData())); } } else if (ErrorCodes::NoSuchKey != status) { return status; } // // Add internal tags based on other member properties. // // Add a voter tag if this non-arbiter member votes; use _id for uniquity. const std::string id = str::stream() << _id; if (isVoter() && !_arbiterOnly) { _tags.push_back(tagConfig->makeTag(kInternalVoterTagName, id)); } // Add an electable tag if this member is electable. if (isElectable()) { _tags.push_back(tagConfig->makeTag(kInternalElectableTagName, id)); } // Add a tag for generic counting of this node. if (!_arbiterOnly) { _tags.push_back(tagConfig->makeTag(kInternalAllTagName, id)); } return Status::OK(); } Status MemberConfig::validate() const { if (_id < 0 || _id > 255) { return Status(ErrorCodes::BadValue, str::stream() << kIdFieldName << " field value of " << _id << " is out of range."); } if (_priority < 0 || _priority > 1000) { return Status(ErrorCodes::BadValue, str::stream() << kPriorityFieldName << " field value of " << _priority << " is out of range"); } if (_votes != 0 && _votes != 1) { return Status(ErrorCodes::BadValue, str::stream() << kVotesFieldName << " field value is " << _votes << " but must be 0 or 1"); } if (_arbiterOnly) { if (!_tags.empty()) { return Status(ErrorCodes::BadValue, "Cannot set tags on arbiters."); } if (!isVoter()) { return Status(ErrorCodes::BadValue, "Arbiter must vote (cannot have 0 votes)"); } } if (_slaveDelay < Seconds(0) || _slaveDelay > kMaxSlaveDelay) { return Status(ErrorCodes::BadValue, str::stream() << kSlaveDelayFieldName << " field value of " << durationCount(_slaveDelay) << " seconds is out of range"); } // Check for additional electable requirements, when priority is non zero if (_priority != 0) { if (_votes == 0) { return Status(ErrorCodes::BadValue, "priority must be 0 when non-voting (votes:0)"); } if (_slaveDelay > Seconds(0)) { return Status(ErrorCodes::BadValue, "priority must be 0 when slaveDelay is used"); } if (_hidden) { return Status(ErrorCodes::BadValue, "priority must be 0 when hidden=true"); } if (!_buildIndexes) { return Status(ErrorCodes::BadValue, "priority must be 0 when buildIndexes=false"); } } return Status::OK(); } bool MemberConfig::hasTags(const ReplSetTagConfig& tagConfig) const { for (std::vector::const_iterator tag = _tags.begin(); tag != _tags.end(); tag++) { std::string tagKey = tagConfig.getTagKey(*tag); if (tagKey[0] == '$') { // Filter out internal tags continue; } return true; } return false; } BSONObj MemberConfig::toBSON(const ReplSetTagConfig& tagConfig) const { BSONObjBuilder configBuilder; configBuilder.append("_id", _id); configBuilder.append("host", _host.toString()); configBuilder.append("arbiterOnly", _arbiterOnly); configBuilder.append("buildIndexes", _buildIndexes); configBuilder.append("hidden", _hidden); configBuilder.append("priority", _priority); BSONObjBuilder tags(configBuilder.subobjStart("tags")); for (std::vector::const_iterator tag = _tags.begin(); tag != _tags.end(); tag++) { std::string tagKey = tagConfig.getTagKey(*tag); if (tagKey[0] == '$') { // Filter out internal tags continue; } tags.append(tagKey, tagConfig.getTagValue(*tag)); } tags.done(); configBuilder.append("slaveDelay", durationCount(_slaveDelay)); configBuilder.append("votes", getNumVotes()); return configBuilder.obj(); } } // namespace repl } // namespace mongo