blob: 44dc30671110adeacb52e12041006cdf0a086ffa [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "kudu/consensus/quorum_util.h"
#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "kudu/gutil/map-util.h"
#include "kudu/gutil/strings/join.h"
#include "kudu/gutil/strings/substitute.h"
#include "kudu/util/status.h"
namespace kudu {
namespace consensus {
using google::protobuf::RepeatedPtrField;
using std::map;
using std::pair;
using std::string;
using strings::Substitute;
bool IsRaftConfigMember(const std::string& uuid, const RaftConfigPB& config) {
for (const RaftPeerPB& peer : config.peers()) {
if (peer.permanent_uuid() == uuid) {
return true;
}
}
return false;
}
bool IsRaftConfigVoter(const std::string& uuid, const RaftConfigPB& config) {
for (const RaftPeerPB& peer : config.peers()) {
if (peer.permanent_uuid() == uuid) {
return peer.member_type() == RaftPeerPB::VOTER;
}
}
return false;
}
Status GetRaftConfigMember(const RaftConfigPB& config,
const std::string& uuid,
RaftPeerPB* peer_pb) {
for (const RaftPeerPB& peer : config.peers()) {
if (peer.permanent_uuid() == uuid) {
*peer_pb = peer;
return Status::OK();
}
}
return Status::NotFound(Substitute("Peer with uuid $0 not found in consensus config", uuid));
}
Status GetRaftConfigLeader(const ConsensusStatePB& cstate, RaftPeerPB* peer_pb) {
if (!cstate.has_leader_uuid() || cstate.leader_uuid().empty()) {
return Status::NotFound("Consensus config has no leader");
}
return GetRaftConfigMember(cstate.config(), cstate.leader_uuid(), peer_pb);
}
bool RemoveFromRaftConfig(RaftConfigPB* config, const string& uuid) {
RepeatedPtrField<RaftPeerPB> modified_peers;
bool removed = false;
for (const RaftPeerPB& peer : config->peers()) {
if (peer.permanent_uuid() == uuid) {
removed = true;
continue;
}
*modified_peers.Add() = peer;
}
if (!removed) return false;
config->mutable_peers()->Swap(&modified_peers);
return true;
}
int CountVoters(const RaftConfigPB& config) {
int voters = 0;
for (const RaftPeerPB& peer : config.peers()) {
if (peer.member_type() == RaftPeerPB::VOTER) {
voters++;
}
}
return voters;
}
int MajoritySize(int num_voters) {
DCHECK_GE(num_voters, 1);
return (num_voters / 2) + 1;
}
RaftPeerPB::Role GetConsensusRole(const std::string& permanent_uuid,
const ConsensusStatePB& cstate) {
if (cstate.leader_uuid() == permanent_uuid) {
if (IsRaftConfigVoter(permanent_uuid, cstate.config())) {
return RaftPeerPB::LEADER;
}
return RaftPeerPB::NON_PARTICIPANT;
}
for (const RaftPeerPB& peer : cstate.config().peers()) {
if (peer.permanent_uuid() == permanent_uuid) {
switch (peer.member_type()) {
case RaftPeerPB::VOTER:
return RaftPeerPB::FOLLOWER;
default:
return RaftPeerPB::LEARNER;
}
}
}
return RaftPeerPB::NON_PARTICIPANT;
}
Status VerifyRaftConfig(const RaftConfigPB& config, RaftConfigState type) {
std::set<string> uuids;
if (config.peers_size() == 0) {
return Status::IllegalState(
Substitute("RaftConfig must have at least one peer. RaftConfig: $0",
config.ShortDebugString()));
}
if (type == COMMITTED_QUORUM) {
// Committed configurations must have 'opid_index' populated.
if (!config.has_opid_index()) {
return Status::IllegalState(
Substitute("Committed configs must have opid_index set. RaftConfig: $0",
config.ShortDebugString()));
}
} else if (type == UNCOMMITTED_QUORUM) {
// Uncommitted configurations must *not* have 'opid_index' populated.
if (config.has_opid_index()) {
return Status::IllegalState(
Substitute("Uncommitted configs must not have opid_index set. RaftConfig: $0",
config.ShortDebugString()));
}
}
for (const RaftPeerPB& peer : config.peers()) {
if (!peer.has_permanent_uuid() || peer.permanent_uuid() == "") {
return Status::IllegalState(Substitute("One peer didn't have an uuid or had the empty"
" string. RaftConfig: $0", config.ShortDebugString()));
}
if (ContainsKey(uuids, peer.permanent_uuid())) {
return Status::IllegalState(
Substitute("Found multiple peers with uuid: $0. RaftConfig: $1",
peer.permanent_uuid(), config.ShortDebugString()));
}
uuids.insert(peer.permanent_uuid());
if (config.peers_size() > 1 && !peer.has_last_known_addr()) {
return Status::IllegalState(
Substitute("Peer: $0 has no address. RaftConfig: $1",
peer.permanent_uuid(), config.ShortDebugString()));
}
if (!peer.has_member_type()) {
return Status::IllegalState(
Substitute("Peer: $0 has no member type set. RaftConfig: $1", peer.permanent_uuid(),
config.ShortDebugString()));
}
if (peer.member_type() == RaftPeerPB::NON_VOTER) {
return Status::IllegalState(
Substitute(
"Peer: $0 is a NON_VOTER, but this isn't supported yet. RaftConfig: $1",
peer.permanent_uuid(), config.ShortDebugString()));
}
}
return Status::OK();
}
Status VerifyConsensusState(const ConsensusStatePB& cstate, RaftConfigState type) {
if (!cstate.has_current_term()) {
return Status::IllegalState("ConsensusStatePB missing current_term", cstate.ShortDebugString());
}
if (!cstate.has_config()) {
return Status::IllegalState("ConsensusStatePB missing config", cstate.ShortDebugString());
}
RETURN_NOT_OK(VerifyRaftConfig(cstate.config(), type));
if (cstate.has_leader_uuid() && !cstate.leader_uuid().empty()) {
if (!IsRaftConfigVoter(cstate.leader_uuid(), cstate.config())) {
return Status::IllegalState(
Substitute("Leader with UUID $0 is not a VOTER in the config! Consensus state: $1",
cstate.leader_uuid(), cstate.ShortDebugString()));
}
}
return Status::OK();
}
std::string DiffRaftConfigs(const RaftConfigPB& old_config,
const RaftConfigPB& new_config) {
// Create dummy ConsensusState objects so we can reuse the code
// from the below function.
ConsensusStatePB old_state;
old_state.mutable_config()->CopyFrom(old_config);
ConsensusStatePB new_state;
new_state.mutable_config()->CopyFrom(new_config);
return DiffConsensusStates(old_state, new_state);
}
string DiffConsensusStates(const ConsensusStatePB& old_state,
const ConsensusStatePB& new_state) {
bool leader_changed = old_state.leader_uuid() != new_state.leader_uuid();
bool term_changed = old_state.current_term() != new_state.current_term();
bool config_changed = old_state.config().opid_index() != new_state.config().opid_index();
// Construct a map from Peer UUID to '<old peer, new peer>' pairs.
// Due to the default construction nature of std::map and std::pair, if a peer
// is present in one configuration but not the other, we'll end up with an empty
// protobuf in that element of the pair.
map<string, pair<RaftPeerPB, RaftPeerPB>> peer_infos;
for (const auto& p : old_state.config().peers()) {
peer_infos[p.permanent_uuid()].first = p;
}
for (const auto& p : new_state.config().peers()) {
peer_infos[p.permanent_uuid()].second = p;
}
// Now collect strings representing the changes.
vector<string> change_strs;
if (config_changed) {
change_strs.push_back(
Substitute("config changed from index $0 to $1",
old_state.config().opid_index(),
new_state.config().opid_index()));
}
if (term_changed) {
change_strs.push_back(
Substitute("term changed from $0 to $1",
old_state.current_term(),
new_state.current_term()));
}
if (leader_changed) {
string old_leader = "<none>";
string new_leader = "<none>";
if (old_state.has_leader_uuid()) {
old_leader = Substitute("$0 ($1)",
old_state.leader_uuid(),
peer_infos[old_state.leader_uuid()].first.last_known_addr().host());
}
if (new_state.has_leader_uuid()) {
new_leader = Substitute("$0 ($1)",
new_state.leader_uuid(),
peer_infos[new_state.leader_uuid()].second.last_known_addr().host());
}
change_strs.push_back(Substitute("leader changed from $0 to $1",
old_leader, new_leader));
}
for (const auto& e : peer_infos) {
const auto& old_peer = e.second.first;
const auto& new_peer = e.second.second;
if (old_peer.has_permanent_uuid() && !new_peer.has_permanent_uuid()) {
change_strs.push_back(
Substitute("$0 $1 ($2) evicted",
RaftPeerPB_MemberType_Name(old_peer.member_type()),
old_peer.permanent_uuid(),
old_peer.last_known_addr().host()));
} else if (!old_peer.has_permanent_uuid() && new_peer.has_permanent_uuid()) {
change_strs.push_back(
Substitute("$0 $1 ($2) added",
RaftPeerPB_MemberType_Name(new_peer.member_type()),
new_peer.permanent_uuid(),
new_peer.last_known_addr().host()));
} else if (old_peer.has_permanent_uuid() && new_peer.has_permanent_uuid()) {
if (old_peer.member_type() != new_peer.member_type()) {
change_strs.push_back(
Substitute("$0 ($1) changed from $2 to $3",
old_peer.permanent_uuid(),
old_peer.last_known_addr().host(),
RaftPeerPB_MemberType_Name(old_peer.member_type()),
RaftPeerPB_MemberType_Name(new_peer.member_type())));
}
}
}
// We expect to have detected some differences above, but in case
// someone forgets to update this function when adding a new field,
// it's still useful to report some change unless the protobufs are identical.
// So, we fall back to just dumping the before/after debug strings.
if (change_strs.empty()) {
if (old_state.ShortDebugString() == new_state.ShortDebugString()) {
return "no change";
}
return Substitute("change from {$0} to {$1}",
old_state.ShortDebugString(),
new_state.ShortDebugString());
}
return JoinStrings(change_strs, ", ");
}
} // namespace consensus
} // namespace kudu