/** * Copyright (C) 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. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kReplication #include "mongo/platform/basic.h" #include "mongo/db/repl/repl_set_heartbeat_response.h" #include #include "mongo/base/status.h" #include "mongo/bson/util/bson_extract.h" #include "mongo/db/jsobj.h" #include "mongo/db/repl/bson_extract_optime.h" #include "mongo/util/assert_util.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" namespace mongo { namespace repl { namespace { const std::string kConfigFieldName = "config"; const std::string kConfigVersionFieldName = "v"; const std::string kElectionTimeFieldName = "electionTime"; const std::string kErrMsgFieldName = "errmsg"; const std::string kErrorCodeFieldName = "code"; const std::string kHasDataFieldName = "hasData"; const std::string kHasStateDisagreementFieldName = "stateDisagreement"; const std::string kHbMessageFieldName = "hbmsg"; const std::string kIsElectableFieldName = "e"; const std::string kIsReplSetFieldName = "rs"; const std::string kMemberStateFieldName = "state"; const std::string kMismatchFieldName = "mismatch"; const std::string kOkFieldName = "ok"; const std::string kDurableOpTimeFieldName = "durableOpTime"; const std::string kAppliedOpTimeFieldName = "opTime"; const std::string kPrimaryIdFieldName = "primaryId"; const std::string kReplSetFieldName = "set"; const std::string kSyncSourceFieldName = "syncingTo"; const std::string kTermFieldName = "term"; const std::string kTimeFieldName = "time"; const std::string kTimestampFieldName = "ts"; } // namespace void ReplSetHeartbeatResponse::addToBSON(BSONObjBuilder* builder, bool isProtocolVersionV1) const { if (_mismatch) { *builder << kOkFieldName << 0.0; *builder << kMismatchFieldName << _mismatch; return; } builder->append(kOkFieldName, 1.0); if (_timeSet) { *builder << kTimeFieldName << durationCount(_time); } if (_electionTimeSet) { builder->appendDate(kElectionTimeFieldName, Date_t::fromMillisSinceEpoch(_electionTime.asLL())); } if (_configSet) { *builder << kConfigFieldName << _config.toBSON(); } if (_electableSet) { *builder << kIsElectableFieldName << _electable; } if (_isReplSet) { *builder << "rs" << _isReplSet; } if (_stateDisagreement) { *builder << kHasStateDisagreementFieldName << _stateDisagreement; } if (_stateSet) { builder->appendIntOrLL(kMemberStateFieldName, _state.s); } if (_configVersion != -1) { *builder << kConfigVersionFieldName << _configVersion; } *builder << kHbMessageFieldName << _hbmsg; if (!_setName.empty()) { *builder << kReplSetFieldName << _setName; } if (!_syncingTo.empty()) { *builder << kSyncSourceFieldName << _syncingTo.toString(); } if (_hasDataSet) { builder->append(kHasDataFieldName, _hasData); } if (_term != -1) { builder->append(kTermFieldName, _term); } if (_primaryIdSet) { builder->append(kPrimaryIdFieldName, _primaryId); } if (_durableOpTimeSet) { _durableOpTime.append(builder, kDurableOpTimeFieldName); } if (_appliedOpTimeSet) { if (isProtocolVersionV1) { _appliedOpTime.append(builder, kAppliedOpTimeFieldName); } else { builder->appendDate(kAppliedOpTimeFieldName, Date_t::fromMillisSinceEpoch(_appliedOpTime.getTimestamp().asLL())); } } } BSONObj ReplSetHeartbeatResponse::toBSON(bool isProtocolVersionV1) const { BSONObjBuilder builder; addToBSON(&builder, isProtocolVersionV1); return builder.obj(); } Status ReplSetHeartbeatResponse::initialize(const BSONObj& doc, long long term) { // Old versions set this even though they returned not "ok" _mismatch = doc[kMismatchFieldName].trueValue(); if (_mismatch) return Status(ErrorCodes::InconsistentReplicaSetNames, "replica set name doesn't match."); // Old versions sometimes set the replica set name ("set") but ok:0 const BSONElement replSetNameElement = doc[kReplSetFieldName]; if (replSetNameElement.eoo()) { _setName.clear(); } else if (replSetNameElement.type() != String) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kReplSetFieldName << "\" field in response to replSetHeartbeat to have " "type String, but found " << typeName(replSetNameElement.type())); } else { _setName = replSetNameElement.String(); } if (_setName.empty() && !doc[kOkFieldName].trueValue()) { std::string errMsg = doc[kErrMsgFieldName].str(); BSONElement errCodeElem = doc[kErrorCodeFieldName]; if (errCodeElem.ok()) { if (!errCodeElem.isNumber()) return Status(ErrorCodes::BadValue, "Error code is not a number!"); int errorCode = errCodeElem.numberInt(); return Status(ErrorCodes::Error(errorCode), errMsg); } return Status(ErrorCodes::UnknownError, errMsg); } const BSONElement hasDataElement = doc[kHasDataFieldName]; _hasDataSet = !hasDataElement.eoo(); _hasData = hasDataElement.trueValue(); const BSONElement electionTimeElement = doc[kElectionTimeFieldName]; if (electionTimeElement.eoo()) { _electionTimeSet = false; } else if (electionTimeElement.type() == bsonTimestamp) { _electionTimeSet = true; _electionTime = electionTimeElement.timestamp(); } else if (electionTimeElement.type() == Date) { _electionTimeSet = true; _electionTime = Timestamp(electionTimeElement.date()); } else { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kElectionTimeFieldName << "\" field in response to replSetHeartbeat " "command to have type Date or Timestamp, but found type " << typeName(electionTimeElement.type())); } const BSONElement timeElement = doc[kTimeFieldName]; if (timeElement.eoo()) { _timeSet = false; } else if (timeElement.isNumber()) { _timeSet = true; _time = Seconds(timeElement.numberLong()); } else { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kTimeFieldName << "\" field in response to replSetHeartbeat " "command to have a numeric type, but found type " << typeName(timeElement.type())); } _isReplSet = doc[kIsReplSetFieldName].trueValue(); Status termStatus = bsonExtractIntegerField(doc, kTermFieldName, &_term); if (!termStatus.isOK() && termStatus != ErrorCodes::NoSuchKey) { return termStatus; } Status status = bsonExtractOpTimeField(doc, kDurableOpTimeFieldName, &_durableOpTime); if (!status.isOK()) { if (status != ErrorCodes::NoSuchKey) { return status; } } else { _durableOpTimeSet = true; } // In order to support both the 3.0(V0) and 3.2(V1) heartbeats we must parse the OpTime // field based on its type. If it is a Date, we parse it as the timestamp and use // initialize's term argument to complete the OpTime type. If it is an Object, then it's // V1 and we construct an OpTime out of its nested fields. const BSONElement appliedOpTimeElement = doc[kAppliedOpTimeFieldName]; if (appliedOpTimeElement.eoo()) { _appliedOpTimeSet = false; } else if (appliedOpTimeElement.type() == bsonTimestamp) { _appliedOpTimeSet = true; _appliedOpTime = OpTime(appliedOpTimeElement.timestamp(), term); } else if (appliedOpTimeElement.type() == Date) { _appliedOpTimeSet = true; _appliedOpTime = OpTime(Timestamp(appliedOpTimeElement.date()), term); } else if (appliedOpTimeElement.type() == Object) { Status status = bsonExtractOpTimeField(doc, kAppliedOpTimeFieldName, &_appliedOpTime); _appliedOpTimeSet = true; // since a v1 OpTime was in the response, the member must be part of a replset _isReplSet = true; } else { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kAppliedOpTimeFieldName << "\" field in response to replSetHeartbeat " "command to have type Date or Timestamp, but found type " << typeName(appliedOpTimeElement.type())); } const BSONElement electableElement = doc[kIsElectableFieldName]; if (electableElement.eoo()) { _electableSet = false; } else { _electableSet = true; _electable = electableElement.trueValue(); } const BSONElement memberStateElement = doc[kMemberStateFieldName]; if (memberStateElement.eoo()) { _stateSet = false; } else if (memberStateElement.type() != NumberInt && memberStateElement.type() != NumberLong) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kMemberStateFieldName << "\" field in response to replSetHeartbeat " "command to have type NumberInt or NumberLong, but found type " << typeName(memberStateElement.type())); } else { long long stateInt = memberStateElement.numberLong(); if (stateInt < 0 || stateInt > MemberState::RS_MAX) { return Status(ErrorCodes::BadValue, str::stream() << "Value for \"" << kMemberStateFieldName << "\" in response to replSetHeartbeat is " "out of range; legal values are non-negative and no more than " << MemberState::RS_MAX); } _stateSet = true; _state = MemberState(static_cast(stateInt)); } _stateDisagreement = doc[kHasStateDisagreementFieldName].trueValue(); // Not required for the case of uninitialized members -- they have no config const BSONElement configVersionElement = doc[kConfigVersionFieldName]; // If we have an optime then we must have a configVersion if (_appliedOpTimeSet && configVersionElement.eoo()) { return Status(ErrorCodes::NoSuchKey, str::stream() << "Response to replSetHeartbeat missing required \"" << kConfigVersionFieldName << "\" field even though initialized"); } // If there is a "v" (config version) then it must be an int. if (!configVersionElement.eoo() && configVersionElement.type() != NumberInt) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kConfigVersionFieldName << "\" field in response to replSetHeartbeat to have " "type NumberInt, but found " << typeName(configVersionElement.type())); } _configVersion = configVersionElement.numberInt(); const BSONElement hbMsgElement = doc[kHbMessageFieldName]; if (hbMsgElement.eoo()) { _hbmsg.clear(); } else if (hbMsgElement.type() != String) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kHbMessageFieldName << "\" field in response to replSetHeartbeat to have " "type String, but found " << typeName(hbMsgElement.type())); } else { _hbmsg = hbMsgElement.String(); } const BSONElement syncingToElement = doc[kSyncSourceFieldName]; if (syncingToElement.eoo()) { _syncingTo = HostAndPort(); } else if (syncingToElement.type() != String) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kSyncSourceFieldName << "\" field in response to replSetHeartbeat to " "have type String, but found " << typeName(syncingToElement.type())); } else { _syncingTo = HostAndPort(syncingToElement.String()); } const BSONElement rsConfigElement = doc[kConfigFieldName]; if (rsConfigElement.eoo()) { _configSet = false; _config = ReplicaSetConfig(); return Status::OK(); } else if (rsConfigElement.type() != Object) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kConfigFieldName << "\" in response to replSetHeartbeat to have type " "Object, but found " << typeName(rsConfigElement.type())); } _configSet = true; return _config.initialize(rsConfigElement.Obj()); } MemberState ReplSetHeartbeatResponse::getState() const { invariant(_stateSet); return _state; } Timestamp ReplSetHeartbeatResponse::getElectionTime() const { invariant(_electionTimeSet); return _electionTime; } bool ReplSetHeartbeatResponse::isElectable() const { invariant(_electableSet); return _electable; } Seconds ReplSetHeartbeatResponse::getTime() const { invariant(_timeSet); return _time; } const ReplicaSetConfig& ReplSetHeartbeatResponse::getConfig() const { invariant(_configSet); return _config; } long long ReplSetHeartbeatResponse::getPrimaryId() const { invariant(_primaryIdSet); return _primaryId; } OpTime ReplSetHeartbeatResponse::getAppliedOpTime() const { invariant(_appliedOpTimeSet); return _appliedOpTime; } OpTime ReplSetHeartbeatResponse::getDurableOpTime() const { invariant(_durableOpTimeSet); return _durableOpTime; } } // namespace repl } // namespace mongo