2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-22 09:57:41 +00:00

[#3770] v4 global options work

Fixed fetch of global options added UT tests

/src/hooks/dhcp/mysql/mysql_cb_impl.cc
    MySqlConfigBackendImpl::getOptions() - take client-classes into account
    MySqlConfigBackendImpl::createInputClientClassesBinding() - use ClientClasses::toElement()

/src/hooks/dhcp/mysql/tests/mysql_cb_dhcp4_unittest.cc
    TEST_F(MySqlConfigBackendDHCPv4Test, globalOption4WithClientClassesTest)
    TEST_F(MySqlConfigBackendDHCPv4Test, getAllOptions4WithClientClassesTest)
    - new tests

/src/hooks/dhcp/pgsql/pgsql_cb_impl.cc
    PgSqlConfigBackendImpl::getOptions() - take client-classes into account

/src/hooks/dhcp/pgsql/tests/pgsql_cb_dhcp4_unittest.cc
    TEST_F(PgSqlConfigBackendDHCPv4Test, globalOption4WithClientClassesTest)
    TEST_F(PgSqlConfigBackendDHCPv4Test, getAllOptions4WithClientClassesTest)
    - new tests

/src/lib/dhcpsrv/parsers/option_data_parser.cc
    OptionDataParser::createOption() - use ClientClasses::fromElement()

/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.*
    GenericConfigBackendDHCPv4Test::TearDown() - skip schema destroy
    if env variable KEA_UNIT_TEST_KEEP_SCHEMA is defined

    GenericConfigBackendDHCPv4Test::makeClassTaggedOptions()
    GenericConfigBackendDHCPv4Test::updateClassTaggedOptions()
    GenericConfigBackendDHCPv4Test::globalOption4WithClientClassesTest()
    GenericConfigBackendDHCPv4Test::getAllOptions4WithClientClassesTest()
    - new tests
This commit is contained in:
Thomas Markwalder 2025-07-10 14:11:31 -04:00
parent 16ffb6d6ed
commit 1ea9698f16
7 changed files with 264 additions and 160 deletions

View File

@ -802,7 +802,8 @@ MySqlConfigBackendImpl::getOptions(const int index,
auto existing_it = existing_it_pair.first;
bool found = false;
for ( ; existing_it != existing_it_pair.second; ++existing_it) {
if (existing_it->space_name_ == desc->space_name_) {
if ((existing_it->space_name_ == desc->space_name_) &&
(existing_it->client_classes_ == desc->client_classes_)) {
found = true;
// This option was already fetched. Let's check if we should
// replace it or not.
@ -818,8 +819,9 @@ MySqlConfigBackendImpl::getOptions(const int index,
// belongs to a different server and the inserted option is not
// for all servers.
if (!found ||
(!existing_it->hasServerTag(last_option_server_tag) &&
!last_option_server_tag.amAll())) {
((!existing_it->hasServerTag(last_option_server_tag) &&
!last_option_server_tag.amAll()) ||
(desc->client_classes_ != existing_it->client_classes_))) {
static_cast<void>(local_options.push_back(*desc));
}
}
@ -1130,11 +1132,7 @@ MySqlConfigBackendImpl::createClientClassesForWhereClause(const ClientClassesPtr
db::MySqlBindingPtr
MySqlConfigBackendImpl::createInputClientClassesBinding(const ClientClasses& client_classes) {
// Create JSON list of client classes.
data::ElementPtr client_classes_element = data::Element::createList();
for (auto const& client_class : client_classes) {
client_classes_element->add(data::Element::create(client_class));
}
auto client_classes_element = client_classes.toElement();
return (db::MySqlBinding::createString(client_classes_element->str()));
}

View File

@ -351,6 +351,14 @@ TEST_F(MySqlConfigBackendDHCPv4Test, getModifiedOptions4Test) {
getModifiedOptions4Test();
}
TEST_F(MySqlConfigBackendDHCPv4Test, globalOption4WithClientClassesTest) {
globalOption4WithClientClassesTest();
}
TEST_F(MySqlConfigBackendDHCPv4Test, getAllOptions4WithClientClassesTest) {
getAllOptions4WithClientClassesTest();
}
TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteSubnetOption4Test) {
createUpdateDeleteSubnetOption4Test();
}
@ -403,10 +411,6 @@ TEST_F(MySqlConfigBackendDHCPv4Test, multipleAuditEntriesTest) {
multipleAuditEntriesTest();
}
TEST_F(MySqlConfigBackendDHCPv4Test, globalOption4WithClientClassesTest) {
globalOption4WithClientClassesTest();
}
TEST_F(MySqlConfigBackendDHCPv4Test, sharedNetworkOption4WithClientClassesTest) {
sharedNetworkOption4WithClientClassesTest();
}

View File

@ -732,7 +732,8 @@ PgSqlConfigBackendImpl::getOptions(const int index,
auto existing_it = existing_it_pair.first;
bool found = false;
for ( ; existing_it != existing_it_pair.second; ++existing_it) {
if (existing_it->space_name_ == desc->space_name_) {
if ((existing_it->space_name_ == desc->space_name_) &&
(existing_it->client_classes_ == desc->client_classes_)) {
found = true;
// This option was already fetched. Let's check if we should
// replace it or not.
@ -748,8 +749,9 @@ PgSqlConfigBackendImpl::getOptions(const int index,
// belongs to a different server and the inserted option is not
// for all servers.
if (!found ||
(!existing_it->hasServerTag(last_option_server_tag) &&
!last_option_server_tag.amAll())) {
((!existing_it->hasServerTag(last_option_server_tag) &&
!last_option_server_tag.amAll()) ||
(desc->client_classes_ != existing_it->client_classes_))) {
static_cast<void>(local_options.push_back(*desc));
}
}

View File

@ -349,6 +349,14 @@ TEST_F(PgSqlConfigBackendDHCPv4Test, getModifiedOptions4Test) {
getModifiedOptions4Test();
}
TEST_F(PgSqlConfigBackendDHCPv4Test, globalOption4WithClientClassesTest) {
globalOption4WithClientClassesTest();
}
TEST_F(PgSqlConfigBackendDHCPv4Test, getAllOptions4WithClientClassesTest) {
getAllOptions4WithClientClassesTest();
}
TEST_F(PgSqlConfigBackendDHCPv4Test, createUpdateDeleteSubnetOption4Test) {
createUpdateDeleteSubnetOption4Test();
}
@ -401,10 +409,6 @@ TEST_F(PgSqlConfigBackendDHCPv4Test, multipleAuditEntriesTest) {
multipleAuditEntriesTest();
}
TEST_F(PgSqlConfigBackendDHCPv4Test, globalOption4WithClientClassesTest) {
globalOption4WithClientClassesTest();
}
TEST_F(PgSqlConfigBackendDHCPv4Test, sharedNetworkOption4WithClientClassesTest) {
sharedNetworkOption4WithClientClassesTest();
}

View File

@ -441,15 +441,7 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
desc.setContext(user_context);
}
#if 1
desc.client_classes_.fromElement(client_classes);
#else
if (client_classes) {
for (auto const& class_element : client_classes->listValue()) {
desc.addClientClass(class_element->stringValue());
}
}
#endif
// All went good, so we can set the option space name.
return (make_pair(desc, space_param));

View File

@ -73,7 +73,11 @@ void
GenericConfigBackendDHCPv4Test::TearDown() {
cbptr_.reset();
// If data wipe enabled, delete transient data otherwise destroy the schema.
if (getenv("KEA_UNIT_TEST_KEEP_SCHEMA")) {
std::cout << "KEA_UNIT_TEST_KEEP_SCHEMA set, avoid schema destruction" << std::endl;
} else {
destroySchema();
}
}
db::AuditEntryCollection
@ -3362,9 +3366,13 @@ GenericConfigBackendDHCPv4Test::createUpdateDeleteOption4Test() {
void
GenericConfigBackendDHCPv4Test::globalOptions4WithServerTagsTest() {
OptionDescriptorPtr opt_boot_file_name1 = test_options_[0];
OptionDescriptorPtr opt_boot_file_name2 = test_options_[6];
OptionDescriptorPtr opt_boot_file_name3 = test_options_[7];
// Create test options without any client classes.
OptionDescriptorPtr opt_boot_file_name1(new OptionDescriptor(*test_options_[0]));
opt_boot_file_name1->client_classes_.clear();
OptionDescriptorPtr opt_boot_file_name2(new OptionDescriptor(*test_options_[6]));
opt_boot_file_name2->client_classes_.clear();
OptionDescriptorPtr opt_boot_file_name3(new OptionDescriptor(*test_options_[7]));
opt_boot_file_name3->client_classes_.clear();
ASSERT_THROW(cbptr_->createUpdateOption4(ServerSelector::ONE("server1"),
opt_boot_file_name1),
@ -3636,6 +3644,215 @@ GenericConfigBackendDHCPv4Test::getModifiedOptions4Test() {
}
}
std::list<OptionDescriptorPtr>
GenericConfigBackendDHCPv4Test::makeClassTaggedOptions() {
// Describes an option to create.
struct OptData {
uint16_t code_;
std::string value_;
std::string cclass_;
};
// List of options to create.
std::list<OptData> opts_to_make = {
{ DHO_TCODE, "T100", "cc-one" },
{ DHO_PCODE, "P100", "cc-one" },
{ DHO_PCODE, "P300", "" },
{ DHO_TCODE, "T200", "" },
{ DHO_PCODE, "P200", "cc-two" }
};
std::list<OptionDescriptorPtr> tagged_options;
for ( auto const& opt_to_make : opts_to_make) {
OptionDescriptor desc = createOption<OptionString>(Option::V4, opt_to_make.code_,
true, false, false, opt_to_make.value_);
desc.space_name_ = DHCP4_OPTION_SPACE;
if (!opt_to_make.cclass_.empty()) {
desc.addClientClass(opt_to_make.cclass_);
}
tagged_options.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
}
return (tagged_options);
}
void
GenericConfigBackendDHCPv4Test::updateClassTaggedOptions(
std::list<OptionDescriptorPtr>& options) {
for ( auto& desc : options) {
OptionStringPtr opt = boost::dynamic_pointer_cast<OptionString>(desc->option_);
ASSERT_TRUE(opt);
std::string new_value(opt->getValue() + std::string(".") + opt->getValue());
opt->setValue(new_value);
}
}
// Macro the make SCOPED_TRACE around equivalance functon more compact and helpful.
#define SCOPED_OPT_COMPARE(exp_opt,test_opt)\
{\
std::stringstream oss;\
oss << "Options not equal:\n"\
<< " exp_opt: " << exp_opt.option_->toText() << "\n"\
<< " test_opt: " << (test_opt.option_ ? test_opt.option_->toText() : "<null>") << "\n";\
SCOPED_TRACE(oss.str());\
testOptionsEquivalent(exp_opt,test_opt);\
}
// Verify that one can add multiple global instances of the same option code
// and that they can be distinguished via their client_classes.
void
GenericConfigBackendDHCPv4Test::globalOption4WithClientClassesTest() {
// Add the options to global scope.
auto ref_options = makeClassTaggedOptions();
for (auto const& ref_option : ref_options) {
// Add option to the config back end.
cbptr_->createUpdateOption4(ServerSelector::ALL(), ref_option);
}
// Make sure that we can find each option.
OptionDescriptorPtr found_option;
for (auto const& ref_option : ref_options) {
// Find the option by code and client_classes.
ClientClassesPtr cclasses(new ClientClasses(ref_option->client_classes_));
found_option = cbptr_->getOption4(ServerSelector::ALL(),
ref_option->option_->getType(),
DHCP4_OPTION_SPACE,
cclasses);
ASSERT_TRUE(found_option);
SCOPED_OPT_COMPARE((*ref_option), (*found_option));
}
// Update the option values.
updateClassTaggedOptions(ref_options);
// Update each option in the backend.
for (auto const& ref_option : ref_options) {
ClientClassesPtr cclasses(new ClientClasses(ref_option->client_classes_));
// Update option in the config back end.
cbptr_->createUpdateOption4(ServerSelector::ALL(), ref_option);
// Fetch and verify the updated option.
found_option = cbptr_->getOption4(ServerSelector::ALL(),
ref_option->option_->getType(),
DHCP4_OPTION_SPACE,
cclasses);
ASSERT_TRUE(found_option);
SCOPED_OPT_COMPARE((*ref_option), (*found_option));
}
// Delete each option from the backend.
for (auto const& ref_option : ref_options) {
ClientClassesPtr cclasses(new ClientClasses(ref_option->client_classes_));
// Delete the option by code and client_classes.
ASSERT_EQ(1, cbptr_->deleteOption4(ServerSelector::ALL(),
ref_option->option_->getType(),
DHCP4_OPTION_SPACE,
cclasses));
// Finding the option by code and client_classes should fail.
found_option = cbptr_->getOption4(ServerSelector::ALL(),
ref_option->option_->getType(),
DHCP4_OPTION_SPACE,
cclasses);
ASSERT_FALSE(found_option);
}
}
void
GenericConfigBackendDHCPv4Test::getAllOptions4WithClientClassesTest() {
// Describes an option to create.
struct OptData {
uint16_t code_;
uint32_t value_;
std::string cclass_;
ServerSelector server_;
};
auto server1 = ServerSelector::ONE("server1");
auto server2 = ServerSelector::ONE("server2");
auto all = ServerSelector::ALL();
// List of options to create.
std::list<OptData> opts_to_make = {
{ 231, 1, "cc-1", server1 },
{ 231, 2, "cc-2", server1 },
{ 232, 3, "cc-3", server1 },
{ 232, 4, "cc-3", server2 },
{ 233, 5, "cc-4", server1 },
{ 233, 6, "cc-4", all },
{ 234, 7, "cc-5", all }
};
// Create two servers.
ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[1]));
ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
// Add all of the global options.
std::vector<OptionDescriptorPtr> ref_options;
for ( auto const& opt_to_make : opts_to_make) {
OptionDescriptor desc = createOption<OptionInt<uint32_t>>(Option::V4, opt_to_make.code_,
true, false, false, opt_to_make.value_);
desc.space_name_ = DHCP4_OPTION_SPACE;
if (!opt_to_make.cclass_.empty()) {
desc.addClientClass(opt_to_make.cclass_);
}
ref_options.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption4(opt_to_make.server_, ref_options.back()));
}
// Try to fetch the collection of global options for the server1.
// Build list of options we expect to get back.
std::vector<OptionDescriptorPtr> exp_options;
exp_options.push_back(ref_options[0]);
exp_options.push_back(ref_options[1]);
exp_options.push_back(ref_options[2]);
exp_options.push_back(ref_options[4]);
exp_options.push_back(ref_options[6]);
OptionContainer returned_options;
ASSERT_NO_THROW_LOG(returned_options = cbptr_->getAllOptions4(server1));
ASSERT_EQ(returned_options.size(), exp_options.size());
auto exp_option = exp_options.begin();
for (auto returned_option : returned_options) {
testOptionsEquivalent(*(*exp_option), returned_option);
++exp_option;
}
// Try to fetch the collection of global options for the server1.
// Build list of options we expect to get back.
exp_options.clear();
exp_options.push_back(ref_options[3]);
exp_options.push_back(ref_options[5]);
exp_options.push_back(ref_options[6]);
ASSERT_NO_THROW_LOG(returned_options = cbptr_->getAllOptions4(server2));
ASSERT_EQ(returned_options.size(), exp_options.size());
exp_option = exp_options.begin();
for (auto returned_option : returned_options) {
testOptionsEquivalent(*(*exp_option), returned_option);
++exp_option;
}
// Try to fetch the collection of global options for the server1.
// Build list of options we expect to get back.
exp_options.clear();
exp_options.push_back(ref_options[5]);
exp_options.push_back(ref_options[6]);
ASSERT_NO_THROW_LOG(returned_options = cbptr_->getAllOptions4(all));
ASSERT_EQ(returned_options.size(), exp_options.size());
exp_option = exp_options.begin();
for (auto returned_option : returned_options) {
testOptionsEquivalent(*(*exp_option), returned_option);
++exp_option;
}
}
void
GenericConfigBackendDHCPv4Test::createUpdateDeleteSubnetOption4Test() {
// Insert new subnet.
@ -4718,123 +4935,6 @@ GenericConfigBackendDHCPv4Test::multipleAuditEntriesTest() {
}
}
std::list<OptionDescriptorPtr>
GenericConfigBackendDHCPv4Test::makeClassTaggedOptions() {
// Describes an option to create.
struct OptData {
uint16_t code_;
std::string value_;
std::string cclass_;
};
// List of options to create.
std::list<OptData> opts_to_make = {
{ DHO_TCODE, "T100", "cc-one" },
{ DHO_PCODE, "P100", "cc-one" },
{ DHO_PCODE, "P300", "" },
{ DHO_TCODE, "T200", "" },
{ DHO_PCODE, "P200", "cc-two" }
};
std::list<OptionDescriptorPtr> tagged_options;
for ( auto const& opt_to_make : opts_to_make) {
OptionDescriptor desc = createOption<OptionString>(Option::V4, opt_to_make.code_,
true, false, false, opt_to_make.value_);
desc.space_name_ = DHCP4_OPTION_SPACE;
if (!opt_to_make.cclass_.empty()) {
desc.addClientClass(opt_to_make.cclass_);
}
tagged_options.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
}
return (tagged_options);
}
void
GenericConfigBackendDHCPv4Test::updateClassTaggedOptions(
std::list<OptionDescriptorPtr>& options) {
for ( auto& desc : options) {
OptionStringPtr opt = boost::dynamic_pointer_cast<OptionString>(desc->option_);
ASSERT_TRUE(opt);
std::string new_value(opt->getValue() + std::string(".") + opt->getValue());
opt->setValue(new_value);
}
}
// Macro the make SCOPED_TRACE around equivalance functon more compact and helpful.
#define SCOPED_OPT_COMPARE(exp_opt,test_opt)\
{\
std::stringstream oss;\
oss << "Options not equal:\n"\
<< " exp_opt: " << exp_opt.option_->toText() << "\n"\
<< " test_opt: " << (test_opt.option_ ? test_opt.option_->toText() : "<null>") << "\n";\
SCOPED_TRACE(oss.str());\
testOptionsEquivalent(exp_opt,test_opt);\
}
// Verify that one can add multiple global instances of the same option code
// and that they can be distinguished via their client_classes.
void
GenericConfigBackendDHCPv4Test::globalOption4WithClientClassesTest() {
// Add the options to global scope.
auto ref_options = makeClassTaggedOptions();
for (auto const& ref_option : ref_options) {
// Add option to the config back end.
cbptr_->createUpdateOption4(ServerSelector::ALL(), ref_option);
}
// Make sure that we can find each option.
OptionDescriptorPtr found_option;
for (auto const& ref_option : ref_options) {
// Find the option by code and client_classes.
ClientClassesPtr cclasses(new ClientClasses(ref_option->client_classes_));
found_option = cbptr_->getOption4(ServerSelector::ALL(),
ref_option->option_->getType(),
DHCP4_OPTION_SPACE,
cclasses);
ASSERT_TRUE(found_option);
SCOPED_OPT_COMPARE((*ref_option), (*found_option));
}
// Update the option values.
updateClassTaggedOptions(ref_options);
// Update each option in the backend.
for (auto const& ref_option : ref_options) {
ClientClassesPtr cclasses(new ClientClasses(ref_option->client_classes_));
// Update option in the config back end.
cbptr_->createUpdateOption4(ServerSelector::ALL(), ref_option);
// Fetch and verify the updated option.
found_option = cbptr_->getOption4(ServerSelector::ALL(),
ref_option->option_->getType(),
DHCP4_OPTION_SPACE,
cclasses);
ASSERT_TRUE(found_option);
SCOPED_OPT_COMPARE((*ref_option), (*found_option));
}
// Delete each option from the backend.
for (auto const& ref_option : ref_options) {
ClientClassesPtr cclasses(new ClientClasses(ref_option->client_classes_));
// Delete the option by code and client_classes.
ASSERT_EQ(1, cbptr_->deleteOption4(ServerSelector::ALL(),
ref_option->option_->getType(),
DHCP4_OPTION_SPACE,
cclasses));
// Finding the option by code and client_classes should fail.
found_option = cbptr_->getOption4(ServerSelector::ALL(),
ref_option->option_->getType(),
DHCP4_OPTION_SPACE,
cclasses);
ASSERT_FALSE(found_option);
}
}
// Verify that one can add multiple instances of the same option code
// to a shared-network and that they can be distinguished via their client_classes.
void

View File

@ -315,6 +315,22 @@ public:
/// @brief This test verifies that modified global options can be retrieved.
void getModifiedOptions4Test();
/// @brief Creates a list of string options with and without client_class tags.
/// It creates 3 DHO_TCODE options and 2 DHO_PCODE options.
std::list<OptionDescriptorPtr> makeClassTaggedOptions();
/// @brief Updates the value of each string option in the list.
void updateClassTaggedOptions(std::list<OptionDescriptorPtr>& options);
/// @brief This test verifies that multiple instances of an option can
/// be added to global scope and be distinguished from one another
/// by their client-classes content.
void globalOption4WithClientClassesTest();
/// @brief This test verifies that global options with varying client-classes
/// and varying server tags are handled properly.
void getAllOptions4WithClientClassesTest();
/// @brief This test verifies that subnet level option can be added, updated and
/// deleted.
void createUpdateDeleteSubnetOption4Test();
@ -369,18 +385,6 @@ public:
/// event and it does not matter).
void multipleAuditEntriesTest();
/// @brief Creates a list of string options with and without client_class tags.
/// It creates 3 DHO_TCODE options and 2 DHO_PCODE options.
std::list<OptionDescriptorPtr> makeClassTaggedOptions();
/// @brief Updates the value of each string option in the list.
void updateClassTaggedOptions(std::list<OptionDescriptorPtr>& options);
/// @brief This test verifies that multiple instances of an option can
/// be added to global scope and be distinguished from one another
/// by their client-classes content.
void globalOption4WithClientClassesTest();
/// @brief This test verifies that multiple instances of an option can
/// be added to a shared-network and be distinguished from one another
/// by their client-classes content.