mirror of
https://github.com/facebook/rocksdb.git
synced 2024-12-04 20:02:50 +00:00
ee258619be
Summary: This PR fixes a few cases where RocksDB was not retrying checksum failure/corruption of file reads with the `verify_and_reconstruct_read` IO option. After fixing these cases, we can almost always successfully open the DB and execute reads even if we see transient corruptions, provided the `FileSystem` supports the `verify_and_reconstruct_read` option. The specific cases fixed in this PR are - 1. CURRENT file 2. IDENTITY file 3. OPTIONS file 4. SST footer Pull Request resolved: https://github.com/facebook/rocksdb/pull/13122 Test Plan: Unit test in `db_io_failure_test.cc` that injects corruption at various stages of DB open and reads Reviewed By: jaykorean Differential Revision: D65617982 Pulled By: anand1976 fbshipit-source-id: 4324b88cc7eee5501ab5df20ef7a95bb12ed3ea7
768 lines
27 KiB
C++
768 lines
27 KiB
C++
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under both the GPLv2 (found in the
|
|
// COPYING file in the root directory) and Apache 2.0 License
|
|
// (found in the LICENSE.Apache file in the root directory).
|
|
|
|
|
|
#include "options/options_parser.h"
|
|
|
|
#include <cmath>
|
|
#include <map>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "file/file_util.h"
|
|
#include "file/line_file_reader.h"
|
|
#include "file/writable_file_writer.h"
|
|
#include "options/cf_options.h"
|
|
#include "options/db_options.h"
|
|
#include "options/options_helper.h"
|
|
#include "port/port.h"
|
|
#include "rocksdb/convenience.h"
|
|
#include "rocksdb/db.h"
|
|
#include "rocksdb/utilities/options_type.h"
|
|
#include "test_util/sync_point.h"
|
|
#include "util/cast_util.h"
|
|
#include "util/string_util.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
static const std::string option_file_header =
|
|
"# This is a RocksDB option file.\n"
|
|
"#\n"
|
|
"# For detailed file format spec, please refer to the example file\n"
|
|
"# in examples/rocksdb_option_file_example.ini\n"
|
|
"#\n"
|
|
"\n";
|
|
|
|
Status PersistRocksDBOptions(const WriteOptions& write_options,
|
|
const DBOptions& db_opt,
|
|
const std::vector<std::string>& cf_names,
|
|
const std::vector<ColumnFamilyOptions>& cf_opts,
|
|
const std::string& file_name, FileSystem* fs) {
|
|
ConfigOptions
|
|
config_options; // Use default for escaped(true) and check (exact)
|
|
config_options.delimiter = "\n ";
|
|
// Do not invoke PrepareOptions when we are doing validation.
|
|
config_options.invoke_prepare_options = false;
|
|
// If a readahead size was set in the input options, use it
|
|
if (db_opt.log_readahead_size > 0) {
|
|
config_options.file_readahead_size = db_opt.log_readahead_size;
|
|
}
|
|
return PersistRocksDBOptions(write_options, config_options, db_opt, cf_names,
|
|
cf_opts, file_name, fs);
|
|
}
|
|
|
|
Status PersistRocksDBOptions(const WriteOptions& write_options,
|
|
const ConfigOptions& config_options_in,
|
|
const DBOptions& db_opt,
|
|
const std::vector<std::string>& cf_names,
|
|
const std::vector<ColumnFamilyOptions>& cf_opts,
|
|
const std::string& file_name, FileSystem* fs) {
|
|
ConfigOptions config_options = config_options_in;
|
|
config_options.delimiter = "\n "; // Override the default to nl
|
|
|
|
TEST_SYNC_POINT("PersistRocksDBOptions:start");
|
|
if (cf_names.size() != cf_opts.size()) {
|
|
return Status::InvalidArgument(
|
|
"cf_names.size() and cf_opts.size() must be the same");
|
|
}
|
|
std::unique_ptr<FSWritableFile> wf;
|
|
|
|
FileOptions file_options;
|
|
file_options.temperature = db_opt.metadata_write_temperature;
|
|
Status s = fs->NewWritableFile(file_name, file_options, &wf, nullptr);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
std::unique_ptr<WritableFileWriter> writable;
|
|
writable.reset(new WritableFileWriter(std::move(wf), file_name, EnvOptions(),
|
|
nullptr /* statistics */));
|
|
TEST_SYNC_POINT("PersistRocksDBOptions:create");
|
|
|
|
std::string options_file_content;
|
|
|
|
IOOptions opts;
|
|
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
|
if (s.ok()) {
|
|
s = writable->Append(opts, option_file_header + "[" +
|
|
opt_section_titles[kOptionSectionVersion] +
|
|
"]\n"
|
|
" rocksdb_version=" +
|
|
std::to_string(ROCKSDB_MAJOR) + "." +
|
|
std::to_string(ROCKSDB_MINOR) + "." +
|
|
std::to_string(ROCKSDB_PATCH) + "\n");
|
|
}
|
|
if (s.ok()) {
|
|
s = writable->Append(
|
|
opts,
|
|
" options_file_version=" + std::to_string(ROCKSDB_OPTION_FILE_MAJOR) +
|
|
"." + std::to_string(ROCKSDB_OPTION_FILE_MINOR) + "\n");
|
|
}
|
|
if (s.ok()) {
|
|
s = writable->Append(
|
|
opts, "\n[" + opt_section_titles[kOptionSectionDBOptions] + "]\n ");
|
|
}
|
|
|
|
if (s.ok()) {
|
|
s = GetStringFromDBOptions(config_options, db_opt, &options_file_content);
|
|
}
|
|
if (s.ok()) {
|
|
s = writable->Append(opts, options_file_content + "\n");
|
|
}
|
|
|
|
for (size_t i = 0; s.ok() && i < cf_opts.size(); ++i) {
|
|
// CFOptions section
|
|
s = writable->Append(
|
|
opts, "\n[" + opt_section_titles[kOptionSectionCFOptions] + " \"" +
|
|
EscapeOptionString(cf_names[i]) + "\"]\n ");
|
|
if (s.ok()) {
|
|
s = GetStringFromColumnFamilyOptions(config_options, cf_opts[i],
|
|
&options_file_content);
|
|
}
|
|
if (s.ok()) {
|
|
s = writable->Append(opts, options_file_content + "\n");
|
|
}
|
|
// TableOptions section
|
|
auto* tf = cf_opts[i].table_factory.get();
|
|
if (tf != nullptr) {
|
|
if (s.ok()) {
|
|
s = writable->Append(
|
|
opts, "[" + opt_section_titles[kOptionSectionTableOptions] +
|
|
tf->Name() + " \"" + EscapeOptionString(cf_names[i]) +
|
|
"\"]\n ");
|
|
}
|
|
if (s.ok()) {
|
|
options_file_content.clear();
|
|
s = tf->GetOptionString(config_options, &options_file_content);
|
|
}
|
|
if (s.ok()) {
|
|
s = writable->Append(opts, options_file_content + "\n");
|
|
}
|
|
}
|
|
}
|
|
if (s.ok()) {
|
|
s = writable->Sync(opts, true /* use_fsync */);
|
|
}
|
|
if (s.ok()) {
|
|
s = writable->Close(opts);
|
|
}
|
|
TEST_SYNC_POINT("PersistRocksDBOptions:written");
|
|
if (s.ok()) {
|
|
return RocksDBOptionsParser::VerifyRocksDBOptionsFromFile(
|
|
config_options, db_opt, cf_names, cf_opts, file_name, fs);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
RocksDBOptionsParser::RocksDBOptionsParser() { Reset(); }
|
|
|
|
void RocksDBOptionsParser::Reset() {
|
|
db_opt_ = DBOptions();
|
|
db_opt_map_.clear();
|
|
cf_names_.clear();
|
|
cf_opts_.clear();
|
|
cf_opt_maps_.clear();
|
|
has_version_section_ = false;
|
|
has_db_options_ = false;
|
|
has_default_cf_options_ = false;
|
|
for (int i = 0; i < 3; ++i) {
|
|
db_version[i] = 0;
|
|
opt_file_version[i] = 0;
|
|
}
|
|
}
|
|
|
|
bool RocksDBOptionsParser::IsSection(const std::string& line) {
|
|
if (line.size() < 2) {
|
|
return false;
|
|
}
|
|
if (line[0] != '[' || line[line.size() - 1] != ']') {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Status RocksDBOptionsParser::ParseSection(OptionSection* section,
|
|
std::string* title,
|
|
std::string* argument,
|
|
const std::string& line,
|
|
const int line_num) {
|
|
*section = kOptionSectionUnknown;
|
|
// A section is of the form [<SectionName> "<SectionArg>"], where
|
|
// "<SectionArg>" is optional.
|
|
size_t arg_start_pos = line.find('\"');
|
|
size_t arg_end_pos = line.rfind('\"');
|
|
// The following if-then check tries to identify whether the input
|
|
// section has the optional section argument.
|
|
if (arg_start_pos != std::string::npos && arg_start_pos != arg_end_pos) {
|
|
*title = TrimAndRemoveComment(line.substr(1, arg_start_pos - 1), true);
|
|
*argument = UnescapeOptionString(
|
|
line.substr(arg_start_pos + 1, arg_end_pos - arg_start_pos - 1));
|
|
} else {
|
|
*title = TrimAndRemoveComment(line.substr(1, line.size() - 2), true);
|
|
*argument = "";
|
|
}
|
|
for (int i = 0; i < kOptionSectionUnknown; ++i) {
|
|
if (title->find(opt_section_titles[i]) == 0) {
|
|
if (i == kOptionSectionVersion || i == kOptionSectionDBOptions ||
|
|
i == kOptionSectionCFOptions) {
|
|
if (title->size() == opt_section_titles[i].size()) {
|
|
// if true, then it indicats equal
|
|
*section = static_cast<OptionSection>(i);
|
|
return CheckSection(*section, *argument, line_num);
|
|
}
|
|
} else if (i == kOptionSectionTableOptions) {
|
|
// This type of sections has a sufffix at the end of the
|
|
// section title
|
|
if (title->size() > opt_section_titles[i].size()) {
|
|
*section = static_cast<OptionSection>(i);
|
|
return CheckSection(*section, *argument, line_num);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Status::InvalidArgument(std::string("Unknown section ") + line);
|
|
}
|
|
|
|
Status RocksDBOptionsParser::InvalidArgument(const int line_num,
|
|
const std::string& message) {
|
|
return Status::InvalidArgument(
|
|
"[RocksDBOptionsParser Error] ",
|
|
message + " (at line " + std::to_string(line_num) + ")");
|
|
}
|
|
|
|
Status RocksDBOptionsParser::ParseStatement(std::string* name,
|
|
std::string* value,
|
|
const std::string& line,
|
|
const int line_num) {
|
|
size_t eq_pos = line.find('=');
|
|
if (eq_pos == std::string::npos) {
|
|
return InvalidArgument(line_num, "A valid statement must have a '='.");
|
|
}
|
|
|
|
*name = TrimAndRemoveComment(line.substr(0, eq_pos), true);
|
|
*value =
|
|
TrimAndRemoveComment(line.substr(eq_pos + 1, line.size() - eq_pos - 1));
|
|
if (name->empty()) {
|
|
return InvalidArgument(line_num,
|
|
"A valid statement must have a variable name.");
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
Status RocksDBOptionsParser::Parse(const std::string& file_name, FileSystem* fs,
|
|
bool ignore_unknown_options,
|
|
size_t file_readahead_size) {
|
|
ConfigOptions
|
|
config_options; // Use default for escaped(true) and check (exact)
|
|
config_options.ignore_unknown_options = ignore_unknown_options;
|
|
if (file_readahead_size > 0) {
|
|
config_options.file_readahead_size = file_readahead_size;
|
|
}
|
|
return Parse(config_options, file_name, fs);
|
|
}
|
|
|
|
Status RocksDBOptionsParser::Parse(const ConfigOptions& config_options_in,
|
|
const std::string& file_name,
|
|
FileSystem* fs) {
|
|
Reset();
|
|
ConfigOptions config_options = config_options_in;
|
|
|
|
Status s;
|
|
bool retry = false;
|
|
do {
|
|
std::unique_ptr<FSSequentialFile> seq_file;
|
|
s = fs->NewSequentialFile(file_name, FileOptions(), &seq_file, nullptr);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
LineFileReader lf_reader(
|
|
std::move(seq_file), file_name, config_options.file_readahead_size,
|
|
nullptr, std::vector<std::shared_ptr<EventListener>>{}, nullptr, retry);
|
|
|
|
OptionSection section = kOptionSectionUnknown;
|
|
std::string title;
|
|
std::string argument;
|
|
std::unordered_map<std::string, std::string> opt_map;
|
|
std::string line;
|
|
// we only support single-lined statement.
|
|
while (
|
|
lf_reader.ReadLine(&line, Env::IO_TOTAL /* rate_limiter_priority */)) {
|
|
int line_num = static_cast<int>(lf_reader.GetLineNumber());
|
|
line = TrimAndRemoveComment(line);
|
|
if (line.empty()) {
|
|
continue;
|
|
}
|
|
if (IsSection(line)) {
|
|
s = EndSection(config_options, section, title, argument, opt_map);
|
|
opt_map.clear();
|
|
if (!s.ok()) {
|
|
break;
|
|
}
|
|
|
|
// If the option file is not generated by a higher version, unknown
|
|
// option should only mean corruption.
|
|
if (config_options.ignore_unknown_options &&
|
|
section == kOptionSectionVersion) {
|
|
using VTuple = std::tuple<int, int, int>;
|
|
if (VTuple(db_version[0], db_version[1], db_version[2]) <=
|
|
VTuple(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH)) {
|
|
config_options.ignore_unknown_options = false;
|
|
}
|
|
}
|
|
|
|
s = ParseSection(§ion, &title, &argument, line, line_num);
|
|
if (!s.ok()) {
|
|
break;
|
|
}
|
|
} else {
|
|
std::string name;
|
|
std::string value;
|
|
s = ParseStatement(&name, &value, line, line_num);
|
|
if (!s.ok()) {
|
|
break;
|
|
}
|
|
opt_map.insert({name, value});
|
|
}
|
|
}
|
|
if (s.ok()) {
|
|
s = lf_reader.GetStatus();
|
|
}
|
|
if (s.ok()) {
|
|
s = EndSection(config_options, section, title, argument, opt_map);
|
|
opt_map.clear();
|
|
}
|
|
if (s.ok()) {
|
|
s = ValidityCheck();
|
|
}
|
|
if (!s.ok()) {
|
|
if ((s.IsCorruption() || s.IsInvalidArgument()) && !retry &&
|
|
CheckFSFeatureSupport(fs,
|
|
FSSupportedOps::kVerifyAndReconstructRead)) {
|
|
retry = true;
|
|
Reset();
|
|
} else {
|
|
return s;
|
|
}
|
|
} else {
|
|
return s;
|
|
}
|
|
} while (retry);
|
|
|
|
return s;
|
|
}
|
|
|
|
Status RocksDBOptionsParser::CheckSection(const OptionSection section,
|
|
const std::string& section_arg,
|
|
const int line_num) {
|
|
if (section == kOptionSectionDBOptions) {
|
|
if (has_db_options_) {
|
|
return InvalidArgument(
|
|
line_num,
|
|
"More than one DBOption section found in the option config file");
|
|
}
|
|
has_db_options_ = true;
|
|
} else if (section == kOptionSectionCFOptions) {
|
|
bool is_default_cf = (section_arg == kDefaultColumnFamilyName);
|
|
if (cf_opts_.size() == 0 && !is_default_cf) {
|
|
return InvalidArgument(
|
|
line_num,
|
|
"Default column family must be the first CFOptions section "
|
|
"in the option config file");
|
|
} else if (cf_opts_.size() != 0 && is_default_cf) {
|
|
return InvalidArgument(
|
|
line_num,
|
|
"Default column family must be the first CFOptions section "
|
|
"in the optio/n config file");
|
|
} else if (GetCFOptions(section_arg) != nullptr) {
|
|
return InvalidArgument(
|
|
line_num,
|
|
"Two identical column families found in option config file");
|
|
}
|
|
has_default_cf_options_ |= is_default_cf;
|
|
} else if (section == kOptionSectionTableOptions) {
|
|
if (GetCFOptions(section_arg) == nullptr) {
|
|
return InvalidArgument(
|
|
line_num, std::string(
|
|
"Does not find a matched column family name in "
|
|
"TableOptions section. Column Family Name:") +
|
|
section_arg);
|
|
}
|
|
} else if (section == kOptionSectionVersion) {
|
|
if (has_version_section_) {
|
|
return InvalidArgument(
|
|
line_num,
|
|
"More than one Version section found in the option config file.");
|
|
}
|
|
has_version_section_ = true;
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
Status RocksDBOptionsParser::ParseVersionNumber(const std::string& ver_name,
|
|
const std::string& ver_string,
|
|
const int max_count,
|
|
int* version) {
|
|
int version_index = 0;
|
|
int current_number = 0;
|
|
int current_digit_count = 0;
|
|
bool has_dot = false;
|
|
for (int i = 0; i < max_count; ++i) {
|
|
version[i] = 0;
|
|
}
|
|
constexpr int kBufferSize = 200;
|
|
char buffer[kBufferSize];
|
|
for (size_t i = 0; i < ver_string.size(); ++i) {
|
|
if (ver_string[i] == '.') {
|
|
if (version_index >= max_count - 1) {
|
|
snprintf(buffer, sizeof(buffer) - 1,
|
|
"A valid %s can only contains at most %d dots.",
|
|
ver_name.c_str(), max_count - 1);
|
|
return Status::InvalidArgument(buffer);
|
|
}
|
|
if (current_digit_count == 0) {
|
|
snprintf(buffer, sizeof(buffer) - 1,
|
|
"A valid %s must have at least one digit before each dot.",
|
|
ver_name.c_str());
|
|
return Status::InvalidArgument(buffer);
|
|
}
|
|
version[version_index++] = current_number;
|
|
current_number = 0;
|
|
current_digit_count = 0;
|
|
has_dot = true;
|
|
} else if (isdigit(ver_string[i])) {
|
|
current_number = current_number * 10 + (ver_string[i] - '0');
|
|
current_digit_count++;
|
|
} else {
|
|
snprintf(buffer, sizeof(buffer) - 1,
|
|
"A valid %s can only contains dots and numbers.",
|
|
ver_name.c_str());
|
|
return Status::InvalidArgument(buffer);
|
|
}
|
|
}
|
|
version[version_index] = current_number;
|
|
if (has_dot && current_digit_count == 0) {
|
|
snprintf(buffer, sizeof(buffer) - 1,
|
|
"A valid %s must have at least one digit after each dot.",
|
|
ver_name.c_str());
|
|
return Status::InvalidArgument(buffer);
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
Status RocksDBOptionsParser::EndSection(
|
|
const ConfigOptions& config_options, const OptionSection section,
|
|
const std::string& section_title, const std::string& section_arg,
|
|
const std::unordered_map<std::string, std::string>& opt_map) {
|
|
Status s;
|
|
if (section == kOptionSectionDBOptions) {
|
|
s = GetDBOptionsFromMap(config_options, DBOptions(), opt_map, &db_opt_);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
db_opt_map_ = opt_map;
|
|
} else if (section == kOptionSectionCFOptions) {
|
|
// This condition should be ensured earlier in ParseSection
|
|
// so we make an assertion here.
|
|
assert(GetCFOptions(section_arg) == nullptr);
|
|
cf_names_.emplace_back(section_arg);
|
|
cf_opts_.emplace_back();
|
|
s = GetColumnFamilyOptionsFromMap(config_options, ColumnFamilyOptions(),
|
|
opt_map, &cf_opts_.back());
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
// keep the parsed string.
|
|
cf_opt_maps_.emplace_back(opt_map);
|
|
} else if (section == kOptionSectionTableOptions) {
|
|
assert(GetCFOptions(section_arg) != nullptr);
|
|
auto* cf_opt = GetCFOptionsImpl(section_arg);
|
|
if (cf_opt == nullptr) {
|
|
return Status::InvalidArgument(
|
|
"The specified column family must be defined before the "
|
|
"TableOptions section:",
|
|
section_arg);
|
|
}
|
|
// Ignore error as table factory deserialization is optional
|
|
cf_opt->table_factory.reset();
|
|
s = TableFactory::CreateFromString(
|
|
config_options,
|
|
section_title.substr(
|
|
opt_section_titles[kOptionSectionTableOptions].size()),
|
|
&(cf_opt->table_factory));
|
|
if (s.ok() && cf_opt->table_factory != nullptr) {
|
|
s = cf_opt->table_factory->ConfigureFromMap(config_options, opt_map);
|
|
// Translate any errors (NotFound, NotSupported, to InvalidArgument
|
|
if (s.ok() || s.IsInvalidArgument()) {
|
|
return s;
|
|
} else {
|
|
return Status::InvalidArgument(s.getState());
|
|
}
|
|
} else {
|
|
// Return OK for not supported table factories as TableFactory
|
|
// Deserialization is optional.
|
|
cf_opt->table_factory.reset();
|
|
return Status::OK();
|
|
}
|
|
} else if (section == kOptionSectionVersion) {
|
|
for (const auto& pair : opt_map) {
|
|
if (pair.first == "rocksdb_version") {
|
|
s = ParseVersionNumber(pair.first, pair.second, 3, db_version);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
} else if (pair.first == "options_file_version") {
|
|
s = ParseVersionNumber(pair.first, pair.second, 2, opt_file_version);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
if (opt_file_version[0] < 1) {
|
|
return Status::InvalidArgument(
|
|
"A valid options_file_version must be at least 1.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
Status RocksDBOptionsParser::ValidityCheck() {
|
|
if (!has_db_options_) {
|
|
return Status::Corruption(
|
|
"A RocksDB Option file must have a single DBOptions section");
|
|
}
|
|
if (!has_default_cf_options_) {
|
|
return Status::Corruption(
|
|
"A RocksDB Option file must have a single CFOptions:default section");
|
|
}
|
|
|
|
return Status::OK();
|
|
}
|
|
|
|
std::string RocksDBOptionsParser::TrimAndRemoveComment(const std::string& line,
|
|
bool trim_only) {
|
|
size_t start = 0;
|
|
size_t end = line.size();
|
|
|
|
// we only support "#" style comment
|
|
if (!trim_only) {
|
|
size_t search_pos = 0;
|
|
while (search_pos < line.size()) {
|
|
size_t comment_pos = line.find('#', search_pos);
|
|
if (comment_pos == std::string::npos) {
|
|
break;
|
|
}
|
|
if (comment_pos == 0 || line[comment_pos - 1] != '\\') {
|
|
end = comment_pos;
|
|
break;
|
|
}
|
|
search_pos = comment_pos + 1;
|
|
}
|
|
}
|
|
|
|
while (start < end && isspace(line[start]) != 0) {
|
|
++start;
|
|
}
|
|
|
|
// start < end implies end > 0.
|
|
while (start < end && isspace(line[end - 1]) != 0) {
|
|
--end;
|
|
}
|
|
|
|
if (start < end) {
|
|
return line.substr(start, end - start);
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
Status RocksDBOptionsParser::VerifyRocksDBOptionsFromFile(
|
|
const ConfigOptions& config_options_in, const DBOptions& db_opt,
|
|
const std::vector<std::string>& cf_names,
|
|
const std::vector<ColumnFamilyOptions>& cf_opts,
|
|
const std::string& file_name, FileSystem* fs) {
|
|
RocksDBOptionsParser parser;
|
|
ConfigOptions config_options = config_options_in;
|
|
config_options.invoke_prepare_options =
|
|
false; // No need to do a prepare for verify
|
|
if (config_options.sanity_level < ConfigOptions::kSanityLevelExactMatch) {
|
|
// If we are not doing an exact comparison, we should ignore
|
|
// unsupported options, as they may cause the Parse to fail
|
|
// (if the ObjectRegistry is not initialized)
|
|
config_options.ignore_unsupported_options = true;
|
|
}
|
|
Status s = parser.Parse(config_options, file_name, fs);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
// Verify DBOptions
|
|
s = VerifyDBOptions(config_options, db_opt, *parser.db_opt(),
|
|
parser.db_opt_map());
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
// Verify ColumnFamily Name
|
|
if (cf_names.size() != parser.cf_names()->size()) {
|
|
if (config_options.sanity_level >=
|
|
ConfigOptions::kSanityLevelLooselyCompatible) {
|
|
return Status::InvalidArgument(
|
|
"[RocksDBOptionParser Error] The persisted options does not have "
|
|
"the same number of column family names as the db instance.");
|
|
} else if (cf_opts.size() > parser.cf_opts()->size()) {
|
|
return Status::InvalidArgument(
|
|
"[RocksDBOptionsParser Error]",
|
|
"The persisted options file has less number of column family "
|
|
"names than that of the specified one.");
|
|
}
|
|
}
|
|
for (size_t i = 0; i < cf_names.size(); ++i) {
|
|
if (cf_names[i] != parser.cf_names()->at(i)) {
|
|
return Status::InvalidArgument(
|
|
"[RocksDBOptionParser Error] The persisted options and the db"
|
|
"instance does not have the same name for column family ",
|
|
std::to_string(i));
|
|
}
|
|
}
|
|
|
|
// Verify Column Family Options
|
|
if (cf_opts.size() != parser.cf_opts()->size()) {
|
|
if (config_options.sanity_level >=
|
|
ConfigOptions::kSanityLevelLooselyCompatible) {
|
|
return Status::InvalidArgument(
|
|
"[RocksDBOptionsParser Error]",
|
|
"The persisted options does not have the same number of "
|
|
"column families as the db instance.");
|
|
} else if (cf_opts.size() > parser.cf_opts()->size()) {
|
|
return Status::InvalidArgument(
|
|
"[RocksDBOptionsParser Error]",
|
|
"The persisted options file has less number of column families "
|
|
"than that of the specified number.");
|
|
}
|
|
}
|
|
for (size_t i = 0; i < cf_opts.size(); ++i) {
|
|
s = VerifyCFOptions(config_options, cf_opts[i], parser.cf_opts()->at(i),
|
|
&(parser.cf_opt_maps()->at(i)));
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
s = VerifyTableFactory(config_options, cf_opts[i].table_factory.get(),
|
|
parser.cf_opts()->at(i).table_factory.get());
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
}
|
|
|
|
return Status::OK();
|
|
}
|
|
|
|
Status RocksDBOptionsParser::VerifyDBOptions(
|
|
const ConfigOptions& config_options, const DBOptions& base_opt,
|
|
const DBOptions& file_opt,
|
|
const std::unordered_map<std::string, std::string>* opt_map) {
|
|
auto base_config = DBOptionsAsConfigurable(base_opt, opt_map);
|
|
auto file_config = DBOptionsAsConfigurable(file_opt, opt_map);
|
|
std::string mismatch;
|
|
if (!base_config->AreEquivalent(config_options, file_config.get(),
|
|
&mismatch)) {
|
|
const size_t kBufferSize = 2048;
|
|
char buffer[kBufferSize];
|
|
std::string base_value;
|
|
std::string file_value;
|
|
int offset = snprintf(buffer, sizeof(buffer),
|
|
"[RocksDBOptionsParser]: "
|
|
"failed the verification on DBOptions::%s -- ",
|
|
mismatch.c_str());
|
|
Status s = base_config->GetOption(config_options, mismatch, &base_value);
|
|
if (s.ok()) {
|
|
s = file_config->GetOption(config_options, mismatch, &file_value);
|
|
}
|
|
assert(offset >= 0);
|
|
assert(static_cast<size_t>(offset) < sizeof(buffer));
|
|
if (s.ok()) {
|
|
snprintf(buffer + offset, sizeof(buffer) - static_cast<size_t>(offset),
|
|
"-- The specified one is %s while the persisted one is %s.\n",
|
|
base_value.c_str(), file_value.c_str());
|
|
} else {
|
|
snprintf(buffer + offset, sizeof(buffer) - static_cast<size_t>(offset),
|
|
"-- Unable to re-serialize an option: %s.\n",
|
|
s.ToString().c_str());
|
|
}
|
|
return Status::InvalidArgument(Slice(buffer, strlen(buffer)));
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
Status RocksDBOptionsParser::VerifyCFOptions(
|
|
const ConfigOptions& config_options, const ColumnFamilyOptions& base_opt,
|
|
const ColumnFamilyOptions& file_opt,
|
|
const std::unordered_map<std::string, std::string>* opt_map) {
|
|
auto base_config = CFOptionsAsConfigurable(base_opt, opt_map);
|
|
auto file_config = CFOptionsAsConfigurable(file_opt, opt_map);
|
|
std::string mismatch;
|
|
if (!base_config->AreEquivalent(config_options, file_config.get(),
|
|
&mismatch)) {
|
|
std::string base_value;
|
|
std::string file_value;
|
|
// The options do not match
|
|
const size_t kBufferSize = 2048;
|
|
char buffer[kBufferSize];
|
|
Status s = base_config->GetOption(config_options, mismatch, &base_value);
|
|
if (s.ok()) {
|
|
s = file_config->GetOption(config_options, mismatch, &file_value);
|
|
// In file_opt, certain options like MergeOperator may be nullptr due to
|
|
// factor methods not available. So we use opt_map to get
|
|
// option value to use in the error message below.
|
|
if (s.ok() && file_value == kNullptrString && opt_map) {
|
|
auto const& opt_val_str = (opt_map->find(mismatch));
|
|
if (opt_val_str != opt_map->end()) {
|
|
file_value = opt_val_str->second;
|
|
}
|
|
}
|
|
}
|
|
int offset = snprintf(buffer, sizeof(buffer),
|
|
"[RocksDBOptionsParser]: "
|
|
"failed the verification on ColumnFamilyOptions::%s",
|
|
mismatch.c_str());
|
|
assert(offset >= 0);
|
|
assert(static_cast<size_t>(offset) < sizeof(buffer));
|
|
if (s.ok()) {
|
|
snprintf(buffer + offset, sizeof(buffer) - static_cast<size_t>(offset),
|
|
"--- The specified one is %s while the persisted one is %s.\n",
|
|
base_value.c_str(), file_value.c_str());
|
|
} else {
|
|
snprintf(buffer + offset, sizeof(buffer) - static_cast<size_t>(offset),
|
|
"--- Unable to re-serialize an option: %s.\n",
|
|
s.ToString().c_str());
|
|
}
|
|
return Status::InvalidArgument(Slice(buffer, sizeof(buffer)));
|
|
} // For each option
|
|
return Status::OK();
|
|
}
|
|
|
|
Status RocksDBOptionsParser::VerifyTableFactory(
|
|
const ConfigOptions& config_options, const TableFactory* base_tf,
|
|
const TableFactory* file_tf) {
|
|
std::string mismatch;
|
|
if (base_tf && file_tf) {
|
|
if (config_options.sanity_level > ConfigOptions::kSanityLevelNone &&
|
|
std::string(base_tf->Name()) != std::string(file_tf->Name())) {
|
|
return Status::Corruption(
|
|
"[RocksDBOptionsParser]: "
|
|
"failed the verification on TableFactory->Name()");
|
|
} else if (!base_tf->AreEquivalent(config_options, file_tf, &mismatch)) {
|
|
return Status::Corruption(std::string("[RocksDBOptionsParser]:"
|
|
"failed the verification on ") +
|
|
base_tf->Name() + "::",
|
|
mismatch);
|
|
}
|
|
} else {
|
|
// TODO(yhchiang): further support sanity check here
|
|
}
|
|
return Status::OK();
|
|
}
|
|
} // namespace ROCKSDB_NAMESPACE
|