blob: 8f541bb918cf0b9d17aabb00edfeeb06c8d22573 [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.
//
// Ksck, a tool to run a Kudu System Check.
#ifndef KUDU_TOOLS_KSCK_H
#define KUDU_TOOLS_KSCK_H
#include <cstdint>
#include <map>
#include <memory>
#include <iosfwd>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <boost/optional/optional.hpp>
#include <glog/logging.h>
#include <gtest/gtest_prod.h>
#include "kudu/common/schema.h"
#include "kudu/consensus/metadata.pb.h"
#include "kudu/gutil/macros.h"
#include "kudu/gutil/strings/substitute.h"
#include "kudu/tablet/metadata.pb.h"
#include "kudu/tablet/tablet.pb.h" // IWYU pragma: keep
#include "kudu/tools/color.h"
#include "kudu/util/monotime.h"
#include "kudu/util/status.h"
namespace kudu {
namespace tools {
class KsckTable;
// Options for checksum scans.
struct ChecksumOptions {
public:
ChecksumOptions();
ChecksumOptions(MonoDelta timeout,
int scan_concurrency,
bool use_snapshot,
uint64_t snapshot_timestamp);
// The maximum total time to wait for results to come back from all replicas.
MonoDelta timeout;
// The maximum number of concurrent checksum scans to run per tablet server.
int scan_concurrency;
// Whether to use a snapshot checksum scanner.
bool use_snapshot;
// The snapshot timestamp to use for snapshot checksum scans.
uint64_t snapshot_timestamp;
// A timestamp indicating that the current time should be used for a checksum snapshot.
static const uint64_t kCurrentTimestamp;
};
// Representation of a tablet replica on a tablet server.
class KsckTabletReplica {
public:
KsckTabletReplica(std::string ts_uuid, bool is_leader)
: is_leader_(is_leader),
ts_uuid_(std::move(ts_uuid)) {
}
const bool& is_leader() const {
return is_leader_;
}
const std::string& ts_uuid() const {
return ts_uuid_;
}
private:
const bool is_leader_;
const std::string ts_uuid_;
DISALLOW_COPY_AND_ASSIGN(KsckTabletReplica);
};
// Possible types of consensus configs.
enum class KsckConsensusConfigType {
// A config reported by the master.
MASTER,
// A config that has been committed.
COMMITTED,
// A config that has not yet been committed.
PENDING,
};
// Representation of a consensus state.
struct KsckConsensusState {
KsckConsensusState(KsckConsensusConfigType type,
boost::optional<int64_t> term,
boost::optional<int64_t> opid_index,
boost::optional<std::string> leader_uuid,
std::vector<std::string> peers)
: type(type),
term(std::move(term)),
opid_index(std::move(opid_index)),
leader_uuid(std::move(leader_uuid)),
peer_uuids(peers.cbegin(), peers.cend()) {
}
// Two KsckConsensusState structs match if they have the same
// leader_uuid, the same set of peers, and one of the following holds:
// - at least one of them is of type MASTER
// - they are configs of the same type and they have the same term
bool Matches(const KsckConsensusState &other) const {
bool same_leader_and_peers = leader_uuid == other.leader_uuid && peer_uuids == other.peer_uuids;
if (type == KsckConsensusConfigType::MASTER || other.type == KsckConsensusConfigType::MASTER) {
return same_leader_and_peers;
}
return type == other.type && term == other.term && same_leader_and_peers;
}
KsckConsensusConfigType type;
boost::optional<int64_t> term;
boost::optional<int64_t> opid_index;
boost::optional<std::string> leader_uuid;
std::set<std::string> peer_uuids;
};
// Representation of a tablet belonging to a table. The tablet is composed of replicas.
class KsckTablet {
public:
// TODO add start/end keys, stale.
KsckTablet(KsckTable* table, std::string id)
: id_(std::move(id)),
table_(table) {
}
const std::string& id() const {
return id_;
}
const std::vector<std::shared_ptr<KsckTabletReplica> >& replicas() const {
return replicas_;
}
void set_replicas(std::vector<std::shared_ptr<KsckTabletReplica> >& replicas) {
replicas_.assign(replicas.begin(), replicas.end());
}
KsckTable* table() {
return table_;
}
private:
const std::string id_;
std::vector<std::shared_ptr<KsckTabletReplica>> replicas_;
KsckTable* table_;
DISALLOW_COPY_AND_ASSIGN(KsckTablet);
};
// Representation of a table. Composed of tablets.
class KsckTable {
public:
KsckTable(std::string name, const Schema& schema, int num_replicas)
: name_(std::move(name)), schema_(schema), num_replicas_(num_replicas) {}
const std::string& name() const {
return name_;
}
const Schema& schema() const {
return schema_;
}
int num_replicas() const {
return num_replicas_;
}
void set_tablets(std::vector<std::shared_ptr<KsckTablet>> tablets) {
tablets_ = std::move(tablets);
}
std::vector<std::shared_ptr<KsckTablet> >& tablets() {
return tablets_;
}
private:
const std::string name_;
const Schema schema_;
const int num_replicas_;
std::vector<std::shared_ptr<KsckTablet>> tablets_;
DISALLOW_COPY_AND_ASSIGN(KsckTable);
};
// Interface for reporting progress on checksumming a single
// replica.
class ChecksumProgressCallbacks {
public:
virtual ~ChecksumProgressCallbacks() {}
// Report incremental progress from the server side.
// 'disk_bytes_summed' only counts data read from DiskRowSets on the server side
// and does not count MRS bytes, etc.
virtual void Progress(int64_t delta_rows_summed, int64_t delta_disk_bytes_summed) = 0;
// The scan of the current tablet is complete.
virtual void Finished(const Status& status, uint64_t checksum) = 0;
};
// The following two classes must be extended in order to communicate with their respective
// components. The two main use cases envisioned for this are:
// - To be able to mock a cluster to more easily test the Ksck checks.
// - To be able to communicate with a real Kudu cluster.
// Class that must be extended to represent a tablet server.
class KsckTabletServer {
public:
// Map from tablet id to tablet replicas.
typedef std::unordered_map<std::string, tablet::TabletStatusPB> TabletStatusMap;
// Map from (tserver id, tablet id) to tablet consensus information.
typedef std::map
<std::pair<std::string, std::string>, consensus::ConsensusStatePB> TabletConsensusStateMap;
explicit KsckTabletServer(std::string uuid) : uuid_(std::move(uuid)) {}
virtual ~KsckTabletServer() { }
// Connects to the configured tablet server and populates the fields of this class.
virtual Status FetchInfo() = 0;
// Connects to the configured tablet server and populates the consensus map.
virtual Status FetchConsensusState() = 0;
// Executes a checksum scan on the associated tablet, and runs the callback
// with the result. The callback must be threadsafe and non-blocking.
virtual void RunTabletChecksumScanAsync(
const std::string& tablet_id,
const Schema& schema,
const ChecksumOptions& options,
ChecksumProgressCallbacks* callbacks) = 0;
virtual const std::string& uuid() const {
return uuid_;
}
std::string ToString() const {
return strings::Substitute("$0 ($1)", uuid(), address());
}
virtual std::string address() const = 0;
bool is_healthy() const {
CHECK_NE(state_, kUninitialized);
return state_ == kFetched;
}
// Gets the mapping of tablet id to tablet replica for this tablet server.
const TabletStatusMap& tablet_status_map() const {
CHECK_EQ(state_, kFetched);
return tablet_status_map_;
}
// Gets the mapping of tablet id to tablet consensus info for this tablet server.
const TabletConsensusStateMap& tablet_consensus_state_map() const {
CHECK_EQ(state_, kFetched);
return tablet_consensus_state_map_;
}
tablet::TabletStatePB ReplicaState(const std::string& tablet_id) const;
uint64_t current_timestamp() const {
CHECK_EQ(state_, kFetched);
return timestamp_;
}
protected:
friend class KsckTest;
FRIEND_TEST(KsckTest, TestMismatchedAssignments);
FRIEND_TEST(KsckTest, TestConsensusConflictExtraPeer);
FRIEND_TEST(KsckTest, TestConsensusConflictMissingPeer);
FRIEND_TEST(KsckTest, TestConsensusConflictDifferentLeader);
enum State {
kUninitialized,
kFetchFailed,
kFetched
};
State state_ = kUninitialized;
TabletStatusMap tablet_status_map_;
TabletConsensusStateMap tablet_consensus_state_map_;
uint64_t timestamp_;
const std::string uuid_;
private:
DISALLOW_COPY_AND_ASSIGN(KsckTabletServer);
};
// Class that must be extended to represent a master.
class KsckMaster {
public:
// Map of KsckTabletServer objects keyed by tablet server permanent_uuid.
typedef std::unordered_map<std::string, std::shared_ptr<KsckTabletServer> > TSMap;
KsckMaster() { }
virtual ~KsckMaster() { }
// Connects to the configured Master.
virtual Status Connect() = 0;
// Gets the list of Tablet Servers from the Master and stores it in the passed
// map, which is keyed on server permanent_uuid.
// 'tablet_servers' is only modified if this method returns OK.
virtual Status RetrieveTabletServers(TSMap* tablet_servers) = 0;
// Gets the list of tables from the Master and stores it in the passed vector.
// tables is only modified if this method returns OK.
virtual Status RetrieveTablesList(std::vector<std::shared_ptr<KsckTable> >* tables) = 0;
// Gets the list of tablets for the specified table and stores the list in it.
// The table's tablet list is only modified if this method returns OK.
virtual Status RetrieveTabletsList(const std::shared_ptr<KsckTable>& table) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(KsckMaster);
};
// Class used to communicate with the cluster. It bootstraps this by using the provided master.
class KsckCluster {
public:
explicit KsckCluster(std::shared_ptr<KsckMaster> master)
: master_(std::move(master)) {}
~KsckCluster();
// Fetches list of tables, tablets, and tablet servers from the master and
// populates the full list in cluster_->tables().
Status FetchTableAndTabletInfo();
const std::shared_ptr<KsckMaster>& master() {
return master_;
}
const KsckMaster::TSMap& tablet_servers() {
return tablet_servers_;
}
const std::vector<std::shared_ptr<KsckTable> >& tables() {
return tables_;
}
private:
friend class KsckTest;
// Gets the list of tablet servers from the Master.
Status RetrieveTabletServers();
// Gets the list of tables from the Master.
Status RetrieveTablesList();
// Fetch the list of tablets for the given table from the Master.
Status RetrieveTabletsList(const std::shared_ptr<KsckTable>& table);
const std::shared_ptr<KsckMaster> master_;
KsckMaster::TSMap tablet_servers_;
std::vector<std::shared_ptr<KsckTable> > tables_;
DISALLOW_COPY_AND_ASSIGN(KsckCluster);
};
// Externally facing class to run checks against the provided cluster.
class Ksck {
public:
explicit Ksck(std::shared_ptr<KsckCluster> cluster,
std::ostream* out = nullptr);
~Ksck() = default;
// Set whether ksck should verify that each of the tablet's raft configurations
// has the same number of replicas that is specified by the tablet metadata.
// (default: true)
void set_check_replica_count(bool check) {
check_replica_count_ = check;
}
// Setters for filtering the tables/tablets to be checked.
//
// Filter strings are glob-style patterns. For example, 'Foo*' matches
// all tables whose name begins with 'Foo'.
//
// If tables is not empty, checks only the named tables.
// If tablets is not empty, checks only the specified tablet IDs.
// If both are specified, takes the intersection.
// If both are empty (unset), all tables and tablets are checked.
void set_table_filters(std::vector<std::string> table_names) {
table_filters_ = std::move(table_names);
}
// See above.
void set_tablet_id_filters(std::vector<std::string> tablet_ids) {
tablet_id_filters_ = std::move(tablet_ids);
}
// Verifies that it can connect to the master.
Status CheckMasterRunning();
// Populates all the cluster table and tablet info from the master.
Status FetchTableAndTabletInfo();
// Connects to all tablet servers, checks that they are alive, and fetches
// their current status and tablet information.
Status FetchInfoFromTabletServers();
// Establishes a connection with the specified Tablet Server.
// Must first call FetchTableAndTabletInfo().
Status ConnectToTabletServer(const std::shared_ptr<KsckTabletServer>& ts);
// Verifies that all the tablets in all tables matching the filters have
// enough replicas, and that each tablet's view of the tablet's consensus
// matches every other tablet's and the master's.
// Must first call FetchTableAndTabletInfo() and, if doing checks against
// tablet servers (the default), must first call FetchInfoFromTabletServers().
Status CheckTablesConsistency();
// Verifies data checksums on all tablets by doing a scan of the database on each replica.
// Must first call FetchTableAndTabletInfo().
Status ChecksumData(const ChecksumOptions& options);
private:
enum class CheckResult {
OK,
UNDER_REPLICATED,
UNAVAILABLE,
// There was a discrepancy among the tablets' consensus configs and the master's.
CONSENSUS_MISMATCH,
};
// Summarizes the result of VerifyTable().
struct TableSummary {
std::string name;
int healthy_tablets = 0;
int underreplicated_tablets = 0;
int consensus_mismatch_tablets = 0;
int unavailable_tablets = 0;
int TotalTablets() const {
return healthy_tablets + underreplicated_tablets +
consensus_mismatch_tablets + unavailable_tablets;
}
// Summarize the table's status with a tablet CheckResult.
// A table's status is determined by the health of the least healthy tablet.
CheckResult TableStatus() const {
if (unavailable_tablets > 0) {
return CheckResult::UNAVAILABLE;
}
if (consensus_mismatch_tablets > 0) {
return CheckResult::CONSENSUS_MISMATCH;
}
if (underreplicated_tablets > 0) {
return CheckResult::UNDER_REPLICATED;
}
return CheckResult::OK;
}
};
bool VerifyTable(const std::shared_ptr<KsckTable>& table, TableSummary* ts);
bool VerifyTableWithTimeout(const std::shared_ptr<KsckTable>& table,
const MonoDelta& timeout,
const MonoDelta& retry_interval);
CheckResult VerifyTablet(const std::shared_ptr<KsckTablet>& tablet,
int table_num_replicas);
// Print an informational message to this instance's output stream.
std::ostream& Out() {
return *out_;
}
// Print an error message to this instance's output stream.
std::ostream& Error() {
return (*out_) << Color(AnsiCode::RED, "ERROR: ");
}
// Print a warning message to this instance's output stream.
std::ostream& Warn() {
return (*out_) << Color(AnsiCode::YELLOW, "WARNING: ");
}
const std::shared_ptr<KsckCluster> cluster_;
bool check_replica_count_ = true;
std::vector<std::string> table_filters_;
std::vector<std::string> tablet_id_filters_;
std::ostream* const out_;
DISALLOW_COPY_AND_ASSIGN(Ksck);
};
} // namespace tools
} // namespace kudu
#endif // KUDU_TOOLS_KSCK_H