Fix PrepareOptions for Customizable Classes (#8468)

Summary:
Added the Customizable::ConfigureNewObject method.  The method will configure the object if options are found and invoke PrepareOptions if the flag is set properly.

Added tests to test that PrepareOptions is properly called and to test if PrepareOptions fails.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/8468

Reviewed By: zhichao-cao

Differential Revision: D29494703

Pulled By: mrambacher

fbshipit-source-id: d5767dee5d7a98620ac66190262101cd0aa9d2b7
This commit is contained in:
mrambacher 2021-06-30 14:08:19 -07:00 committed by Facebook GitHub Bot
parent a0cbb69421
commit 41c4b665f4
5 changed files with 192 additions and 77 deletions

View File

@ -162,6 +162,17 @@ class Customizable : public Configurable {
const std::string& opt_value, std::string* id,
std::unordered_map<std::string, std::string>* options);
// Helper method to configure a new object with the supplied options.
// If the object is not null and invoke_prepare_options=true, the object
// will be configured and prepared.
// Returns success if the object is properly configured and (optionally)
// prepared Returns InvalidArgument if the object is nullptr and there are
// options in the map Returns the result of the ConfigureFromMap or
// PrepareOptions
static Status ConfigureNewObject(
const ConfigOptions& config_options, Customizable* object,
const std::unordered_map<std::string, std::string>& options);
// Returns the inner class when a Customizable implements a has-a (wrapped)
// relationship. Derived classes that implement a has-a must override this
// method in order to get CheckedCast to function properly.

View File

@ -71,12 +71,11 @@ static Status NewSharedObject(
} else {
status = Status::NotSupported("Cannot reset object ");
}
if (!status.ok() || opt_map.empty()) {
if (!status.ok()) {
return status;
} else if (result->get() != nullptr) {
return result->get()->ConfigureFromMap(config_options, opt_map);
} else {
return Status::InvalidArgument("Cannot configure null object ", id);
return Customizable::ConfigureNewObject(config_options, result->get(),
opt_map);
}
}
@ -126,12 +125,9 @@ static Status LoadSharedObject(const ConfigOptions& config_options,
} else {
return NewSharedObject(config_options, id, opt_map, result);
}
} else if (opt_map.empty()) {
return status;
} else if (result->get() != nullptr) {
return result->get()->ConfigureFromMap(config_options, opt_map);
} else {
return Status::InvalidArgument("Cannot configure null object ");
return Customizable::ConfigureNewObject(config_options, result->get(),
opt_map);
}
}
@ -166,12 +162,11 @@ static Status NewUniqueObject(
return Status::OK();
}
}
if (!status.ok() || opt_map.empty()) {
if (!status.ok()) {
return status;
} else if (result->get() != nullptr) {
return result->get()->ConfigureFromMap(config_options, opt_map);
} else {
return Status::InvalidArgument("Cannot configure null object ");
return Customizable::ConfigureNewObject(config_options, result->get(),
opt_map);
}
}
@ -205,12 +200,9 @@ static Status LoadUniqueObject(const ConfigOptions& config_options,
} else {
return NewUniqueObject(config_options, id, opt_map, result);
}
} else if (opt_map.empty()) {
return status;
} else if (result->get() != nullptr) {
return result->get()->ConfigureFromMap(config_options, opt_map);
} else {
return Status::InvalidArgument("Cannot configure null object ");
return Customizable::ConfigureNewObject(config_options, result->get(),
opt_map);
}
}
@ -244,12 +236,10 @@ static Status NewStaticObject(
return Status::OK();
}
}
if (!status.ok() || opt_map.empty()) {
if (!status.ok()) {
return status;
} else if (*result != nullptr) {
return (*result)->ConfigureFromMap(config_options, opt_map);
} else {
return Status::InvalidArgument("Cannot configure null object ");
return Customizable::ConfigureNewObject(config_options, *result, opt_map);
}
}
@ -282,12 +272,8 @@ static Status LoadStaticObject(const ConfigOptions& config_options,
} else {
return NewStaticObject(config_options, id, opt_map, result);
}
} else if (opt_map.empty()) {
return status;
} else if (*result != nullptr) {
return (*result)->ConfigureFromMap(config_options, opt_map);
} else {
return Status::InvalidArgument("Cannot configure null object ");
return Customizable::ConfigureNewObject(config_options, *result, opt_map);
}
}
} // namespace ROCKSDB_NAMESPACE

View File

@ -39,30 +39,33 @@ void Configurable::RegisterOptions(
Status Configurable::PrepareOptions(const ConfigOptions& opts) {
Status status = Status::OK();
if (opts.invoke_prepare_options) {
#ifndef ROCKSDB_LITE
for (auto opt_iter : options_) {
for (auto map_iter : *(opt_iter.type_map)) {
auto& opt_info = map_iter.second;
if (!opt_info.IsDeprecated() && !opt_info.IsAlias() &&
opt_info.IsConfigurable()) {
if (!opt_info.IsEnabled(OptionTypeFlags::kDontPrepare)) {
Configurable* config =
opt_info.AsRawPointer<Configurable>(opt_iter.opt_ptr);
if (config != nullptr) {
status = config->PrepareOptions(opts);
if (!status.ok()) {
return status;
for (auto opt_iter : options_) {
for (auto map_iter : *(opt_iter.type_map)) {
auto& opt_info = map_iter.second;
if (!opt_info.IsDeprecated() && !opt_info.IsAlias() &&
opt_info.IsConfigurable()) {
if (!opt_info.IsEnabled(OptionTypeFlags::kDontPrepare)) {
Configurable* config =
opt_info.AsRawPointer<Configurable>(opt_iter.opt_ptr);
if (config != nullptr) {
status = config->PrepareOptions(opts);
if (!status.ok()) {
return status;
}
} else if (!opt_info.CanBeNull()) {
status = Status::NotFound("Missing configurable object",
map_iter.first);
}
}
}
}
}
}
#else
(void)opts;
#endif // ROCKSDB_LITE
if (status.ok()) {
prepared_ = true;
if (status.ok()) {
prepared_ = true;
}
}
return status;
}
@ -158,17 +161,26 @@ Status Configurable::ConfigureOptions(
const std::unordered_map<std::string, std::string>& opts_map,
std::unordered_map<std::string, std::string>* unused) {
std::string curr_opts;
#ifndef ROCKSDB_LITE
if (!config_options.ignore_unknown_options) {
// If we are not ignoring unused, get the defaults in case we need to reset
Status s;
if (!opts_map.empty()) {
// There are options in the map.
// Save the current configuration in curr_opts and then configure the
// options, but do not prepare them now. We will do all the prepare when
// the configuration is complete.
ConfigOptions copy = config_options;
copy.depth = ConfigOptions::kDepthDetailed;
copy.delimiter = "; ";
GetOptionString(copy, &curr_opts).PermitUncheckedError();
}
copy.invoke_prepare_options = false;
#ifndef ROCKSDB_LITE
if (!config_options.ignore_unknown_options) {
// If we are not ignoring unused, get the defaults in case we need to
// reset
copy.depth = ConfigOptions::kDepthDetailed;
copy.delimiter = "; ";
GetOptionString(copy, &curr_opts).PermitUncheckedError();
}
#endif // ROCKSDB_LITE
Status s = ConfigurableHelper::ConfigureOptions(config_options, *this,
opts_map, unused);
s = ConfigurableHelper::ConfigureOptions(copy, *this, opts_map, unused);
}
if (config_options.invoke_prepare_options && s.ok()) {
s = PrepareOptions(config_options);
}
@ -177,6 +189,7 @@ Status Configurable::ConfigureOptions(
ConfigOptions reset = config_options;
reset.ignore_unknown_options = true;
reset.invoke_prepare_options = true;
reset.ignore_unsupported_options = true;
// There are some options to reset from this current error
ConfigureFromString(reset, curr_opts).PermitUncheckedError();
}

View File

@ -104,4 +104,16 @@ Status Customizable::GetOptionsMap(
return ConfigurableHelper::GetOptionsMap(value, "", id, props);
}
}
Status Customizable::ConfigureNewObject(
const ConfigOptions& config_options, Customizable* object,
const std::unordered_map<std::string, std::string>& opt_map) {
Status status;
if (object != nullptr) {
status = object->ConfigureFromMap(config_options, opt_map);
} else if (!opt_map.empty()) {
status = Status::InvalidArgument("Cannot configure null object ");
}
return status;
}
} // namespace ROCKSDB_NAMESPACE

View File

@ -217,8 +217,8 @@ const FactoryFunc<TestCustomizable>& s_func =
#endif // ROCKSDB_LITE
struct SimpleOptions {
static const char* kName() { return "simple"; }
bool b = true;
bool is_mutable = true;
std::unique_ptr<TestCustomizable> cu;
std::shared_ptr<TestCustomizable> cs;
TestCustomizable* cp = nullptr;
@ -246,28 +246,18 @@ class SimpleConfigurable : public Configurable {
SimpleOptions simple_;
public:
SimpleConfigurable() {
RegisterOptions("simple", &simple_, &simple_option_info);
}
SimpleConfigurable() { RegisterOptions(&simple_, &simple_option_info); }
explicit SimpleConfigurable(
const std::unordered_map<std::string, OptionTypeInfo>* map) {
RegisterOptions("simple", &simple_, map);
RegisterOptions(&simple_, map);
}
bool IsPrepared() const override {
if (simple_.is_mutable) {
return false;
} else {
return Configurable::IsPrepared();
}
}
private:
};
class CustomizableTest : public testing::Test {
public:
CustomizableTest() { config_options_.invoke_prepare_options = false; }
ConfigOptions config_options_;
};
@ -285,7 +275,7 @@ TEST_F(CustomizableTest, CreateByNameTest) {
return guard->get();
});
std::unique_ptr<Configurable> configurable(new SimpleConfigurable());
SimpleOptions* simple = configurable->GetOptions<SimpleOptions>("simple");
SimpleOptions* simple = configurable->GetOptions<SimpleOptions>();
ASSERT_NE(simple, nullptr);
ASSERT_OK(
configurable->ConfigureFromString(config_options_, "unique={id=TEST_1}"));
@ -313,7 +303,7 @@ TEST_F(CustomizableTest, SimpleConfigureTest) {
};
std::unique_ptr<Configurable> configurable(new SimpleConfigurable());
ASSERT_OK(configurable->ConfigureFromMap(config_options_, opt_map));
SimpleOptions* simple = configurable->GetOptions<SimpleOptions>("simple");
SimpleOptions* simple = configurable->GetOptions<SimpleOptions>();
ASSERT_NE(simple, nullptr);
ASSERT_NE(simple->cu, nullptr);
ASSERT_EQ(simple->cu->GetId(), "A");
@ -349,7 +339,7 @@ TEST_F(CustomizableTest, ConfigureFromPropsTest) {
};
std::unique_ptr<Configurable> configurable(new SimpleConfigurable());
ASSERT_OK(configurable->ConfigureFromMap(config_options_, opt_map));
SimpleOptions* simple = configurable->GetOptions<SimpleOptions>("simple");
SimpleOptions* simple = configurable->GetOptions<SimpleOptions>();
ASSERT_NE(simple, nullptr);
ASSERT_NE(simple->cu, nullptr);
ASSERT_EQ(simple->cu->GetId(), "A");
@ -372,7 +362,7 @@ TEST_F(CustomizableTest, ConfigureFromShortTest) {
};
std::unique_ptr<Configurable> configurable(new SimpleConfigurable());
ASSERT_OK(configurable->ConfigureFromMap(config_options_, opt_map));
SimpleOptions* simple = configurable->GetOptions<SimpleOptions>("simple");
SimpleOptions* simple = configurable->GetOptions<SimpleOptions>();
ASSERT_NE(simple, nullptr);
ASSERT_NE(simple->cu, nullptr);
ASSERT_EQ(simple->cu->GetId(), "A");
@ -385,13 +375,12 @@ TEST_F(CustomizableTest, AreEquivalentOptionsTest) {
};
std::string mismatch;
ConfigOptions config_options = config_options_;
config_options.invoke_prepare_options = false;
std::unique_ptr<Configurable> c1(new SimpleConfigurable());
std::unique_ptr<Configurable> c2(new SimpleConfigurable());
ASSERT_OK(c1->ConfigureFromMap(config_options, opt_map));
ASSERT_OK(c2->ConfigureFromMap(config_options, opt_map));
ASSERT_TRUE(c1->AreEquivalent(config_options, c2.get(), &mismatch));
SimpleOptions* simple = c1->GetOptions<SimpleOptions>("simple");
SimpleOptions* simple = c1->GetOptions<SimpleOptions>();
ASSERT_TRUE(
simple->cu->AreEquivalent(config_options, simple->cs.get(), &mismatch));
ASSERT_OK(simple->cu->ConfigureOption(config_options, "int", "2"));
@ -462,7 +451,7 @@ TEST_F(CustomizableTest, UniqueIdTest) {
std::unique_ptr<Configurable> base(new SimpleConfigurable());
ASSERT_OK(base->ConfigureFromString(config_options_,
"unique={id=A_1;int=1;bool=true}"));
SimpleOptions* simple = base->GetOptions<SimpleOptions>("simple");
SimpleOptions* simple = base->GetOptions<SimpleOptions>();
ASSERT_NE(simple, nullptr);
ASSERT_NE(simple->cu, nullptr);
ASSERT_EQ(simple->cu->GetId(), std::string("A_1"));
@ -497,6 +486,110 @@ TEST_F(CustomizableTest, IsInstanceOfTest) {
ASSERT_EQ(tc->CheckedCast<ACustomizable>(), nullptr);
}
TEST_F(CustomizableTest, PrepareOptionsTest) {
static std::unordered_map<std::string, OptionTypeInfo> p_option_info = {
#ifndef ROCKSDB_LITE
{"can_prepare",
{0, OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
#endif // ROCKSDB_LITE
};
class PrepareCustomizable : public TestCustomizable {
public:
bool can_prepare_ = true;
PrepareCustomizable() : TestCustomizable("P") {
RegisterOptions("Prepare", &can_prepare_, &p_option_info);
}
Status PrepareOptions(const ConfigOptions& opts) override {
if (!can_prepare_) {
return Status::InvalidArgument("Cannot Prepare");
} else {
return TestCustomizable::PrepareOptions(opts);
}
}
};
ObjectLibrary::Default()->Register<TestCustomizable>(
"P",
[](const std::string& /*name*/, std::unique_ptr<TestCustomizable>* guard,
std::string* /* msg */) {
guard->reset(new PrepareCustomizable());
return guard->get();
});
std::unique_ptr<Configurable> base(new SimpleConfigurable());
ConfigOptions prepared(config_options_);
prepared.invoke_prepare_options = true;
ASSERT_FALSE(base->IsPrepared());
ASSERT_OK(base->ConfigureFromString(
prepared, "unique=A_1; shared={id=B;string=s}; pointer.id=S"));
SimpleOptions* simple = base->GetOptions<SimpleOptions>();
ASSERT_NE(simple, nullptr);
ASSERT_NE(simple->cu, nullptr);
ASSERT_NE(simple->cs, nullptr);
ASSERT_NE(simple->cp, nullptr);
ASSERT_TRUE(base->IsPrepared());
ASSERT_TRUE(simple->cu->IsPrepared());
ASSERT_TRUE(simple->cs->IsPrepared());
ASSERT_TRUE(simple->cp->IsPrepared());
delete simple->cp;
base.reset(new SimpleConfigurable());
ASSERT_OK(base->ConfigureFromString(
config_options_, "unique=A_1; shared={id=B;string=s}; pointer.id=S"));
simple = base->GetOptions<SimpleOptions>();
ASSERT_NE(simple, nullptr);
ASSERT_NE(simple->cu, nullptr);
ASSERT_NE(simple->cs, nullptr);
ASSERT_NE(simple->cp, nullptr);
ASSERT_FALSE(base->IsPrepared());
ASSERT_FALSE(simple->cu->IsPrepared());
ASSERT_FALSE(simple->cs->IsPrepared());
ASSERT_FALSE(simple->cp->IsPrepared());
ASSERT_OK(base->PrepareOptions(config_options_));
ASSERT_FALSE(base->IsPrepared());
ASSERT_FALSE(simple->cu->IsPrepared());
ASSERT_FALSE(simple->cs->IsPrepared());
ASSERT_FALSE(simple->cp->IsPrepared());
ASSERT_OK(base->PrepareOptions(prepared));
ASSERT_TRUE(base->IsPrepared());
ASSERT_TRUE(simple->cu->IsPrepared());
ASSERT_TRUE(simple->cs->IsPrepared());
ASSERT_TRUE(simple->cp->IsPrepared());
delete simple->cp;
base.reset(new SimpleConfigurable());
ASSERT_NOK(
base->ConfigureFromString(prepared, "unique={id=P; can_prepare=false}"));
simple = base->GetOptions<SimpleOptions>();
ASSERT_NE(simple, nullptr);
ASSERT_NE(simple->cu, nullptr);
ASSERT_FALSE(simple->cu->IsPrepared());
ASSERT_OK(
base->ConfigureFromString(prepared, "unique={id=P; can_prepare=true}"));
ASSERT_TRUE(simple->cu->IsPrepared());
ASSERT_OK(base->ConfigureFromString(config_options_,
"unique={id=P; can_prepare=true}"));
ASSERT_NE(simple->cu, nullptr);
ASSERT_FALSE(simple->cu->IsPrepared());
ASSERT_OK(simple->cu->PrepareOptions(prepared));
ASSERT_TRUE(simple->cu->IsPrepared());
ASSERT_OK(base->ConfigureFromString(config_options_,
"unique={id=P; can_prepare=false}"));
ASSERT_NE(simple->cu, nullptr);
ASSERT_FALSE(simple->cu->IsPrepared());
ASSERT_NOK(simple->cu->PrepareOptions(prepared));
ASSERT_FALSE(simple->cu->IsPrepared());
}
static std::unordered_map<std::string, OptionTypeInfo> inner_option_info = {
#ifndef ROCKSDB_LITE
{"inner",
@ -600,7 +693,7 @@ TEST_F(CustomizableTest, NewCustomizableTest) {
A_count = 0;
ASSERT_OK(base->ConfigureFromString(config_options_,
"unique={id=A_1;int=1;bool=true}"));
SimpleOptions* simple = base->GetOptions<SimpleOptions>("simple");
SimpleOptions* simple = base->GetOptions<SimpleOptions>();
ASSERT_NE(simple, nullptr);
ASSERT_NE(simple->cu, nullptr);
ASSERT_EQ(A_count, 1); // Created one A
@ -698,7 +791,7 @@ TEST_F(CustomizableTest, MutableOptionsTest) {
static std::unordered_map<std::string, OptionTypeInfo> immutable_option_info =
{{"immutable",
OptionTypeInfo::AsCustomSharedPtr<TestCustomizable>(
0, OptionVerificationType::kNormal, OptionTypeFlags::kNone)}};
0, OptionVerificationType::kNormal, OptionTypeFlags::kAllowNull)}};
class MutableCustomizable : public Customizable {
private: