Repository: kudu Updated Branches: refs/heads/master 72df429ec -> 05d594867
[tools] ksck improvements [7/n] Add JSON output option to ksck This adds support for JSON output to ksck, using a new flag --ksck_format that supports four options: 'plain_concise', 'plain_full', 'json_pretty', and 'json_concise'. 'plain_concise' formatting is the default: it's the original formatting and it is not changed by this patch. 'plain_full' supersedes the --verbose flag. The 'json_pretty' option pretty-prints a json representation of all the information gathered by ksck. 'json_compact' ugly-prints the same, and is suitable for parsing by other programs, like jq. There's a knock-on effect of how this change is implemented: since KsckResults is translated to PB and then to JSON via generic PB-to-JSON code, the printing for CONSENSUS_MISMATCH health has changed to match its PB stringification. Previously it was stringified as UNAVAILABLE. Note that it isn't necessarily true that CONSENSUS_MISMATCH implies unavailable, anyway, since, e.g. a replica not yet aware of a new leader will cause a CONSENSUS_MISMATCH state on a tablet that's available. Here's a sample of the 4 formats run against the same 1-table, 8-tablet cluster: plain_concise: https://gist.github.com/wdberkeley/674a8b0322c4c0ad6e8eb4ef79664d37 plain_full: https://gist.github.com/wdberkeley/841c92cf4e500782b0dc3a30a8c1cbd8 json_pretty: https://gist.github.com/wdberkeley/04dca6dd5ec7a10ad10c4bd30decab35 json_compact: https://gist.github.com/wdberkeley/283d48ad248a26073a30e013e19443c3 Change-Id: Ib5da0752f8e41c022611253c300450368f6ae969 Reviewed-on: http://gerrit.cloudera.org:8080/10288 Reviewed-by: Alexey Serbin <aser...@cloudera.com> Reviewed-by: Andrew Wong <aw...@cloudera.com> Tested-by: Will Berkeley <wdberke...@gmail.com> Project: http://git-wip-us.apache.org/repos/asf/kudu/repo Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/ee0a75c5 Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/ee0a75c5 Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/ee0a75c5 Branch: refs/heads/master Commit: ee0a75c5a22751241ff25c8f7b9c0a2654fbd767 Parents: 72df429 Author: Will Berkeley <wdberke...@apache.org> Authored: Tue May 1 13:17:57 2018 -0700 Committer: Will Berkeley <wdberke...@gmail.com> Committed: Fri May 11 21:32:01 2018 +0000 ---------------------------------------------------------------------- src/kudu/tools/CMakeLists.txt | 1 + src/kudu/tools/ksck-test.cc | 490 ++++++++++++++++++++++++++--- src/kudu/tools/ksck.cc | 35 ++- src/kudu/tools/ksck_results.cc | 227 ++++++++++++- src/kudu/tools/ksck_results.h | 25 +- src/kudu/tools/tool.proto | 114 +++++++ src/kudu/tools/tool_action_cluster.cc | 2 +- 7 files changed, 819 insertions(+), 75 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/kudu/blob/ee0a75c5/src/kudu/tools/CMakeLists.txt ---------------------------------------------------------------------- diff --git a/src/kudu/tools/CMakeLists.txt b/src/kudu/tools/CMakeLists.txt index 832bb1d..1a7a0fa 100644 --- a/src/kudu/tools/CMakeLists.txt +++ b/src/kudu/tools/CMakeLists.txt @@ -31,6 +31,7 @@ add_library(tool_proto target_link_libraries(tool_proto kudu_common_proto protobuf + tablet_proto wire_protocol_proto) ####################################### http://git-wip-us.apache.org/repos/asf/kudu/blob/ee0a75c5/src/kudu/tools/ksck-test.cc ---------------------------------------------------------------------- diff --git a/src/kudu/tools/ksck-test.cc b/src/kudu/tools/ksck-test.cc index 18d28ef..18db2ce 100644 --- a/src/kudu/tools/ksck-test.cc +++ b/src/kudu/tools/ksck-test.cc @@ -15,8 +15,12 @@ // specific language governing permissions and limitations // under the License. +#include "kudu/tools/ksck.h" + +#include <cstdint> #include <map> #include <memory> +#include <set> #include <sstream> #include <string> #include <type_traits> @@ -28,6 +32,7 @@ #include <gflags/gflags_declare.h> #include <glog/logging.h> #include <gtest/gtest.h> +#include <rapidjson/document.h> #include "kudu/common/schema.h" #include "kudu/consensus/metadata.pb.h" @@ -36,8 +41,8 @@ #include "kudu/gutil/strings/substitute.h" #include "kudu/tablet/metadata.pb.h" #include "kudu/tablet/tablet.pb.h" -#include "kudu/tools/ksck.h" #include "kudu/tools/ksck_results.h" +#include "kudu/util/jsonreader.h" #include "kudu/util/scoped_cleanup.h" #include "kudu/util/status.h" #include "kudu/util/test_macros.h" @@ -49,6 +54,7 @@ DECLARE_string(color); namespace kudu { namespace tools { +using std::ostringstream; using std::shared_ptr; using std::static_pointer_cast; using std::string; @@ -336,6 +342,7 @@ class KsckTest : public KuduTest { pb.set_tablet_id(tablet_id); pb.set_table_name("fake-table"); pb.set_state(is_running ? tablet::RUNNING : tablet::FAILED); + pb.set_tablet_data_state(TabletDataState::TABLET_DATA_UNKNOWN); InsertOrDie(&ts->tablet_status_map_, tablet_id, pb); } @@ -346,6 +353,12 @@ class KsckTest : public KuduTest { return ksck_->RunAndPrintResults(); } + const string KsckResultsToJsonString() { + ostringstream json_stream; + ksck_->results().PrintJsonTo(PrintMode::JSON_COMPACT, json_stream); + return json_stream.str(); + } + shared_ptr<MockKsckCluster> cluster_; shared_ptr<Ksck> ksck_; // This is used as a stack. First the unit test is responsible to create a plan to follow, that @@ -358,6 +371,327 @@ class KsckTest : public KuduTest { std::ostringstream err_stream_; }; +// Helpful macros for checking JSON fields vs. expected values. +// In all cases, the meaning of the parameters are as follows: +// 'reader' is the JsonReader that owns the parsed JSON data. +// 'value' is the rapidjson::Value* containing the field, or, if 'field' +// is nullptr, the field itself. +// 'field' is a const char* naming the field of 'value' to check. +// If it is null, the field value is extracted from 'value' directly. +// 'expected' is the expected value. +#define EXPECT_JSON_STRING_FIELD(reader, value, field, expected) do { \ + string actual; \ + ASSERT_OK((reader).ExtractString((value), (field), &actual)); \ + EXPECT_EQ((expected), actual); \ +} while (0); + +#define EXPECT_JSON_INT_FIELD(reader, value, field, expected) do { \ + int64_t actual; \ + ASSERT_OK((reader).ExtractInt64((value), (field), &actual)); \ + EXPECT_EQ((expected), actual); \ +} while (0); + +#define EXPECT_JSON_BOOL_FIELD(reader, value, field, expected) do { \ + bool actual; \ + ASSERT_OK((reader).ExtractBool((value), (field), &actual)); \ + EXPECT_EQ((expected), actual); \ +} while (0); + +#define EXPECT_JSON_FIELD_NOT_PRESENT(reader, value, field) do { \ + int64_t unused; \ + ASSERT_TRUE((reader).ExtractInt64((value), (field), &unused).IsNotFound()); \ +} while (0); + +// 'array' is a vector<const rapidjson::Value*> into which the array elements +// will be extracted. +// 'exp_size' is the expected size of the vector after extraction. +#define EXTRACT_ARRAY_CHECK_SIZE(reader, value, field, array, exp_size) do { \ + ASSERT_OK((reader).ExtractObjectArray((value), (field), &(array))); \ + ASSERT_EQ(exp_size, (array).size()); \ +} while (0); + +void CheckJsonVsServerHealthSummaries(const JsonReader& r, + const string& key, + const vector<KsckServerHealthSummary>& summaries) { + if (summaries.empty()) { + EXPECT_JSON_FIELD_NOT_PRESENT(r, r.root(), key.c_str()); + return; + } + vector<const rapidjson::Value*> health; + EXTRACT_ARRAY_CHECK_SIZE(r, r.root(), key.c_str(), health, summaries.size()); + for (int i = 0; i < summaries.size(); i++) { + const auto& summary = summaries[i]; + const auto* server = health[i]; + EXPECT_JSON_STRING_FIELD(r, server, "uuid", summary.uuid); + EXPECT_JSON_STRING_FIELD(r, server, "address", summary.address); + EXPECT_JSON_STRING_FIELD(r, server, "health", ServerHealthToString(summary.health)); + EXPECT_JSON_STRING_FIELD(r, server, "status", summary.status.ToString()); + } +} + +const string KsckConsensusConfigTypeToString(KsckConsensusConfigType t) { + switch (t) { + case KsckConsensusConfigType::COMMITTED: + return "COMMITTED"; + case KsckConsensusConfigType::PENDING: + return "PENDING"; + case KsckConsensusConfigType::MASTER: + return "MASTER"; + default: + LOG(FATAL) << "unknown KsckConsensusConfigType"; + } +} + +void CheckJsonVsConsensusState(const JsonReader& r, + const rapidjson::Value* cstate, + const string& ref_id, + const KsckConsensusState& ref_cstate) { + EXPECT_JSON_STRING_FIELD(r, cstate, "type", + KsckConsensusConfigTypeToString(ref_cstate.type)); + if (ref_cstate.leader_uuid) { + EXPECT_JSON_STRING_FIELD(r, cstate, "leader_uuid", ref_cstate.leader_uuid); + } else { + EXPECT_JSON_FIELD_NOT_PRESENT(r, cstate, "leader_uuid"); + } + if (ref_cstate.term) { + EXPECT_JSON_INT_FIELD(r, cstate, "term", ref_cstate.term); + } else { + EXPECT_JSON_FIELD_NOT_PRESENT(r, cstate, "term"); + } + if (ref_cstate.opid_index) { + EXPECT_JSON_INT_FIELD(r, cstate, "opid_index", ref_cstate.opid_index); + } else { + EXPECT_JSON_FIELD_NOT_PRESENT(r, cstate, "opid_index"); + } + // Check voters. + if (ref_cstate.voter_uuids.empty()) { + EXPECT_JSON_FIELD_NOT_PRESENT(r, cstate, "voter_uuids"); + } else { + const vector<string> ref_voter_uuids(ref_cstate.voter_uuids.begin(), + ref_cstate.voter_uuids.end()); + vector<const rapidjson::Value*> voter_uuids; + EXTRACT_ARRAY_CHECK_SIZE(r, cstate, "voter_uuids", + voter_uuids, ref_voter_uuids.size()); + for (int j = 0; j < voter_uuids.size(); j++) { + EXPECT_JSON_STRING_FIELD(r, voter_uuids[j], nullptr, ref_voter_uuids[j]); + } + } + // Check non-voters. + if (ref_cstate.non_voter_uuids.empty()) { + EXPECT_JSON_FIELD_NOT_PRESENT(r, cstate, "non_voter_uuids"); + } else { + const vector<string> ref_non_voter_uuids(ref_cstate.non_voter_uuids.begin(), + ref_cstate.non_voter_uuids.end()); + vector<const rapidjson::Value*> non_voter_uuids; + EXTRACT_ARRAY_CHECK_SIZE(r, cstate, "nonvoter_uuids", + non_voter_uuids, ref_non_voter_uuids.size()); + for (int j = 0; j < non_voter_uuids.size(); j++) { + EXPECT_JSON_STRING_FIELD(r, non_voter_uuids[j], nullptr, ref_non_voter_uuids[j]); + } + } +} + +void CheckJsonVsReplicaSummary(const JsonReader& r, + const rapidjson::Value* replica, + const KsckReplicaSummary& ref_replica) { + EXPECT_JSON_STRING_FIELD(r, replica, "ts_uuid", ref_replica.ts_uuid); + if (ref_replica.ts_address) { + EXPECT_JSON_STRING_FIELD(r, replica, "ts_address", ref_replica.ts_address); + } else { + EXPECT_JSON_FIELD_NOT_PRESENT(r, replica, "ts_address"); + } + EXPECT_JSON_BOOL_FIELD(r, replica, "is_leader", ref_replica.is_leader); + EXPECT_JSON_BOOL_FIELD(r, replica, "is_voter", ref_replica.is_voter); + EXPECT_JSON_BOOL_FIELD(r, replica, "ts_healthy", ref_replica.ts_healthy); + EXPECT_JSON_STRING_FIELD(r, replica, "state", tablet::TabletStatePB_Name(ref_replica.state)); + // The only thing ksck expects from the status_pb is the data state, + // so it's all we check (even though the other info is nice to have). + if (ref_replica.status_pb) { + const rapidjson::Value* status_pb; + ASSERT_OK(r.ExtractObject(replica, "status_pb", &status_pb)); + EXPECT_JSON_STRING_FIELD( + r, + status_pb, + "tablet_data_state", + tablet::TabletDataState_Name(ref_replica.status_pb->tablet_data_state())); + } else { + EXPECT_JSON_FIELD_NOT_PRESENT(r, replica, "status_pb"); + } + if (ref_replica.consensus_state) { + const rapidjson::Value* cstate; + ASSERT_OK(r.ExtractObject(replica, "consensus_state", &cstate)); + CheckJsonVsConsensusState(r, cstate, ref_replica.ts_uuid, *ref_replica.consensus_state); + } else { + EXPECT_JSON_FIELD_NOT_PRESENT(r, replica, "consensus_state"); + } +} + +void CheckJsonVsMasterConsensus(const JsonReader& r, + bool ref_conflict, + const KsckConsensusStateMap& ref_cstates) { + if (ref_cstates.empty()) { + EXPECT_JSON_FIELD_NOT_PRESENT(r, r.root(), "master_consensus_states"); + return; + } + EXPECT_JSON_BOOL_FIELD(r, r.root(), "master_consensus_conflict", ref_conflict); + vector<const rapidjson::Value*> cstates; + EXTRACT_ARRAY_CHECK_SIZE(r, r.root(), "master_consensus_states", + cstates, ref_cstates.size()); + int i = 0; + for (const auto& entry : ref_cstates) { + CheckJsonVsConsensusState(r, cstates[i++], entry.first, entry.second); + } +} + +void CheckJsonVsTableSummaries(const JsonReader& r, + const string& key, + const vector<KsckTableSummary>& ref_tables) { + if (ref_tables.empty()) { + EXPECT_JSON_FIELD_NOT_PRESENT(r, r.root(), key.c_str()); + return; + } + vector<const rapidjson::Value*> tables; + EXTRACT_ARRAY_CHECK_SIZE(r, r.root(), key.c_str(), tables, ref_tables.size()); + for (int i = 0; i < ref_tables.size(); i++) { + const auto& ref_table = ref_tables[i]; + const auto* table = tables[i]; + EXPECT_JSON_STRING_FIELD(r, table, "id", ref_table.id); + EXPECT_JSON_STRING_FIELD(r, table, "name", ref_table.name); + EXPECT_JSON_STRING_FIELD(r, table, + "health", KsckCheckResultToString(ref_table.TableStatus())); + EXPECT_JSON_INT_FIELD(r, table, + "replication_factor", ref_table.replication_factor); + EXPECT_JSON_INT_FIELD(r, table, + "total_tablets", ref_table.TotalTablets()); + EXPECT_JSON_INT_FIELD(r, table, + "healthy_tablets", ref_table.healthy_tablets); + EXPECT_JSON_INT_FIELD(r, table, + "recovering_tablets", ref_table.recovering_tablets); + EXPECT_JSON_INT_FIELD(r, table, + "underreplicated_tablets", ref_table.underreplicated_tablets); + EXPECT_JSON_INT_FIELD(r, table, + "unavailable_tablets", ref_table.unavailable_tablets); + EXPECT_JSON_INT_FIELD(r, table, + "consensus_mismatch_tablets", ref_table.consensus_mismatch_tablets); + } +} + +void CheckJsonVsTabletSummaries(const JsonReader& r, + const string& key, + const vector<KsckTabletSummary>& ref_tablets) { + if (ref_tablets.empty()) { + EXPECT_JSON_FIELD_NOT_PRESENT(r, r.root(), key.c_str()); + return; + } + vector<const rapidjson::Value*> tablets; + EXTRACT_ARRAY_CHECK_SIZE(r, r.root(), key.c_str(), tablets, ref_tablets.size()); + for (int i = 0; i < ref_tablets.size(); i++) { + const auto& ref_tablet = ref_tablets[i]; + const auto& tablet = tablets[i]; + EXPECT_JSON_STRING_FIELD(r, tablet, "id", ref_tablet.id); + EXPECT_JSON_STRING_FIELD(r, tablet, "table_id", ref_tablet.table_id); + EXPECT_JSON_STRING_FIELD(r, tablet, "table_name", ref_tablet.table_name); + EXPECT_JSON_STRING_FIELD(r, tablet, + "health", KsckCheckResultToString(ref_tablet.result)); + EXPECT_JSON_STRING_FIELD(r, tablet, "status", ref_tablet.status); + const rapidjson::Value* master_cstate; + ASSERT_OK(r.ExtractObject(tablet, "master_cstate", &master_cstate)); + CheckJsonVsConsensusState(r, master_cstate, "master", ref_tablet.master_cstate); + if (ref_tablet.replicas.empty()) { + EXPECT_JSON_FIELD_NOT_PRESENT(r, tablet, "replicas"); + continue; + } + vector<const rapidjson::Value*> replicas; + EXTRACT_ARRAY_CHECK_SIZE(r, tablet, + "replicas", replicas, ref_tablet.replicas.size()); + for (int j = 0; j < replicas.size(); j++) { + const auto& ref_replica = ref_tablet.replicas[j]; + const auto* replica = replicas[j]; + CheckJsonVsReplicaSummary(r, replica, ref_replica); + } + } +} + +void CheckJsonVsChecksumResults(const JsonReader& r, + const string& key, + const KsckChecksumResults& ref_checksum_results) { + if (ref_checksum_results.tables.empty()) { + EXPECT_JSON_FIELD_NOT_PRESENT(r, r.root(), key.c_str()); + return; + } + const rapidjson::Value* checksum_results; + ASSERT_OK(r.ExtractObject(r.root(), key.c_str(), &checksum_results)); + if (ref_checksum_results.snapshot_timestamp) { + EXPECT_JSON_INT_FIELD(r, checksum_results, + "snapshot_timestamp", *ref_checksum_results.snapshot_timestamp); + } else { + EXPECT_JSON_FIELD_NOT_PRESENT(r, checksum_results, "snapshot_timestamp"); + } + vector<const rapidjson::Value*> tables; + EXTRACT_ARRAY_CHECK_SIZE(r, checksum_results, "tables", + tables, ref_checksum_results.tables.size()); + int i = 0; + for (const auto& table_entry : ref_checksum_results.tables) { + const auto& ref_table = table_entry.second; + const auto* table = tables[i++]; + EXPECT_JSON_STRING_FIELD(r, table, "name", table_entry.first); + vector<const rapidjson::Value*> tablets; + EXTRACT_ARRAY_CHECK_SIZE(r, table, "tablets", tablets, ref_table.size()); + int j = 0; + for (const auto& tablet_entry : ref_table) { + const auto& ref_tablet = tablet_entry.second; + const auto* tablet = tablets[j++]; + EXPECT_JSON_STRING_FIELD(r, tablet, "tablet_id", tablet_entry.first); + EXPECT_JSON_BOOL_FIELD(r, tablet, "mismatch", ref_tablet.mismatch); + vector<const rapidjson::Value*> checksums; + EXTRACT_ARRAY_CHECK_SIZE(r, tablet, "replica_checksums", + checksums, ref_tablet.replica_checksums.size()); + int k = 0; + for (const auto& replica_entry : ref_tablet.replica_checksums) { + const auto& ref_replica = replica_entry.second; + const auto* replica = checksums[k++]; + EXPECT_JSON_STRING_FIELD(r, replica, "ts_uuid", ref_replica.ts_uuid); + EXPECT_JSON_STRING_FIELD(r, replica, "ts_address", ref_replica.ts_address); + EXPECT_JSON_STRING_FIELD(r, replica, "status", ref_replica.status.ToString()); + // Checksum is a uint64_t and might plausibly be larger than int64_t's max, + // so we're handling it special. + int64_t signed_checksum; + ASSERT_OK(r.ExtractInt64(replica, "checksum", &signed_checksum)); + ASSERT_EQ(ref_replica.checksum, static_cast<uint64_t>(signed_checksum)); + } + } + } +} + +void CheckJsonVsErrors(const JsonReader& r, + const string& key, + const vector<Status>& ref_errors) { + if (ref_errors.empty()) { + EXPECT_JSON_FIELD_NOT_PRESENT(r, r.root(), key.c_str()); + return; + } + vector<const rapidjson::Value*> errors; + EXTRACT_ARRAY_CHECK_SIZE(r, r.root(), "errors", errors, ref_errors.size()); + for (int i = 0; i < ref_errors.size(); i++) { + EXPECT_JSON_STRING_FIELD(r, errors[i], nullptr, ref_errors[i].ToString()); + } +} + +void CheckJsonStringVsKsckResults(const string& json, const KsckResults& results) { + JsonReader r(json); + ASSERT_OK(r.Init()); + + CheckJsonVsServerHealthSummaries(r, "master_summaries", results.master_summaries); + CheckJsonVsMasterConsensus(r, + results.master_consensus_conflict, + results.master_consensus_state_map); + CheckJsonVsServerHealthSummaries(r, "tserver_summaries", results.tserver_summaries); + CheckJsonVsTabletSummaries(r, "tablet_summaries", results.tablet_summaries);; + CheckJsonVsTableSummaries(r, "table_summaries", results.table_summaries);; + CheckJsonVsChecksumResults(r, "checksum_results", results.checksum_results); + CheckJsonVsErrors(r, "errors", results.error_messages); +} + TEST_F(KsckTest, TestServersOk) { ASSERT_OK(RunKsck()); const string err_string = err_stream_.str(); @@ -377,6 +711,8 @@ TEST_F(KsckTest, TestServersOk) { " ts-id-0 | <mock> | HEALTHY\n" " ts-id-1 | <mock> | HEALTHY\n" " ts-id-2 | <mock> | HEALTHY\n"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestMasterUnavailable) { @@ -405,6 +741,8 @@ TEST_F(KsckTest, TestMasterUnavailable) { " A | A* B C | 0 | | Yes\n" " B | [config not available] | | | \n" " C | A* B C | 0 | | Yes\n"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } // A wrong-master-uuid situation can happen if a master that is part of, e.g., @@ -442,6 +780,8 @@ TEST_F(KsckTest, TestWrongMasterUuid) { " A | A* B D | 0 | | Yes\n" " B | A* B D | 0 | | Yes\n" " C | C* | 0 | | Yes\n"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestTwoLeaderMasters) { @@ -463,13 +803,15 @@ TEST_F(KsckTest, TestTwoLeaderMasters) { " A | A* B C | 0 | | Yes\n" " B | A B* C | 0 | | Yes\n" " C | A* B C | 0 | | Yes\n"); -} + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); +} TEST_F(KsckTest, TestLeaderMasterUnavailable) { Status error = Status::NetworkError("Network failure"); cluster_->fetch_info_status_ = error; ASSERT_TRUE(ksck_->CheckClusterRunning().IsNetworkError()); + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestWrongUUIDTabletServer) { @@ -491,6 +833,8 @@ TEST_F(KsckTest, TestWrongUUIDTabletServer) { " ts-id-0 | <mock> | HEALTHY\n" " ts-id-2 | <mock> | HEALTHY\n" " ts-id-1 | <mock> | WRONG_SERVER_UUID\n"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestBadTabletServer) { @@ -538,6 +882,8 @@ TEST_F(KsckTest, TestBadTabletServer) { " ts-id-0 (<mock>): RUNNING [LEADER]\n" " ts-id-1 (<mock>): TS unavailable\n" " ts-id-2 (<mock>): RUNNING\n"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestOneTableCheck) { @@ -546,6 +892,8 @@ TEST_F(KsckTest, TestOneTableCheck) { ASSERT_OK(RunKsck()); ASSERT_STR_CONTAINS(err_stream_.str(), "0/1 replicas remaining (20B from disk, 10 rows summed)"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestOneSmallReplicatedTable) { @@ -554,6 +902,8 @@ TEST_F(KsckTest, TestOneSmallReplicatedTable) { ASSERT_OK(RunKsck()); ASSERT_STR_CONTAINS(err_stream_.str(), "0/9 replicas remaining (180B from disk, 90 rows summed)"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } // Test filtering on a non-matching table pattern. @@ -568,6 +918,8 @@ TEST_F(KsckTest, TestNonMatchingTableFilter) { error_messages[0].ToString()); ASSERT_STR_CONTAINS(err_stream_.str(), "The cluster doesn't have any matching tables"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } // Test filtering with a matching table pattern. @@ -578,6 +930,8 @@ TEST_F(KsckTest, TestMatchingTableFilter) { ASSERT_OK(RunKsck()); ASSERT_STR_CONTAINS(err_stream_.str(), "0/9 replicas remaining (180B from disk, 90 rows summed)"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } // Test filtering on a non-matching tablet id pattern. @@ -593,6 +947,8 @@ TEST_F(KsckTest, TestNonMatchingTabletIdFilter) { error_messages[0].ToString()); ASSERT_STR_CONTAINS(err_stream_.str(), "The cluster doesn't have any matching tablets"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } // Test filtering with a matching tablet ID pattern. @@ -603,6 +959,8 @@ TEST_F(KsckTest, TestMatchingTabletIdFilter) { ASSERT_OK(RunKsck()); ASSERT_STR_CONTAINS(err_stream_.str(), "0/3 replicas remaining (60B from disk, 30 rows summed)"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestOneSmallReplicatedTableWithConsensusState) { @@ -611,11 +969,13 @@ TEST_F(KsckTest, TestOneSmallReplicatedTableWithConsensusState) { ASSERT_STR_CONTAINS(err_stream_.str(), ExpectedKsckTableSummary("test", /*replication_factor=*/ 3, - /*healthy_tables=*/ 3, - /*recovering_tables=*/ 0, - /*underreplicated_tables=*/ 0, - /*consensus_mismatch_tables=*/ 0, - /*unavailable_tables=*/ 0)); + /*healthy_tablets=*/ 3, + /*recovering_tablets=*/ 0, + /*underreplicated_tablets=*/ 0, + /*consensus_mismatch_tablets=*/ 0, + /*unavailable_tablets=*/ 0)); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestConsensusConflictExtraPeer) { @@ -642,11 +1002,13 @@ TEST_F(KsckTest, TestConsensusConflictExtraPeer) { ASSERT_STR_CONTAINS(err_stream_.str(), ExpectedKsckTableSummary("test", /*replication_factor=*/ 3, - /*healthy_tables=*/ 2, - /*recovering_tables=*/ 0, - /*underreplicated_tables=*/ 0, - /*consensus_mismatch_tables=*/ 0, - /*unavailable_tables=*/ 1)); + /*healthy_tablets=*/ 2, + /*recovering_tablets=*/ 0, + /*underreplicated_tablets=*/ 0, + /*consensus_mismatch_tablets=*/ 1, + /*unavailable_tablets=*/ 0)); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestConsensusConflictMissingPeer) { @@ -673,11 +1035,13 @@ TEST_F(KsckTest, TestConsensusConflictMissingPeer) { ASSERT_STR_CONTAINS(err_stream_.str(), ExpectedKsckTableSummary("test", /*replication_factor=*/ 3, - /*healthy_tables=*/ 2, - /*recovering_tables=*/ 0, - /*underreplicated_tables=*/ 0, - /*consensus_mismatch_tables=*/ 0, - /*unavailable_tables=*/ 1)); + /*healthy_tablets=*/ 2, + /*recovering_tablets=*/ 0, + /*underreplicated_tablets=*/ 0, + /*consensus_mismatch_tablets=*/ 1, + /*unavailable_tablets=*/ 0)); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestConsensusConflictDifferentLeader) { @@ -704,11 +1068,13 @@ TEST_F(KsckTest, TestConsensusConflictDifferentLeader) { ASSERT_STR_CONTAINS(err_stream_.str(), ExpectedKsckTableSummary("test", /*replication_factor=*/ 3, - /*healthy_tables=*/ 2, - /*recovering_tables=*/ 0, - /*underreplicated_tables=*/ 0, - /*consensus_mismatch_tables=*/ 0, - /*unavailable_tables=*/ 1)); + /*healthy_tablets=*/ 2, + /*recovering_tablets=*/ 0, + /*underreplicated_tablets=*/ 0, + /*consensus_mismatch_tablets=*/ 1, + /*unavailable_tablets=*/ 0)); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestOneOneTabletBrokenTable) { @@ -724,11 +1090,13 @@ TEST_F(KsckTest, TestOneOneTabletBrokenTable) { ASSERT_STR_CONTAINS(err_stream_.str(), ExpectedKsckTableSummary("test", /*replication_factor=*/ 3, - /*healthy_tables=*/ 0, - /*recovering_tables=*/ 0, - /*underreplicated_tables=*/ 1, - /*consensus_mismatch_tables=*/ 0, - /*unavailable_tables=*/ 0)); + /*healthy_tablets=*/ 0, + /*recovering_tablets=*/ 0, + /*underreplicated_tablets=*/ 1, + /*consensus_mismatch_tablets=*/ 0, + /*unavailable_tablets=*/ 0)); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestMismatchedAssignments) { @@ -751,11 +1119,13 @@ TEST_F(KsckTest, TestMismatchedAssignments) { ASSERT_STR_CONTAINS(err_stream_.str(), ExpectedKsckTableSummary("test", /*replication_factor=*/ 3, - /*healthy_tables=*/ 2, - /*recovering_tables=*/ 0, - /*underreplicated_tables=*/ 1, - /*consensus_mismatch_tables=*/ 0, - /*unavailable_tables=*/ 0)); + /*healthy_tablets=*/ 2, + /*recovering_tablets=*/ 0, + /*underreplicated_tablets=*/ 1, + /*consensus_mismatch_tablets=*/ 0, + /*unavailable_tablets=*/ 0)); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestTabletNotRunning) { @@ -784,11 +1154,13 @@ TEST_F(KsckTest, TestTabletNotRunning) { ASSERT_STR_CONTAINS(err_stream_.str(), ExpectedKsckTableSummary("test", /*replication_factor=*/ 3, - /*healthy_tables=*/ 2, - /*recovering_tables=*/ 0, - /*underreplicated_tables=*/ 0, - /*consensus_mismatch_tables=*/ 0, - /*unavailable_tables=*/ 1)); + /*healthy_tablets=*/ 2, + /*recovering_tablets=*/ 0, + /*underreplicated_tablets=*/ 0, + /*consensus_mismatch_tablets=*/ 0, + /*unavailable_tablets=*/ 1)); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestTabletCopying) { @@ -808,11 +1180,13 @@ TEST_F(KsckTest, TestTabletCopying) { ASSERT_STR_CONTAINS(err_stream_.str(), ExpectedKsckTableSummary("test", /*replication_factor=*/ 3, - /*healthy_tables=*/ 2, - /*recovering_tables=*/ 1, - /*underreplicated_tables=*/ 0, - /*consensus_mismatch_tables=*/ 0, - /*unavailable_tables=*/ 0)); + /*healthy_tablets=*/ 2, + /*recovering_tablets=*/ 1, + /*underreplicated_tablets=*/ 0, + /*consensus_mismatch_tablets=*/ 0, + /*unavailable_tablets=*/ 0)); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } // Test for a bug where we weren't properly handling a tserver not reported by the master. @@ -831,11 +1205,13 @@ TEST_F(KsckTest, TestMasterNotReportingTabletServer) { ASSERT_STR_CONTAINS(err_stream_.str(), ExpectedKsckTableSummary("test", /*replication_factor=*/ 3, - /*healthy_tables=*/ 0, - /*recovering_tables=*/ 0, - /*underreplicated_tables=*/ 3, - /*consensus_mismatch_tables=*/ 0, - /*unavailable_tables=*/ 0)); + /*healthy_tablets=*/ 0, + /*recovering_tablets=*/ 0, + /*underreplicated_tablets=*/ 3, + /*consensus_mismatch_tablets=*/ 0, + /*unavailable_tablets=*/ 0)); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } // KUDU-2113: Test for a bug where we weren't properly handling a tserver not @@ -868,11 +1244,13 @@ TEST_F(KsckTest, TestMasterNotReportingTabletServerWithConsensusConflict) { ASSERT_STR_CONTAINS(err_stream_.str(), ExpectedKsckTableSummary("test", /*replication_factor=*/ 3, - /*healthy_tables=*/ 0, - /*recovering_tables=*/ 0, - /*underreplicated_tables=*/ 3, - /*consensus_mismatch_tables=*/ 0, - /*unavailable_tables=*/ 0)); + /*healthy_tablets=*/ 0, + /*recovering_tablets=*/ 0, + /*underreplicated_tablets=*/ 3, + /*consensus_mismatch_tablets=*/ 0, + /*unavailable_tablets=*/ 0)); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestTableFiltersNoMatch) { @@ -886,6 +1264,8 @@ TEST_F(KsckTest, TestTableFiltersNoMatch) { ASSERT_STR_NOT_CONTAINS(err_stream_.str(), " | Total Count\n" "----------------+-------------\n"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestTableFilters) { @@ -902,6 +1282,8 @@ TEST_F(KsckTest, TestTableFilters) { " Tables | 1\n" " Tablets | 3\n" " Replicas | 9\n"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestTabletFiltersNoMatch) { @@ -915,6 +1297,8 @@ TEST_F(KsckTest, TestTabletFiltersNoMatch) { ASSERT_STR_NOT_CONTAINS(err_stream_.str(), " | Total Count\n" "----------------+-------------\n"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } TEST_F(KsckTest, TestTabletFilters) { @@ -930,6 +1314,8 @@ TEST_F(KsckTest, TestTabletFilters) { " Tables | 1\n" " Tablets | 2\n" " Replicas | 6\n"); + + CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results()); } } // namespace tools http://git-wip-us.apache.org/repos/asf/kudu/blob/ee0a75c5/src/kudu/tools/ksck.cc ---------------------------------------------------------------------- diff --git a/src/kudu/tools/ksck.cc b/src/kudu/tools/ksck.cc index 70a1b64..48a0814 100644 --- a/src/kudu/tools/ksck.cc +++ b/src/kudu/tools/ksck.cc @@ -72,10 +72,17 @@ DEFINE_uint64(checksum_snapshot_timestamp, DEFINE_int32(fetch_replica_info_concurrency, 20, "Number of concurrent tablet servers to fetch replica info from."); +DEFINE_string(ksck_format, "plain_concise", + "Output format for ksck. Available options are 'plain_concise', " + "'plain_full', 'json_pretty', and 'json_compact'.\n" + "'plain_concise' format is plain text, omitting most information " + "about healthy tablets.\n" + "'plain_full' is plain text with all results included.\n" + "'json_pretty' produces pretty-printed json.\n" + "'json_compact' produces json suitable for parsing by other programs.\n" + "'json_pretty' and 'json_compact' differ in format, not content."); DEFINE_bool(consensus, true, "Whether to check the consensus state from each tablet against the master."); -DEFINE_bool(verbose, false, - "Output detailed information even if no inconsistency is found."); using std::cout; using std::endl; @@ -362,7 +369,19 @@ Status Ksck::Run() { } Status Ksck::PrintResults() { - PrintMode mode = FLAGS_verbose ? PrintMode::VERBOSE : PrintMode::DEFAULT; + PrintMode mode; + if (FLAGS_ksck_format == "plain_concise") { + mode = PrintMode::PLAIN_CONCISE; + } else if (FLAGS_ksck_format == "plain_full") { + mode = PrintMode::PLAIN_FULL; + } else if (FLAGS_ksck_format == "json_pretty") { + mode = PrintMode::JSON_PRETTY; + } else if (FLAGS_ksck_format == "json_compact") { + mode = PrintMode::JSON_COMPACT; + } else { + return Status::InvalidArgument("unknown ksck format (--ksck_format)", + FLAGS_ksck_format); + } return results_.PrintTo(mode, *out_); } @@ -780,10 +799,10 @@ KsckCheckResult Ksck::VerifyTablet(const shared_ptr<KsckTablet>& tablet, int copying_replicas_count = 0; int conflicting_states = 0; int num_voters = 0; - vector<KsckReplicaSummary> replica_infos; + vector<KsckReplicaSummary> replicas; for (const shared_ptr<KsckTabletReplica>& replica : tablet->replicas()) { - replica_infos.emplace_back(); - auto* repl_info = &replica_infos.back(); + replicas.emplace_back(); + auto* repl_info = &replicas.back(); repl_info->ts_uuid = replica->ts_uuid(); VLOG(1) << Substitute("A replica of tablet $0 is on live tablet server $1", tablet->id(), replica->ts_uuid()); @@ -824,7 +843,7 @@ KsckCheckResult Ksck::VerifyTablet(const shared_ptr<KsckTablet>& tablet, copying_replicas_count++; } // Compare the master's and peers' consensus configs. - for (const auto& r : replica_infos) { + for (const auto& r : replicas) { if (r.consensus_state && !r.consensus_state->Matches(master_config)) { conflicting_states++; } @@ -883,7 +902,7 @@ KsckCheckResult Ksck::VerifyTablet(const shared_ptr<KsckTablet>& tablet, tablet_summary.result = result; tablet_summary.status = status; tablet_summary.master_cstate = std::move(master_config); - tablet_summary.replica_infos.swap(replica_infos); + tablet_summary.replicas.swap(replicas); results_.tablet_summaries.push_back(std::move(tablet_summary)); return result; } http://git-wip-us.apache.org/repos/asf/kudu/blob/ee0a75c5/src/kudu/tools/ksck_results.cc ---------------------------------------------------------------------- diff --git a/src/kudu/tools/ksck_results.cc b/src/kudu/tools/ksck_results.cc index a38b9b8..29776a6 100644 --- a/src/kudu/tools/ksck_results.cc +++ b/src/kudu/tools/ksck_results.cc @@ -32,11 +32,12 @@ #include "kudu/gutil/map-util.h" #include "kudu/gutil/strings/substitute.h" -#include "kudu/tools/tool_action_common.h" #include "kudu/tools/color.h" +#include "kudu/tools/tool.pb.h" +#include "kudu/tools/tool_action_common.h" +#include "kudu/util/jsonwriter.h" #include "kudu/util/status.h" -using std::cout; using std::endl; using std::left; using std::map; @@ -127,8 +128,9 @@ const char* const KsckCheckResultToString(KsckCheckResult cr) { case KsckCheckResult::RECOVERING: return "RECOVERING"; case KsckCheckResult::UNDER_REPLICATED: - return "UNDER-REPLICATED"; + return "UNDER_REPLICATED"; case KsckCheckResult::CONSENSUS_MISMATCH: + return "CONSENSUS_MISMATCH"; case KsckCheckResult::UNAVAILABLE: return "UNAVAILABLE"; default: @@ -174,12 +176,16 @@ int ServerHealthScore(KsckServerHealth sh) { } Status KsckResults::PrintTo(PrintMode mode, ostream& out) { + if (mode == PrintMode::JSON_PRETTY || mode == PrintMode::JSON_COMPACT) { + return PrintJsonTo(mode, out); + } + // First, report on the masters and master tablet. std::sort(master_summaries.begin(), master_summaries.end(), ServerByHealthComparator); RETURN_NOT_OK(PrintServerHealthSummaries(KsckServerType::MASTER, master_summaries, out)); - if (mode == PrintMode::VERBOSE || master_consensus_conflict) { + if (mode == PrintMode::PLAIN_FULL || master_consensus_conflict) { RETURN_NOT_OK(PrintConsensusMatrix(master_uuids, boost::none, master_consensus_state_map, @@ -325,12 +331,19 @@ Status PrintTabletSummaries(const vector<KsckTabletSummary>& tablet_summaries, out << "The cluster doesn't have any matching tablets" << endl; return Status::OK(); } + bool first = true; for (const auto& tablet_summary : tablet_summaries) { - if (mode != PrintMode::VERBOSE && tablet_summary.result == KsckCheckResult::HEALTHY) { + if (first) { + first = false; + } else { + out << endl; + } + if (mode != PrintMode::PLAIN_FULL && + tablet_summary.result == KsckCheckResult::HEALTHY) { continue; } out << tablet_summary.status << endl; - for (const KsckReplicaSummary& r : tablet_summary.replica_infos) { + for (const KsckReplicaSummary& r : tablet_summary.replicas) { const char* spec_str = r.is_leader ? " [LEADER]" : (!r.is_voter ? " [NONVOTER]" : ""); @@ -364,11 +377,11 @@ Status PrintTabletSummaries(const vector<KsckTabletSummary>& tablet_summaries, auto& master_cstate = tablet_summary.master_cstate; vector<string> ts_uuids; - for (const KsckReplicaSummary& rs : tablet_summary.replica_infos) { + for (const KsckReplicaSummary& rs : tablet_summary.replicas) { ts_uuids.push_back(rs.ts_uuid); } KsckConsensusStateMap consensus_state_map; - for (const KsckReplicaSummary& rs : tablet_summary.replica_infos) { + for (const KsckReplicaSummary& rs : tablet_summary.replicas) { if (rs.consensus_state) { InsertOrDie(&consensus_state_map, rs.ts_uuid, *rs.consensus_state); } @@ -424,7 +437,7 @@ Status PrintTotalCounts(const KsckResults& results, std::ostream& out) { results.tablet_summaries.end(), 0, [](int acc, const KsckTabletSummary& ts) { - return acc + ts.replica_infos.size(); + return acc + ts.replicas.size(); }); DataTable totals({ "", "Total Count" }); totals.AddRow({ "Masters", to_string(results.master_summaries.size()) }); @@ -435,5 +448,201 @@ Status PrintTotalCounts(const KsckResults& results, std::ostream& out) { return totals.PrintTo(out); } +void KsckServerHealthSummaryToPb(const KsckServerHealthSummary& summary, + KsckServerHealthSummaryPB* pb) { + switch (summary.health) { + case KsckServerHealth::HEALTHY: + pb->set_health(KsckServerHealthSummaryPB_ServerHealth_HEALTHY); + break; + case KsckServerHealth::UNAVAILABLE: + pb->set_health(KsckServerHealthSummaryPB_ServerHealth_UNAVAILABLE); + break; + case KsckServerHealth::WRONG_SERVER_UUID: + pb->set_health(KsckServerHealthSummaryPB_ServerHealth_WRONG_SERVER_UUID); + break; + default: + pb->set_health(KsckServerHealthSummaryPB_ServerHealth_UNKNOWN); + } + pb->set_uuid(summary.uuid); + pb->set_address(summary.address); + pb->set_status(summary.status.ToString()); +} + +void KsckConsensusStateToPb(const KsckConsensusState& cstate, + KsckConsensusStatePB* pb) { + switch (cstate.type) { + case KsckConsensusConfigType::MASTER: + pb->set_type(KsckConsensusStatePB_ConfigType_MASTER); + break; + case KsckConsensusConfigType::COMMITTED: + pb->set_type(KsckConsensusStatePB_ConfigType_COMMITTED); + break; + case KsckConsensusConfigType::PENDING: + pb->set_type(KsckConsensusStatePB_ConfigType_PENDING); + break; + default: + pb->set_type(KsckConsensusStatePB_ConfigType_UNKNOWN); + } + if (cstate.term) { + pb->set_term(*cstate.term); + } + if (cstate.opid_index) { + pb->set_opid_index(*cstate.opid_index); + } + if (cstate.leader_uuid) { + pb->set_leader_uuid(*cstate.leader_uuid); + } + for (const auto& voter_uuid : cstate.voter_uuids) { + pb->add_voter_uuids(voter_uuid); + } + for (const auto& non_voter_uuid : cstate.non_voter_uuids) { + pb->add_non_voter_uuids(non_voter_uuid); + } +} + +void KsckReplicaSummaryToPb(const KsckReplicaSummary& replica, + KsckReplicaSummaryPB* pb) { + pb->set_ts_uuid(replica.ts_uuid); + if (replica.ts_address) { + pb->set_ts_address(*replica.ts_address); + } + pb->set_ts_healthy(replica.ts_healthy); + pb->set_is_leader(replica.is_leader); + pb->set_is_voter(replica.is_voter); + pb->set_state(replica.state); + if (replica.status_pb) { + pb->mutable_status_pb()->CopyFrom(*replica.status_pb); + } + if (replica.consensus_state) { + KsckConsensusStateToPb(*replica.consensus_state, pb->mutable_consensus_state()); + } +} + +KsckTabletHealthPB KsckTabletHealthToPB(KsckCheckResult c) { + switch (c) { + case KsckCheckResult::HEALTHY: + return KsckTabletHealthPB::HEALTHY; + case KsckCheckResult::RECOVERING: + return KsckTabletHealthPB::RECOVERING; + case KsckCheckResult::UNDER_REPLICATED: + return KsckTabletHealthPB::UNDER_REPLICATED; + case KsckCheckResult::UNAVAILABLE: + return KsckTabletHealthPB::UNAVAILABLE; + case KsckCheckResult::CONSENSUS_MISMATCH: + return KsckTabletHealthPB::CONSENSUS_MISMATCH; + default: + return KsckTabletHealthPB::UNKNOWN; + } +} + +void KsckTabletSummaryToPb(const KsckTabletSummary& tablet, + KsckTabletSummaryPB* pb) { + pb->set_id(tablet.id); + pb->set_table_id(tablet.table_id); + pb->set_table_name(tablet.table_name); + pb->set_health(KsckTabletHealthToPB(tablet.result)); + pb->set_status(tablet.status); + KsckConsensusStateToPb(tablet.master_cstate, pb->mutable_master_cstate()); + for (const auto& replica : tablet.replicas) { + KsckReplicaSummaryToPb(replica, pb->add_replicas()); + } +} + +void KsckTableSummaryToPb(const KsckTableSummary& table, KsckTableSummaryPB* pb) { + pb->set_id(table.id); + pb->set_name(table.name); + pb->set_health(KsckTabletHealthToPB(table.TableStatus())); + pb->set_replication_factor(table.replication_factor); + pb->set_total_tablets(table.TotalTablets()); + pb->set_healthy_tablets(table.healthy_tablets); + pb->set_recovering_tablets(table.recovering_tablets); + pb->set_underreplicated_tablets(table.underreplicated_tablets); + pb->set_unavailable_tablets(table.unavailable_tablets); + pb->set_consensus_mismatch_tablets(table.consensus_mismatch_tablets); +} + +void KsckReplicaChecksumToPb(const string& ts_uuid, + const KsckReplicaChecksum& replica, + KsckReplicaChecksumPB* pb) { + pb->set_ts_uuid(ts_uuid); + pb->set_ts_address(replica.ts_address); + pb->set_checksum(replica.checksum); + pb->set_status(replica.status.ToString()); +} + +void KsckTabletChecksumToPb(const string& tablet_id, + const KsckTabletChecksum& tablet, + KsckTabletChecksumPB* pb) { + pb->set_tablet_id(tablet_id); + pb->set_mismatch(tablet.mismatch); + for (const auto& entry : tablet.replica_checksums) { + KsckReplicaChecksumToPb(entry.first, entry.second, pb->add_replica_checksums()); + } +} + +void KsckTableChecksumToPb(const string& name, + const KsckTableChecksum& table, + KsckTableChecksumPB* pb) { + pb->set_name(name); + for (const auto& entry : table) { + KsckTabletChecksumToPb(entry.first, entry.second, pb->add_tablets()); + } +} + +void KsckChecksumResultsToPb(const KsckChecksumResults& results, + KsckChecksumResultsPB* pb) { + if (results.snapshot_timestamp) { + pb->set_snapshot_timestamp(*results.snapshot_timestamp); + } + for (const auto& entry : results.tables) { + KsckTableChecksumToPb(entry.first, entry.second, pb->add_tables()); + } +} + +void KsckResults::ToPb(KsckResultsPB* pb) const { + for (const auto& error : error_messages) { + pb->add_errors(error.ToString()); + } + + for (const auto& master_summary : master_summaries) { + KsckServerHealthSummaryToPb(master_summary, pb->add_master_summaries()); + } + for (const auto& tserver_summary : tserver_summaries) { + KsckServerHealthSummaryToPb(tserver_summary, pb->add_tserver_summaries()); + } + + for (const auto& master_uuid : master_uuids) { + pb->add_master_uuids(master_uuid); + } + pb->set_master_consensus_conflict(master_consensus_conflict); + for (const auto& entry : master_consensus_state_map) { + KsckConsensusStateToPb(entry.second, pb->add_master_consensus_states()); + } + + for (const auto& tablet : tablet_summaries) { + KsckTabletSummaryToPb(tablet, pb->add_tablet_summaries()); + } + for (const auto& table : table_summaries) { + KsckTableSummaryToPb(table, pb->add_table_summaries()); + } + + if (!checksum_results.tables.empty()) { + KsckChecksumResultsToPb(checksum_results, pb->mutable_checksum_results()); + } +} + +Status KsckResults::PrintJsonTo(PrintMode mode, ostream& out) const { + CHECK(mode == PrintMode::JSON_PRETTY || mode == PrintMode::JSON_COMPACT); + JsonWriter::Mode jw_mode = JsonWriter::Mode::PRETTY; + if (mode == PrintMode::JSON_COMPACT) { + jw_mode = JsonWriter::Mode::COMPACT; + } + + KsckResultsPB pb; + ToPb(&pb); + out << JsonWriter::ToJson(pb, jw_mode) << endl; + return Status::OK(); +} + } // namespace tools } // namespace kudu http://git-wip-us.apache.org/repos/asf/kudu/blob/ee0a75c5/src/kudu/tools/ksck_results.h ---------------------------------------------------------------------- diff --git a/src/kudu/tools/ksck_results.h b/src/kudu/tools/ksck_results.h index 55b8920..c9fba92 100644 --- a/src/kudu/tools/ksck_results.h +++ b/src/kudu/tools/ksck_results.h @@ -16,10 +16,10 @@ // under the License. #pragma once +#include <cstdint> #include <iosfwd> #include <map> #include <set> -#include <stdint.h> #include <string> #include <utility> #include <vector> @@ -34,6 +34,8 @@ namespace kudu { namespace tools { +class KsckResultsPB; + // The result of health check on a tablet. // Also used to indicate the health of a table, since the health of a table is // the health of its least healthy tablet. @@ -208,7 +210,7 @@ struct KsckTabletSummary { KsckCheckResult result; std::string status; KsckConsensusState master_cstate; - std::vector<KsckReplicaSummary> replica_infos; + std::vector<KsckReplicaSummary> replicas; }; // The result of a checksum on a tablet replica. @@ -237,9 +239,16 @@ struct KsckChecksumResults { }; enum class PrintMode { - DEFAULT, - // Print all results, including for healthy tablets. - VERBOSE, + // Print results in pretty-printed JSON format. + JSON_PRETTY, + // Print results in compact JSON format. Differs from JSON_PRETTY only in + // format, not content. + JSON_COMPACT, + // Print results in plain text, focusing on errors and omitting most + // information about healthy tablets. + PLAIN_CONCISE, + // Print results in plain text. + PLAIN_FULL, }; typedef std::map<std::string, KsckConsensusState> KsckConsensusStateMap; @@ -270,6 +279,12 @@ struct KsckResults { // Print this KsckResults to 'out', according to the PrintMode 'mode'. Status PrintTo(PrintMode mode, std::ostream& out); + + // Print this KsckResults to 'out' in JSON format. + // 'mode' must be PrintMode::JSON_PRETTY or PrintMode::JSON_COMPACT. + Status PrintJsonTo(PrintMode mode, std::ostream& out) const; + + void ToPb(KsckResultsPB* pb) const; }; // Print a formatted health summary to 'out', given a list `summaries` http://git-wip-us.apache.org/repos/asf/kudu/blob/ee0a75c5/src/kudu/tools/tool.proto ---------------------------------------------------------------------- diff --git a/src/kudu/tools/tool.proto b/src/kudu/tools/tool.proto index 2757ee7..e6acb0e 100644 --- a/src/kudu/tools/tool.proto +++ b/src/kudu/tools/tool.proto @@ -21,6 +21,8 @@ option java_package = "org.apache.kudu.tools"; import "kudu/common/common.proto"; import "kudu/common/wire_protocol.proto"; +import "kudu/tablet/metadata.proto"; +import "kudu/tablet/tablet.proto"; // Creates a new ExternalMiniCluster. // @@ -193,3 +195,115 @@ message ControlShellRequestPB { KinitRequestPB kinit = 11; } } + +// Results of ksck, the Kudu system check. +// See the struct analogues of these messages in ksck_results.h. +message KsckResultsPB { + repeated string errors = 1; + + repeated KsckServerHealthSummaryPB master_summaries = 2; + repeated KsckServerHealthSummaryPB tserver_summaries = 3; + + repeated string master_uuids = 4; + optional bool master_consensus_conflict = 5; + repeated KsckConsensusStatePB master_consensus_states = 6; + + repeated KsckTabletSummaryPB tablet_summaries = 7; + repeated KsckTableSummaryPB table_summaries = 8; + + optional KsckChecksumResultsPB checksum_results = 9; +} + +message KsckServerHealthSummaryPB { + enum ServerHealth { + UNKNOWN = 999; + HEALTHY = 0; + UNAVAILABLE = 1; + WRONG_SERVER_UUID = 2; + } + optional string uuid = 1; + optional string address = 2; + optional ServerHealth health = 3; + optional string status = 4; +} + +message KsckConsensusStatePB { + enum ConfigType { + UNKNOWN = 999; + MASTER = 0; + COMMITTED = 1; + PENDING = 2; + } + optional ConfigType type = 1; + optional int64 term = 2; + optional int64 opid_index = 3; + optional string leader_uuid = 4; + repeated string voter_uuids = 5; + repeated string non_voter_uuids = 6; +} + +enum KsckTabletHealthPB { + UNKNOWN = 999; + HEALTHY = 0; + RECOVERING = 1; + UNDER_REPLICATED = 2; + UNAVAILABLE = 3; + CONSENSUS_MISMATCH = 4; +} + +message KsckTabletSummaryPB { + optional string id = 1; + optional string table_id = 2; + optional string table_name = 3; + optional KsckTabletHealthPB health = 4; + optional string status = 5; + optional KsckConsensusStatePB master_cstate = 6; + repeated KsckReplicaSummaryPB replicas = 7; +} + +message KsckReplicaSummaryPB { + optional string ts_uuid = 1; + optional string ts_address = 2; + optional bool ts_healthy = 3; + optional bool is_leader = 4; + optional bool is_voter = 5; + optional tablet.TabletStatePB state = 6; + optional tablet.TabletStatusPB status_pb = 7; + optional KsckConsensusStatePB consensus_state = 8; +} + +message KsckTableSummaryPB { + optional string id = 1; + optional string name = 2; + optional KsckTabletHealthPB health = 3; + optional int32 replication_factor = 4; + optional int32 total_tablets = 5; + optional int32 healthy_tablets = 6; + optional int32 recovering_tablets = 7; + optional int32 underreplicated_tablets = 8; + optional int32 unavailable_tablets = 9; + optional int32 consensus_mismatch_tablets = 10; +} + +message KsckChecksumResultsPB { + optional fixed64 snapshot_timestamp = 1; + repeated KsckTableChecksumPB tables = 2; +} + +message KsckTableChecksumPB { + optional string name = 1; + repeated KsckTabletChecksumPB tablets = 2; +} + +message KsckTabletChecksumPB { + optional string tablet_id = 1; + optional bool mismatch = 2; + repeated KsckReplicaChecksumPB replica_checksums = 3; +} + +message KsckReplicaChecksumPB { + optional string ts_address = 1; + optional string ts_uuid = 2; + optional string status = 3; + optional fixed64 checksum = 4; +} http://git-wip-us.apache.org/repos/asf/kudu/blob/ee0a75c5/src/kudu/tools/tool_action_cluster.cc ---------------------------------------------------------------------- diff --git a/src/kudu/tools/tool_action_cluster.cc b/src/kudu/tools/tool_action_cluster.cc index 662ad64..e225254 100644 --- a/src/kudu/tools/tool_action_cluster.cc +++ b/src/kudu/tools/tool_action_cluster.cc @@ -101,9 +101,9 @@ unique_ptr<Mode> BuildClusterMode() { .AddOptionalParameter("checksum_timeout_sec") .AddOptionalParameter("color") .AddOptionalParameter("consensus") + .AddOptionalParameter("ksck_format") .AddOptionalParameter("tables") .AddOptionalParameter("tablets") - .AddOptionalParameter("verbose") .Build(); return ModeBuilder("cluster")