blob: 2a7e2a30918e424c89d0b916fec74af7662344f1 [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 <gen_cpp/olap_file.pb.h>
#include <gen_cpp/segment_v2.pb.h>
#include <gflags/gflags.h>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <set>
#include <sstream>
#include <string>
#include "common/status.h"
#include "gutil/strings/numbers.h"
#include "io/fs/file_reader.h"
#include "io/fs/local_file_system.h"
#include "json2pb/pb_to_json.h"
#include "olap/data_dir.h"
#include "olap/options.h"
#include "olap/rowset/segment_v2/column_reader.h"
#include "olap/storage_engine.h"
#include "olap/tablet_meta.h"
#include "olap/tablet_meta_manager.h"
#include "util/coding.h"
#include "util/crc32c.h"
using doris::DataDir;
using doris::StorageEngine;
using doris::Status;
using doris::TabletMeta;
using doris::TabletMetaManager;
using doris::Slice;
using doris::segment_v2::SegmentFooterPB;
using doris::io::FileReaderSPtr;
DEFINE_string(root_path, "", "storage root path");
DEFINE_string(operation, "get_meta",
"valid operation: get_meta, flag, load_meta, delete_meta, show_meta");
DEFINE_int64(tablet_id, 0, "tablet_id for tablet meta");
DEFINE_int32(schema_hash, 0, "schema_hash for tablet meta");
DEFINE_string(json_meta_path, "", "absolute json meta file path");
DEFINE_string(pb_meta_path, "", "pb meta file path");
DEFINE_string(tablet_file, "", "file to save a set of tablets");
DEFINE_string(file, "", "segment file path");
std::string get_usage(const std::string& progname) {
std::stringstream ss;
ss << progname << " is the Doris BE Meta tool.\n";
ss << "Stop BE first before use this tool.\n";
ss << "Usage:\n";
ss << "./meta_tool --operation=get_meta --root_path=/path/to/storage/path "
"--tablet_id=tabletid --schema_hash=schemahash\n";
ss << "./meta_tool --operation=load_meta --root_path=/path/to/storage/path "
"--json_meta_path=path\n";
ss << "./meta_tool --operation=delete_meta "
"--root_path=/path/to/storage/path --tablet_id=tabletid "
"--schema_hash=schemahash\n";
ss << "./meta_tool --operation=delete_meta --tablet_file=file_path\n";
ss << "./meta_tool --operation=show_meta --pb_meta_path=path\n";
ss << "./meta_tool --operation=show_segment_footer --file=/path/to/segment/file\n";
return ss.str();
}
void show_meta() {
TabletMeta tablet_meta;
Status s = tablet_meta.create_from_file(FLAGS_pb_meta_path);
if (!s.ok()) {
std::cout << "load pb meta file:" << FLAGS_pb_meta_path << " failed"
<< ", status:" << s << std::endl;
return;
}
std::string json_meta;
json2pb::Pb2JsonOptions json_options;
json_options.pretty_json = true;
doris::TabletMetaPB tablet_meta_pb;
tablet_meta.to_meta_pb(&tablet_meta_pb);
json2pb::ProtoMessageToJson(tablet_meta_pb, &json_meta, json_options);
std::cout << json_meta << std::endl;
}
void get_meta(DataDir* data_dir) {
std::string value;
Status s =
TabletMetaManager::get_json_meta(data_dir, FLAGS_tablet_id, FLAGS_schema_hash, &value);
if (s.is<doris::ErrorCode::META_KEY_NOT_FOUND>()) {
std::cout << "no tablet meta for tablet_id:" << FLAGS_tablet_id
<< ", schema_hash:" << FLAGS_schema_hash << std::endl;
return;
}
std::cout << value << std::endl;
}
void load_meta(DataDir* data_dir) {
// load json tablet meta into meta
Status s = TabletMetaManager::load_json_meta(data_dir, FLAGS_json_meta_path);
if (!s.ok()) {
std::cout << "load meta failed, status:" << s << std::endl;
return;
}
std::cout << "load meta successfully" << std::endl;
}
void delete_meta(DataDir* data_dir) {
Status s = TabletMetaManager::remove(data_dir, FLAGS_tablet_id, FLAGS_schema_hash);
if (!s.ok()) {
std::cout << "delete tablet meta failed for tablet_id:" << FLAGS_tablet_id
<< ", schema_hash:" << FLAGS_schema_hash << ", status:" << s << std::endl;
return;
}
std::cout << "delete meta successfully" << std::endl;
}
Status init_data_dir(StorageEngine& engine, const std::string& dir, std::unique_ptr<DataDir>* ret) {
std::string root_path;
RETURN_IF_ERROR(doris::io::global_local_filesystem()->canonicalize(dir, &root_path));
doris::StorePath path;
auto res = parse_root_path(root_path, &path);
if (!res.ok()) {
std::cout << "parse root path failed:" << root_path << std::endl;
return Status::InternalError("parse root path failed");
}
auto p = std::make_unique<DataDir>(engine, path.path, path.capacity_bytes, path.storage_medium);
if (p == nullptr) {
std::cout << "new data dir failed" << std::endl;
return Status::InternalError("new data dir failed");
}
res = p->init();
if (!res.ok()) {
std::cout << "data_dir load failed" << std::endl;
return Status::InternalError("data_dir load failed");
}
p.swap(*ret);
return Status::OK();
}
void batch_delete_meta(const std::string& tablet_file) {
// each line in tablet file indicate a tablet to delete, format is:
// data_dir,tablet_id,schema_hash
// eg:
// /data1/palo.HDD,100010,11212389324
// /data2/palo.HDD,100010,23049230234
std::ifstream infile(tablet_file);
std::string line = "";
int err_num = 0;
int delete_num = 0;
int total_num = 0;
StorageEngine engine(doris::EngineOptions {});
std::unordered_map<std::string, std::unique_ptr<DataDir>> dir_map;
while (std::getline(infile, line)) {
total_num++;
std::vector<string> v = absl::StrSplit(line, ",");
if (v.size() != 3) {
std::cout << "invalid line in tablet_file: " << line << std::endl;
err_num++;
continue;
}
// 1. get dir
std::string dir;
Status st = doris::io::global_local_filesystem()->canonicalize(v[0], &dir);
if (!st.ok()) {
std::cout << "invalid root dir in tablet_file: " << line << std::endl;
err_num++;
continue;
}
if (dir_map.find(dir) == dir_map.end()) {
// new data dir, init it
std::unique_ptr<DataDir> data_dir_p;
Status st = init_data_dir(engine, dir, &data_dir_p);
if (!st.ok()) {
std::cout << "invalid root path:" << FLAGS_root_path
<< ", error: " << st.to_string() << std::endl;
err_num++;
continue;
}
dir_map[dir] = std::move(data_dir_p);
std::cout << "get a new data dir: " << dir << std::endl;
}
DataDir* data_dir = dir_map[dir].get();
if (data_dir == nullptr) {
std::cout << "failed to get data dir: " << line << std::endl;
err_num++;
continue;
}
// 2. get tablet id/schema_hash
int64_t tablet_id;
if (!safe_strto64(v[1].c_str(), &tablet_id)) {
std::cout << "invalid tablet id: " << line << std::endl;
err_num++;
continue;
}
int64_t schema_hash;
if (!safe_strto64(v[2].c_str(), &schema_hash)) {
std::cout << "invalid schema hash: " << line << std::endl;
err_num++;
continue;
}
Status s = TabletMetaManager::remove(data_dir, tablet_id, schema_hash);
if (!s.ok()) {
std::cout << "delete tablet meta failed for tablet_id:" << tablet_id
<< ", schema_hash:" << schema_hash << ", status:" << s << std::endl;
err_num++;
continue;
}
delete_num++;
}
std::cout << "total: " << total_num << ", delete: " << delete_num << ", error: " << err_num
<< std::endl;
return;
}
Status get_segment_footer(doris::io::FileReader* file_reader, SegmentFooterPB* footer) {
// Footer := SegmentFooterPB, FooterPBSize(4), FooterPBChecksum(4), MagicNumber(4)
std::string file_name = file_reader->path();
uint64_t file_size = file_reader->size();
if (file_size < 12) {
return Status::Corruption("Bad segment file {}: file size {} < 12", file_name, file_size);
}
size_t bytes_read = 0;
uint8_t fixed_buf[12];
Slice slice(fixed_buf, 12);
RETURN_IF_ERROR(file_reader->read_at(file_size - 12, slice, &bytes_read));
// validate magic number
const char* k_segment_magic = "D0R1";
const uint32_t k_segment_magic_length = 4;
if (memcmp(fixed_buf + 8, k_segment_magic, k_segment_magic_length) != 0) {
return Status::Corruption("Bad segment file {}: magic number not match", file_name);
}
// read footer PB
uint32_t footer_length = doris::decode_fixed32_le(fixed_buf);
if (file_size < 12 + footer_length) {
return Status::Corruption("Bad segment file {}: file size {} < {}", file_name, file_size,
12 + footer_length);
}
std::string footer_buf;
footer_buf.resize(footer_length);
Slice slice2(footer_buf);
RETURN_IF_ERROR(file_reader->read_at(file_size - 12 - footer_length, slice2, &bytes_read));
// validate footer PB's checksum
uint32_t expect_checksum = doris::decode_fixed32_le(fixed_buf + 4);
uint32_t actual_checksum = doris::crc32c::Value(footer_buf.data(), footer_buf.size());
if (actual_checksum != expect_checksum) {
return Status::Corruption(
"Bad segment file {}: footer checksum not match, actual={} vs expect={}", file_name,
actual_checksum, expect_checksum);
}
// deserialize footer PB
if (!footer->ParseFromString(footer_buf)) {
return Status::Corruption("Bad segment file {}: failed to parse SegmentFooterPB",
file_name);
}
return Status::OK();
}
void show_segment_footer(const std::string& file_name) {
doris::io::FileReaderSPtr file_reader;
Status status = doris::io::global_local_filesystem()->open_file(file_name, &file_reader);
if (!status.ok()) {
std::cout << "open file failed: " << status << std::endl;
return;
}
SegmentFooterPB footer;
status = get_segment_footer(file_reader.get(), &footer);
if (!status.ok()) {
std::cout << "get footer failed: " << status.to_string() << std::endl;
return;
}
std::string json_footer;
json2pb::Pb2JsonOptions json_options;
json_options.pretty_json = true;
bool ret = json2pb::ProtoMessageToJson(footer, &json_footer, json_options);
if (!ret) {
std::cout << "Convert PB to json failed" << std::endl;
return;
}
std::cout << json_footer << std::endl;
return;
}
int main(int argc, char** argv) {
SCOPED_INIT_THREAD_CONTEXT();
std::string usage = get_usage(argv[0]);
gflags::SetUsageMessage(usage);
google::ParseCommandLineFlags(&argc, &argv, true);
if (FLAGS_operation == "show_meta") {
show_meta();
} else if (FLAGS_operation == "batch_delete_meta") {
std::string tablet_file;
Status st =
doris::io::global_local_filesystem()->canonicalize(FLAGS_tablet_file, &tablet_file);
if (!st.ok()) {
std::cout << "invalid tablet file: " << FLAGS_tablet_file
<< ", error: " << st.to_string() << std::endl;
return -1;
}
batch_delete_meta(tablet_file);
} else if (FLAGS_operation == "show_segment_footer") {
if (FLAGS_file == "") {
std::cout << "no file flag for show dict" << std::endl;
return -1;
}
show_segment_footer(FLAGS_file);
} else {
// operations that need root path should be written here
std::set<std::string> valid_operations = {"get_meta", "load_meta", "delete_meta"};
if (valid_operations.find(FLAGS_operation) == valid_operations.end()) {
std::cout << "invalid operation:" << FLAGS_operation << std::endl;
return -1;
}
StorageEngine engine(doris::EngineOptions {});
std::unique_ptr<DataDir> data_dir;
Status st = init_data_dir(engine, FLAGS_root_path, &data_dir);
if (!st.ok()) {
std::cout << "invalid root path:" << FLAGS_root_path << ", error: " << st.to_string()
<< std::endl;
return -1;
}
if (FLAGS_operation == "get_meta") {
get_meta(data_dir.get());
} else if (FLAGS_operation == "load_meta") {
load_meta(data_dir.get());
} else if (FLAGS_operation == "delete_meta") {
delete_meta(data_dir.get());
} else {
std::cout << "invalid operation: " << FLAGS_operation << "\n" << usage << std::endl;
return -1;
}
}
gflags::ShutDownCommandLineFlags();
return 0;
}