blob: 6044e1d26cbf63c7aa6d3f5c10c7b705e752a279 [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.
#pragma once
#include <array>
#include <atomic>
#include <cstdint>
#include <iosfwd>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <glog/logging.h>
#include <gtest/gtest_prod.h>
#include "kudu/common/partition.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/rebalance/cluster_status.h" // IWYU pragma: keep
#include "kudu/server/server_base.pb.h" // IWYU pragma: keep
#include "kudu/tablet/metadata.pb.h"
#include "kudu/tablet/tablet.pb.h" // IWYU pragma: keep
#include "kudu/tools/ksck_results.h"
#include "kudu/util/status.h"
#include "kudu/util/threadpool.h"
namespace kudu {
namespace rpc {
class Messenger;
} // namespace rpc
namespace tools {
class KsckChecksumManager;
class KsckTable;
struct KsckChecksumOptions;
// Representation of a tablet replica on a tablet server.
class KsckTabletReplica {
public:
KsckTabletReplica(std::string ts_uuid, bool is_leader, bool is_voter)
: ts_uuid_(std::move(ts_uuid)),
is_leader_(is_leader),
is_voter_(is_voter) {
}
const std::string& ts_uuid() const {
return ts_uuid_;
}
bool is_leader() const {
return is_leader_;
}
bool is_voter() const {
return is_voter_;
}
private:
const std::string ts_uuid_;
const bool is_leader_;
const bool is_voter_;
DISALLOW_COPY_AND_ASSIGN(KsckTabletReplica);
};
// Representation of a tablet belonging to a table. The tablet is composed of replicas.
class KsckTablet {
public:
KsckTablet(KsckTable* table, std::string id, Partition partition)
: table_(table),
id_(std::move(id)),
partition_(std::move(partition)) {
}
const std::string& id() const {
return id_;
}
const std::vector<std::shared_ptr<KsckTabletReplica>>& replicas() const {
return replicas_;
}
const Partition& partition() const {
return partition_;
}
void set_replicas(std::vector<std::shared_ptr<KsckTabletReplica>> replicas) {
replicas_.swap(replicas);
}
KsckTable* table() {
return table_;
}
private:
KsckTable* table_;
const std::string id_;
const Partition partition_;
std::vector<std::shared_ptr<KsckTabletReplica>> replicas_;
DISALLOW_COPY_AND_ASSIGN(KsckTablet);
};
// Representation of a table. Composed of tablets.
class KsckTable {
public:
KsckTable(std::string id, std::string name, Schema schema, int num_replicas)
: id_(std::move(id)),
name_(std::move(name)),
schema_(std::move(schema)),
num_replicas_(num_replicas) {
}
const std::string& id() const {
return id_;
}
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);
}
const std::vector<std::shared_ptr<KsckTablet>>& tablets() const {
return tablets_;
}
private:
const std::string id_;
const std::string name_;
const Schema schema_;
const int num_replicas_;
std::vector<std::shared_ptr<KsckTablet>> tablets_;
DISALLOW_COPY_AND_ASSIGN(KsckTable);
};
// Enum representing the fetch status of a ksck master or tablet server.
enum class KsckFetchState {
// Information has not yet been fetched.
UNINITIALIZED,
// The attempt to fetch information failed.
FETCH_FAILED,
// Information was fetched successfully.
FETCHED,
};
// Required for logging in case of CHECK failures.
std::ostream& operator<<(std::ostream& lhs, KsckFetchState state);
// Enum representing logical categories of flags used in a Kudu cluster.
enum FlagsCategory {
// Flags specific to the time source, clock, and alike.
TIME_SOURCE = 0,
// Flags tagged hidden, experimental, or unsafe.
UNUSUAL = 1,
// Utility members used for range-related expressions.
// NOTE: update the MAX member upon updates
MIN = 0,
MAX = UNUSUAL
};
// Information on flags fetched using GetFlags() RPC. Consists of the status of
// the GetFlags() operation and the result flags wrapped into std::optional,
// where the latter is std::nullopt unless the RPC was successful.
struct FetchedFlags {
KsckFetchState state = KsckFetchState::UNINITIALIZED;
std::optional<server::GetFlagsResponsePB> flags;
};
// Flags retrieved using GetFlags(), indexed by FlagsCategory.
typedef std::array<FetchedFlags, FlagsCategory::MAX + 1> FetchedFlagsByCategory;
// Structure to represent a filter to build appropriate request for GetFlags()
// RPC.
struct FlagsFetchFilter {
const std::vector<std::string> flags;
const std::vector<std::string> tags;
};
// Get filter to build GetFlags() RPC for the specified flags category.
const FlagsFetchFilter& GetFlagsCategoryFilter(FlagsCategory category);
// Get string representation for the specified flag category.
const char* FlagsCategoryToString(FlagsCategory category);
// Convert string representation of flag category into corresponding enum field.
// On success, returns Status::OK() and outputs the result category into the
// 'category' output parameter. On failure returns Status::InvalidParameter().
Status StringToFlagsCategory(const std::string& str,
FlagsCategory* category);
// Convert comma-separated string into vector of flag categories. The result
// is appended to the 'categories' output parameter. In addition, this function
// removes duplicates in resulting 'categories'.
Status StringToFlagsCategories(const std::string& str,
std::vector<FlagsCategory>* categories);
// The following three 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 master.
class KsckMaster {
public:
explicit KsckMaster(std::string address) :
address_(std::move(address)),
uuid_(strings::Substitute("$0 ($1)", kDummyUuid, address_)) {}
virtual ~KsckMaster() = default;
virtual Status Init() = 0;
// Connects to the master, checking if it is healthy and gathering basic info.
virtual Status FetchInfo() = 0;
// Connects to the master and populates the consensus map.
virtual Status FetchConsensusState() = 0;
// Fetch flags for the requested categories.
virtual Status FetchFlags(const std::vector<FlagsCategory>& categories) = 0;
// Since masters are provided by address, FetchInfo() must be called before
// calling this method.
virtual const std::string& uuid() const {
CHECK_NE(KsckFetchState::UNINITIALIZED, state_);
return uuid_;
}
virtual const std::string& address() const {
return address_;
}
virtual const std::optional<std::string>& version() const {
CHECK_NE(KsckFetchState::UNINITIALIZED, state_);
return version_;
}
virtual const std::optional<consensus::ConsensusStatePB>& cstate() const {
CHECK_NE(KsckFetchState::UNINITIALIZED, state_);
return cstate_;
}
virtual const std::optional<server::GetFlagsResponsePB>& flags(
FlagsCategory category) const {
CHECK_GE(category, FlagsCategory::MIN);
CHECK_LE(category, FlagsCategory::MAX);
CHECK_NE(KsckFetchState::UNINITIALIZED, flags_by_category_[category].state);
return flags_by_category_[category].flags;
}
std::string ToString() const {
return strings::Substitute("$0 ($1)", uuid(), address());
}
bool is_healthy() const {
CHECK_NE(KsckFetchState::UNINITIALIZED, state_);
return state_ == KsckFetchState::FETCHED;
}
// Masters that haven't been fetched from or that were unavailable have a
// dummy uuid.
static constexpr const char* const kDummyUuid = "<unknown>";
protected:
friend class KsckTest;
const std::string address_;
std::string uuid_;
// state_ reflects whether the fetch of the core ksck info has been done, and
// if it succeeded or failed.
KsckFetchState state_ = KsckFetchState::UNINITIALIZED;
// May be none if fetching info from the master fails.
std::optional<std::string> version_;
// Fetched flags indexed by category.
FetchedFlagsByCategory flags_by_category_;
// May be none if consensus state fetch fails.
std::optional<consensus::ConsensusStatePB> cstate_;
private:
DISALLOW_COPY_AND_ASSIGN(KsckMaster);
};
// 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, std::string location = "")
: uuid_(std::move(uuid)),
location_(std::move(location)) {}
virtual ~KsckTabletServer() {}
// Connects to the configured tablet server and populates the fields of this class. 'health' must
// not be nullptr.
//
// If Status is OK, 'health' will be HEALTHY
// If the UUID is not what ksck expects, 'health' will be WRONG_SERVER_UUID
// Otherwise 'health' will be UNAVAILABLE
virtual Status FetchInfo(cluster_summary::ServerHealth* health) = 0;
// Connects to the configured tablet server and populates the consensus map. 'health' must not be
// nullptr.
//
// If Status is OK, 'health' will be HEALTHY
// Otherwise 'health' will be UNAVAILABLE
virtual Status FetchConsensusState(cluster_summary::ServerHealth* health) = 0;
// Fetches flags for the requested categories.
virtual Status FetchFlags(const std::vector<FlagsCategory>& categories) = 0;
// Fetches and updates the current timestamp from the tablet server.
virtual void FetchCurrentTimestampAsync() = 0;
virtual Status FetchCurrentTimestamp() = 0;
// Fetches the quiescing information for the tablet server. This is best
// effort; a message is logged if it is unsuccessful (e.g. if the server
// doesn't support the quiescing RPC).
virtual void FetchQuiescingInfo() = 0;
// Executes a checksum scan on a tablet and reports the result to 'manager'.
virtual void RunTabletChecksumScanAsync(
const std::string& tablet_id,
const Schema& schema,
const KsckChecksumOptions& options,
std::shared_ptr<KsckChecksumManager> manager) = 0;
virtual const std::string& uuid() const {
return uuid_;
}
virtual const std::string& location() const {
return location_;
}
std::string ToString() const {
return strings::Substitute("$0 ($1)", uuid(), address());
}
virtual std::string address() const = 0;
bool is_healthy() const {
CHECK_NE(KsckFetchState::UNINITIALIZED, state_);
return state_ == KsckFetchState::FETCHED;
}
// Gets the mapping of tablet id to tablet replica for this tablet server.
const TabletStatusMap& tablet_status_map() const {
CHECK_EQ(KsckFetchState::FETCHED, state_);
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(KsckFetchState::FETCHED, state_);
return tablet_consensus_state_map_;
}
tablet::TabletStatePB ReplicaState(const std::string& tablet_id) const;
virtual const std::optional<std::string>& version() const {
CHECK_NE(KsckFetchState::UNINITIALIZED, state_);
return version_;
}
virtual const std::optional<server::GetFlagsResponsePB>& flags(
FlagsCategory category) const {
DCHECK_GE(category, FlagsCategory::MIN);
DCHECK_LE(category, FlagsCategory::MAX);
CHECK_NE(KsckFetchState::UNINITIALIZED, flags_by_category_[category].state);
return flags_by_category_[category].flags;
}
virtual const std::optional<cluster_summary::QuiescingInfo>& quiescing_info() const {
CHECK_NE(KsckFetchState::UNINITIALIZED, state_);
return quiescing_info_;
}
uint64_t current_timestamp() const {
CHECK_EQ(KsckFetchState::FETCHED, state_);
return timestamp_;
}
protected:
friend class KsckTest;
FRIEND_TEST(KsckTest, TestConsensusConflictExtraPeer);
FRIEND_TEST(KsckTest, TestConsensusConflictDifferentLeader);
FRIEND_TEST(KsckTest, TestConsensusConflictMissingPeer);
FRIEND_TEST(KsckTest, TestMasterNotReportingTabletServerWithConsensusConflict);
FRIEND_TEST(KsckTest, TestMismatchedAssignments);
FRIEND_TEST(KsckTest, TestTabletCopying);
// state_ reflects whether the fetch of the core info has been done, and if
// it succeeded or failed.
KsckFetchState state_ = KsckFetchState::UNINITIALIZED;
// May be none if fetching info from the tablet server fails.
std::optional<std::string> version_;
// Fetched flags indexed by category.
FetchedFlagsByCategory flags_by_category_;
TabletStatusMap tablet_status_map_;
TabletConsensusStateMap tablet_consensus_state_map_;
// May be none if the quiescing request fails.
std::optional<cluster_summary::QuiescingInfo> quiescing_info_;
const std::string uuid_;
std::string location_;
std::atomic<uint64_t> timestamp_;
private:
DISALLOW_COPY_AND_ASSIGN(KsckTabletServer);
};
// Class used to communicate with a cluster.
class KsckCluster {
public:
virtual ~KsckCluster() = default;
// A list of masters.
typedef std::vector<std::shared_ptr<KsckMaster>> MasterList;
// Map of KsckTabletServer objects keyed by tablet server uuid.
typedef std::map<std::string, std::shared_ptr<KsckTabletServer>> TSMap;
// Fetches the lists of tables, tablets, and tablet servers from the master.
Status FetchTableAndTabletInfo() {
RETURN_NOT_OK(Connect());
RETURN_NOT_OK(RetrieveTablesList());
RETURN_NOT_OK(RetrieveTabletServers());
RETURN_NOT_OK(RetrieveAllTablets());
return Status::OK();
}
// Connects to the cluster (i.e. to the leader master).
virtual Status Connect() = 0;
// Fetches the list of tablet servers.
virtual Status RetrieveTabletServers() = 0;
// Fetches the list of tables.
virtual Status RetrieveTablesList() = 0;
// Fetches all tablets in the cluster.
virtual Status RetrieveAllTablets() = 0;
// Fetches the list of tablets for the given table.
// The table's tablet list is modified only if this method returns OK.
virtual Status RetrieveTabletsList(const std::shared_ptr<KsckTable>& table) = 0;
const MasterList& masters() const {
return masters_;
}
const TSMap& tablet_servers() const {
return tablet_servers_;
}
const KsckTServerStateMap& ts_states() const {
return ts_states_;
}
const std::shared_ptr<KsckTable>& txn_sys_table() const {
return txn_sys_table_;
}
const std::vector<std::shared_ptr<KsckTable>>& tables() const {
return tables_;
}
// Returns a reference to the messenger used by this instance.
// Returns nullptr if no messenger is used.
virtual std::shared_ptr<rpc::Messenger> messenger() const {
return nullptr;
}
// Setters for filtering the tables/tablets to be checked.
// Equivalent to the same functions in class 'Ksck'.
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);
}
const std::vector<std::string>& table_filters() const {
return table_filters_;
}
const std::vector<std::string>& tablet_id_filters() const {
return tablet_id_filters_;
}
int filtered_tables_count() const {
return filtered_tables_count_;
}
int filtered_tablets_count() const {
return filtered_tablets_count_;
}
protected:
KsckCluster() : filtered_tables_count_(0), filtered_tablets_count_(0) {}
MasterList masters_;
TSMap tablet_servers_;
KsckTServerStateMap ts_states_;
std::shared_ptr<KsckTable> txn_sys_table_;
std::vector<std::shared_ptr<KsckTable>> tables_;
std::unique_ptr<ThreadPool> pool_;
std::vector<std::string> table_filters_;
std::vector<std::string> tablet_id_filters_;
// The count of tables/tablets filtered out.
// Used to determine whether all tables/tablets have been filtered out.
std::atomic<int> filtered_tables_count_;
std::atomic<int> filtered_tablets_count_;
private:
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);
}
void set_print_sections(const std::vector<std::string>& sections);
const KsckResults& results() const {
return results_;
}
// Check that all masters are healthy.
Status CheckMasterHealth();
// Check that the masters' consensus information is consistent.
Status CheckMasterConsensus();
// Check for "unusual" flags on masters.
// "Unusual" flags are ones tagged hidden, experimental, or unsafe and set
// to a non-default value.
// Must first call CheckMasterHealth().
Status CheckMasterUnusualFlags();
// Check for the consistency of flag values across all masters in the cluster.
// Must first call CheckMasterHealth().
Status CheckMasterDivergedFlags();
// Verifies that it can connect to the cluster, i.e. that it can contact a
// leader master.
Status CheckClusterRunning();
// Populates all the cluster table and tablet info from the master.
// Must first call CheckClusterRunning().
Status FetchTableAndTabletInfo();
// Connects to all tablet servers, checks that they are alive, and fetches
// their current status and tablet information.
// Must first call FetchTableAndTabletInfo().
Status FetchInfoFromTabletServers();
// Check for "unusual" flags on tablet servers.
// "Unusual" flags are ones tagged hidden, experimental, or unsafe and set
// to a non-default value.
// Must first call FetchInfoFromTabletServers().
Status CheckTabletServerUnusualFlags();
// Check for the consistency of flag values across all tablet servers
// in the cluster.
// Must first call FetchInfoFromTabletServers().
Status CheckTabletServerDivergedFlags();
// Check for the consistency of flag values across all tablet servers and
// masters in the cluster.
// Must first call CheckMasterDivergedFlags() and
// CheckTabletServerDivergedFlags().
Status CheckDivergedFlags();
// Check for version inconsistencies among all servers.
Status CheckServerVersions();
// 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 KsckChecksumOptions& opts);
// Runs all the checks of ksck in the proper order, including checksum scans,
// if enabled. Returns OK if and only if all checks succeed.
Status Run();
// Prints the results of ksck.
Status PrintResults();
// Performs all checks and prints the results.
// Returns OK if and only if the ksck finds the cluster completely healthy,
// and printing succeeds.
Status RunAndPrintResults();
private:
friend class KsckTest;
// Accumulate information about flags from a server into a FlagToServersMap
// and, optionally, flag's tags into a FlagTagsMap. 'flags_to_server_map' must
// not be null; 'flag_tags_map' may be null: in such case no information on
// flag's tags is accumulated.
static void AddFlagsToFlagMaps(const server::GetFlagsResponsePB& flags,
const std::string& server_address,
KsckFlagToServersMap* flags_to_servers_map,
KsckFlagTagsMap* flag_tags_map = nullptr);
// Returns true if the table is healthy, creating a health summary for it and
// adding the table to 'table_summaries'. Only adds a summary if there are
// tablets in the table.
bool VerifyTable(const std::shared_ptr<KsckTable>& table,
std::vector<cluster_summary::TableSummary>* table_summaries);
cluster_summary::HealthCheckResult VerifyTablet(
const std::shared_ptr<KsckTablet>& tablet,
int table_num_replicas);
const std::shared_ptr<KsckCluster> cluster_;
std::unique_ptr<ThreadPool> pool_;
bool check_replica_count_ = true;
std::vector<std::string> table_filters_;
std::vector<std::string> tablet_id_filters_;
std::ostream* const out_;
// The output sections, could be a composite of PrintSections::Values
int print_sections_flags_ = PrintSections::ALL_SECTIONS;
KsckResults results_;
DISALLOW_COPY_AND_ASSIGN(Ksck);
};
} // namespace tools
} // namespace kudu