2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 14:05:33 +00:00

[#401,!254] kea-dhcp4 now merges CB options into current config

Basic merging works, need to add validation against definitions.

src/bin/dhcp4/tests/config_backend_unittest.cc
    TEST_F(Dhcp4CBTest, mergeOptions) - enabled and updated

src/lib/dhcpsrv/cfg_option.*
    CfgOption::merge(CfgOption& other) - new method, conformant
    to others, that merges/updates a config option from another

src/lib/dhcpsrv/srv_config.cc
    SrvConfig::merge4(SrvConfig& other) - modified to
    merge configured options

src/lib/dhcpsrv/tests/cfg_option_unittest.cc
    TEST_F(CfgOptionTest, merge) - new test
This commit is contained in:
Thomas Markwalder
2019-03-04 10:54:53 -05:00
parent 723efb56e8
commit 1a663d09e4
5 changed files with 161 additions and 30 deletions

View File

@@ -335,15 +335,19 @@ TEST_F(Dhcp4CBTest, mergeOptionDefs) {
// This test verifies that externally configured options
// merged correctly into staging configuration.
// @todo enable test when SrvConfig can merge options.
TEST_F(Dhcp4CBTest, DISABLED_mergeOptions) {
TEST_F(Dhcp4CBTest, mergeOptions) {
string base_config =
"{ \n"
" \"option-data\": [ {"
" \"name\": \"dhcp-message\","
" \"data\": \"0A0B0C0D\","
" \"csv-format\": false"
" } ],"
" \"option-data\": [ { \n"
" \"name\": \"dhcp-message\", \n"
" \"data\": \"0A0B0C0D\", \n"
" \"csv-format\": false \n"
" },{ \n"
" \"name\": \"host-name\", \n"
" \"data\": \"old.example.com\", \n"
" \"csv-format\": true \n"
" } \n"
" ], \n"
" \"config-control\": { \n"
" \"config-databases\": [ { \n"
" \"type\": \"memfile\", \n"
@@ -358,19 +362,28 @@ TEST_F(Dhcp4CBTest, DISABLED_mergeOptions) {
extractConfig(base_config);
// Create option two and add it to first backend.
OptionDescriptorPtr opt_two(new OptionDescriptor(
createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
true, false, "my-boot-file")));
opt_two->space_name_ = DHCP4_OPTION_SPACE;
db1_->createUpdateOption4(ServerSelector::ALL(), opt_two);
OptionDescriptorPtr opt;
// Create option three and add it to second backend.
OptionDescriptorPtr opt_three(new OptionDescriptor(
createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
true, false, "your-boot-file")));
opt_three->space_name_ = DHCP4_OPTION_SPACE;
db2_->createUpdateOption4(ServerSelector::ALL(), opt_three);
// Add host-name to the first backend.
opt.reset(new OptionDescriptor(
createOption<OptionString>(Option::V4, DHO_HOST_NAME,
true, false, "new.example.com")));
opt->space_name_ = DHCP4_OPTION_SPACE;
db1_->createUpdateOption4(ServerSelector::ALL(), opt);
// Add boot-file-name to the first backend.
opt.reset(new OptionDescriptor(
createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
true, false, "my-boot-file")));
opt->space_name_ = DHCP4_OPTION_SPACE;
db1_->createUpdateOption4(ServerSelector::ALL(), opt);
// Add boot-file-name to the second backend.
opt.reset(new OptionDescriptor(
createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
true, false, "your-boot-file")));
opt->space_name_ = DHCP4_OPTION_SPACE;
db2_->createUpdateOption4(ServerSelector::ALL(), opt);
// Should parse and merge without error.
ASSERT_NO_FATAL_FAILURE(configure(base_config, CONTROL_RESULT_SUCCESS, ""));
@@ -381,13 +394,21 @@ TEST_F(Dhcp4CBTest, DISABLED_mergeOptions) {
// Option definition from JSON should be there.
CfgOptionPtr options = staging_cfg->getCfgOption();
// dhcp-message should come from the original config.
OptionDescriptor found_opt = options->get("dhcp4", DHO_DHCP_MESSAGE);
ASSERT_TRUE(found_opt.option_);
EXPECT_EQ("0x0A0B0C0D", found_opt.option_->toHexString());
// host-name should come from the first back end,
// (overwriting the original).
found_opt = options->get("dhcp4", DHO_HOST_NAME);
ASSERT_TRUE(found_opt.option_);
EXPECT_EQ("new.example.com", found_opt.option_->toString());
// booth-file-name should come from the first back end.
found_opt = options->get("dhcp4", DHO_BOOT_FILE_NAME);
ASSERT_TRUE(found_opt.option_);
EXPECT_EQ("my-boot-file", found_opt.formatted_value_);
EXPECT_EQ("my-boot-file", found_opt.option_->toString());
}
// This test verifies that externally configured shared-networks are

View File

@@ -81,6 +81,18 @@ CfgOption::getVendorIdsSpaceNames() const {
return (names);
}
void
CfgOption::merge(CfgOption& other) {
// First we merge our options into other.
// This adds my opitions that are not
// in other, to other (i.e we skip over
// duplicates).
mergeTo(other);
// Next we copy "other" on top of ourself.
other.copyTo(*this);
}
void
CfgOption::mergeTo(CfgOption& other) const {
// Merge non-vendor options.

View File

@@ -332,6 +332,22 @@ public:
/// @throw isc::BadValue if the option space is invalid.
void add(const OptionDescriptor& desc, const std::string& option_space);
/// @brief Merges another option configuration into this one.
///
/// This method calls @c mergeTo() to add this configuration's
/// options into @c other (skipping any duplicates). It then calls
/// @c copyTo() to overwrite this configurations' options with
/// the merged set in @c other.
///
/// @warning The merge operation will affect the @c other configuration.
/// Therefore, the caller must not rely on the data held in the @c other
/// object after the call to @c merge. Also, the data held in @c other must
/// not be modified after the call to @c merge because it may affect the
/// merged configuration.
///
/// @param option configurations to merge with.
void merge(CfgOption& other);
/// @brief Merges this configuration to another configuration.
///
/// This method iterates over the configuration items held in this
@@ -339,8 +355,6 @@ public:
/// as a parameter. If an item exists in the destination it is not
/// copied.
///
/// @note: this method is not longer used so should become private.
///
/// @param [out] other Configuration object to merge to.
void mergeTo(CfgOption& other) const;

View File

@@ -177,23 +177,26 @@ SrvConfig::merge(ConfigBase& other) {
void
SrvConfig::merge4(SrvConfig& other) {
/// We merge objects in order of dependency (real or theoretical).
// We merge objects in order of dependency (real or theoretical).
/// Merge globals.
// Merge globals.
mergeGlobals4(other);
/// Merge option defs
// Merge option defs
cfg_option_def_->merge((*other.getCfgOptionDef()));
/// @todo merge options
// Merge options
// @todo should we sanity check and make sure
// that there are option defs for merged options?
cfg_option_->merge((*other.getCfgOption()));
// Merge shared networks.
cfg_shared_networks4_->merge(*(other.getCfgSharedNetworks4()));
/// Merge subnets.
// Merge subnets.
cfg_subnets4_->merge(getCfgSharedNetworks4(), *(other.getCfgSubnets4()));
/// @todo merge other parts of the configuration here.
// @todo merge other parts of the configuration here.
}
void

View File

@@ -213,8 +213,8 @@ TEST_F(CfgOptionTest, add) {
EXPECT_TRUE(options->empty());
}
// This test verifies that two option configurations can be merged.
TEST_F(CfgOptionTest, merge) {
// This test verifies that one configuration can be merged into another.
TEST_F(CfgOptionTest, mergeTo) {
CfgOption cfg_src;
CfgOption cfg_dst;
@@ -327,6 +327,87 @@ TEST_F(CfgOptionTest, copy) {
EXPECT_EQ(10, container->size());
}
// This test verifies option definitions from one configuration
// can be used to update definitions in another configuration.
// In other words, definitions from an "other" configuration
// can be merged into an existing configuration, with any
// duplicates in other overwriting those in the existing
// configuration.
TEST_F(CfgOptionTest, merge) {
CfgOption this_cfg;
CfgOption other_cfg;
// Create collection of options in option space dhcp6, with option codes
// from the range of 100 to 109 and holding one byte of data equal to 0xFF.
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0xFF)));
ASSERT_NO_THROW(this_cfg.add(option, false, DHCP6_OPTION_SPACE));
}
// Create collection of options in vendor space 123, with option codes
// from the range of 100 to 109 and holding one byte of data equal to 0xFF.
for (uint16_t code = 100; code < 110; code += 2) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0xFF)));
ASSERT_NO_THROW(this_cfg.add(option, false, "vendor-123"));
}
// Create destination configuration (configuration that we merge the
// other configuration to).
// Create collection of options having even option codes in the range of
// 100 to 108.
for (uint16_t code = 100; code < 110; code += 2) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0x01)));
ASSERT_NO_THROW(other_cfg.add(option, false, DHCP6_OPTION_SPACE));
}
// Create collection of options having odd option codes in the range of
// 101 to 109.
for (uint16_t code = 101; code < 110; code += 2) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0x01)));
ASSERT_NO_THROW(other_cfg.add(option, false, "vendor-123"));
}
// Merge source configuration to the destination configuration. The options
// in the destination should be preserved. The options from the source
// configuration should be added.
ASSERT_NO_THROW(this_cfg.merge(other_cfg));
// Validate the options in the dhcp6 option space in the destination.
for (uint16_t code = 100; code < 110; ++code) {
OptionDescriptor desc = this_cfg.get(DHCP6_OPTION_SPACE, code);
ASSERT_TRUE(desc.option_);
ASSERT_EQ(1, desc.option_->getData().size());
// The options with even option codes should hold one byte of data
// equal to 0x1. These are the ones that we have initially added to
// the destination configuration. The other options should hold the
// values of 0xFF which indicates that they have been merged from the
// source configuration.
if ((code % 2) == 0) {
EXPECT_EQ(0x01, desc.option_->getData()[0]);
} else {
EXPECT_EQ(0xFF, desc.option_->getData()[0]);
}
}
// Validate the options in the vendor space.
for (uint16_t code = 100; code < 110; ++code) {
OptionDescriptor desc = this_cfg.get(123, code);
ASSERT_TRUE(desc.option_);
ASSERT_EQ(1, desc.option_->getData().size());
// This time, the options with even option codes should hold a byte
// of data equal to 0xFF. The other options should hold the byte of
// data equal to 0x01.
if ((code % 2) == 0) {
EXPECT_EQ(0xFF, desc.option_->getData()[0]);
} else {
EXPECT_EQ(0x01, desc.option_->getData()[0]);
}
}
}
// This test verifies that encapsulated options are added as sub-options
// to the top level options on request.
TEST_F(CfgOptionTest, encapsulate) {