2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-29 13:07:50 +00:00

[#2314] Checkpoint: allows multiple entries

This commit is contained in:
Francis Dupont 2022-03-17 17:53:35 +01:00 committed by Razvan Becheriu
parent 5194803106
commit ebe3bdfb69
4 changed files with 237 additions and 96 deletions

View File

@ -1856,7 +1856,7 @@ expression.
] ]
} }
If (and only if) the query includes a ``host-name`` option (code 12), If (and only if) the **query** includes a ``host-name`` option (code 12),
a ``boot-file-name`` option (code 67) is added to the response with the host a ``boot-file-name`` option (code 67) is added to the response with the host
name followed by ``.boot`` for content. name followed by ``.boot`` for content.
@ -1877,6 +1877,9 @@ Since Kea 2.1.4, the ``client-class`` parameter specifies a guard: it takes
a client class name, when not empty the entry is skipped if the query does a client class name, when not empty the entry is skipped if the query does
not belong to the class. not belong to the class.
Since Kea 2.1.4, it is allowed to have multiple entries for the same option,
but each entry must have exactly one action.
.. _host-cmds: .. _host-cmds:
``host_cmds``: Host Commands ``host_cmds``: Host Commands

View File

@ -204,9 +204,6 @@ FlexOptionImpl::parseOptionConfig(ConstElementPtr option) {
} }
code = def->getCode(); code = def->getCode();
} }
if (option_config_map_.count(code)) {
isc_throw(BadValue, "option " << code << " was already specified");
}
bool csv_format = false; bool csv_format = false;
if (csv_format_elem) { if (csv_format_elem) {
@ -250,7 +247,10 @@ FlexOptionImpl::parseOptionConfig(ConstElementPtr option) {
isc_throw(BadValue, "no action: " << option->str()); isc_throw(BadValue, "no action: " << option->str());
} }
option_config_map_[code] = opt_cfg; // The [] operator creates the item if it does not exist before
// returning a reference to it.
OptionConfigList& opt_lst = option_config_map_[code];
opt_lst.push_back(opt_cfg);
} }
void void

View File

@ -157,8 +157,11 @@ public:
/// @brief The type of shared pointers to option config. /// @brief The type of shared pointers to option config.
typedef boost::shared_ptr<OptionConfig> OptionConfigPtr; typedef boost::shared_ptr<OptionConfig> OptionConfigPtr;
/// @brief The type of lists of shared pointers to option config.
typedef std::list<OptionConfigPtr> OptionConfigList;
/// @brief The type of the option config map. /// @brief The type of the option config map.
typedef std::map<uint16_t, OptionConfigPtr> OptionConfigMap; typedef std::map<uint16_t, OptionConfigList> OptionConfigMap;
/// @brief Constructor. /// @brief Constructor.
FlexOptionImpl(); FlexOptionImpl();
@ -189,90 +192,94 @@ public:
void process(isc::dhcp::Option::Universe universe, void process(isc::dhcp::Option::Universe universe,
PktType query, PktType response) { PktType query, PktType response) {
for (auto pair : getOptionConfigMap()) { for (auto pair : getOptionConfigMap()) {
const OptionConfigPtr& opt_cfg = pair.second; for (const OptionConfigPtr& opt_cfg : pair.second) {
const isc::dhcp::ClientClass& client_class = opt_cfg->getClass(); const isc::dhcp::ClientClass& client_class =
if (!client_class.empty()) { opt_cfg->getClass();
if (!query->inClass(client_class)) { if (!client_class.empty()) {
logClass(client_class, opt_cfg->getCode()); if (!query->inClass(client_class)) {
continue; logClass(client_class, opt_cfg->getCode());
continue;
}
} }
} std::string value;
std::string value; isc::dhcp::OptionBuffer buffer;
isc::dhcp::OptionBuffer buffer; isc::dhcp::OptionPtr opt =
isc::dhcp::OptionPtr opt = response->getOption(opt_cfg->getCode()); response->getOption(opt_cfg->getCode());
isc::dhcp::OptionDefinitionPtr def = opt_cfg->getOptionDef(); isc::dhcp::OptionDefinitionPtr def = opt_cfg->getOptionDef();
switch (opt_cfg->getAction()) { switch (opt_cfg->getAction()) {
case NONE: case NONE:
break;
case ADD:
// Don't add if option is already there.
if (opt) {
break; break;
} case ADD:
value = isc::dhcp::evaluateString(*opt_cfg->getExpr(), *query); // Don't add if option is already there.
// Do nothing is the expression evaluates to empty. if (opt) {
if (value.empty()) { break;
break; }
} value = isc::dhcp::evaluateString(*opt_cfg->getExpr(), *query);
// Set the value. // Do nothing is the expression evaluates to empty.
if (def) { if (value.empty()) {
std::vector<std::string> split_vec = break;
}
// Set the value.
if (def) {
std::vector<std::string> split_vec =
isc::util::str::tokens(value, ",", true); isc::util::str::tokens(value, ",", true);
opt = def->optionFactory(universe, opt_cfg->getCode(), opt = def->optionFactory(universe, opt_cfg->getCode(),
split_vec); split_vec);
} else { } else {
buffer.assign(value.begin(), value.end()); buffer.assign(value.begin(), value.end());
opt.reset(new isc::dhcp::Option(universe, opt.reset(new isc::dhcp::Option(universe,
opt_cfg->getCode(), opt_cfg->getCode(),
buffer)); buffer));
} }
// Add the option. // Add the option.
response->addOption(opt); response->addOption(opt);
logAction(ADD, opt_cfg->getCode(), value); logAction(ADD, opt_cfg->getCode(), value);
break;
case SUPERSEDE:
// Do nothing is the expression evaluates to empty.
value = isc::dhcp::evaluateString(*opt_cfg->getExpr(), *query);
if (value.empty()) {
break; break;
} case SUPERSEDE:
// Remove the option if already there. // Do nothing is the expression evaluates to empty.
while (opt) { value = isc::dhcp::evaluateString(*opt_cfg->getExpr(),
response->delOption(opt_cfg->getCode()); *query);
opt = response->getOption(opt_cfg->getCode()); if (value.empty()) {
} break;
// Set the value. }
if (def) { // Remove the option if already there.
std::vector<std::string> split_vec = while (opt) {
response->delOption(opt_cfg->getCode());
opt = response->getOption(opt_cfg->getCode());
}
// Set the value.
if (def) {
std::vector<std::string> split_vec =
isc::util::str::tokens(value, ",", true); isc::util::str::tokens(value, ",", true);
opt = def->optionFactory(universe, opt_cfg->getCode(), opt = def->optionFactory(universe, opt_cfg->getCode(),
split_vec); split_vec);
} else { } else {
buffer.assign(value.begin(), value.end()); buffer.assign(value.begin(), value.end());
opt.reset(new isc::dhcp::Option(universe, opt.reset(new isc::dhcp::Option(universe,
opt_cfg->getCode(), opt_cfg->getCode(),
buffer)); buffer));
} }
// Add the option. // Add the option.
response->addOption(opt); response->addOption(opt);
logAction(SUPERSEDE, opt_cfg->getCode(), value); logAction(SUPERSEDE, opt_cfg->getCode(), value);
break; break;
case REMOVE: case REMOVE:
// Nothing to remove if option is not present. // Nothing to remove if option is not present.
if (!opt) { if (!opt) {
break;
}
// Do nothing is the expression evaluates to false.
if (!isc::dhcp::evaluateBool(*opt_cfg->getExpr(), *query)) {
break;
}
// Remove the option.
while (opt) {
response->delOption(opt_cfg->getCode());
opt = response->getOption(opt_cfg->getCode());
}
logAction(REMOVE, opt_cfg->getCode(), "");
break; break;
} }
// Do nothing is the expression evaluates to false.
if (!isc::dhcp::evaluateBool(*opt_cfg->getExpr(), *query)) {
break;
}
// Remove the option.
while (opt) {
response->delOption(opt_cfg->getCode());
opt = response->getOption(opt_cfg->getCode());
}
logAction(REMOVE, opt_cfg->getCode(), "");
break;
} }
} }
} }

View File

@ -281,6 +281,8 @@ TEST_F(FlexOptionTest, optionConfigUnknownCodeNoCSVFormat) {
auto map = impl_->getOptionConfigMap(); auto map = impl_->getOptionConfigMap();
EXPECT_EQ(1, map.count(109)); EXPECT_EQ(1, map.count(109));
auto opt_lst = map[109];
EXPECT_EQ(1, opt_lst.size());
} }
// Verify that the definition is not required when csv-format is false. // Verify that the definition is not required when csv-format is false.
@ -299,6 +301,8 @@ TEST_F(FlexOptionTest, optionConfigUnknownCodeDisableCSVFormat) {
auto map = impl_->getOptionConfigMap(); auto map = impl_->getOptionConfigMap();
EXPECT_EQ(1, map.count(109)); EXPECT_EQ(1, map.count(109));
auto opt_lst = map[109];
EXPECT_EQ(1, opt_lst.size());
} }
// Verify that the code must be a known option when csv-format is true. // Verify that the code must be a known option when csv-format is true.
@ -330,6 +334,8 @@ TEST_F(FlexOptionTest, optionConfigStandardName) {
auto map = impl_->getOptionConfigMap(); auto map = impl_->getOptionConfigMap();
EXPECT_EQ(1, map.count(DHO_HOST_NAME)); EXPECT_EQ(1, map.count(DHO_HOST_NAME));
auto opt_lst = map[DHO_HOST_NAME];
EXPECT_EQ(1, opt_lst.size());
} }
// Verify that the name can be an user defined option. // Verify that the name can be an user defined option.
@ -352,6 +358,8 @@ TEST_F(FlexOptionTest, optionConfigDefinedName) {
auto map = impl_->getOptionConfigMap(); auto map = impl_->getOptionConfigMap();
EXPECT_EQ(1, map.count(222)); EXPECT_EQ(1, map.count(222));
auto opt_lst = map[222];
EXPECT_EQ(1, opt_lst.size());
} }
// Last resort is only option 43... // Last resort is only option 43...
@ -386,7 +394,7 @@ TEST_F(FlexOptionTest, optionConfigBadCSVFormat) {
EXPECT_EQ("'csv-format' must be a boolean: 123", impl_->getErrMsg()); EXPECT_EQ("'csv-format' must be a boolean: 123", impl_->getErrMsg());
} }
// Verify that an option can be configured only once. // Verify that an option can be configured more than once.
TEST_F(FlexOptionTest, optionConfigTwice) { TEST_F(FlexOptionTest, optionConfigTwice) {
ElementPtr options = Element::createList(); ElementPtr options = Element::createList();
ElementPtr option = Element::createMap(); ElementPtr option = Element::createMap();
@ -396,9 +404,15 @@ TEST_F(FlexOptionTest, optionConfigTwice) {
ElementPtr code = Element::create(DHO_HOST_NAME); ElementPtr code = Element::create(DHO_HOST_NAME);
option->set("code", code); option->set("code", code);
// Add it a second time.
options->add(option); options->add(option);
EXPECT_THROW(impl_->testConfigure(options), BadValue); EXPECT_NO_THROW(impl_->testConfigure(options));
EXPECT_EQ("option 12 was already specified", impl_->getErrMsg()); EXPECT_TRUE(impl_->getErrMsg().empty());
auto map = impl_->getOptionConfigMap();
EXPECT_EQ(1, map.count(DHO_HOST_NAME));
auto opt_lst = map[DHO_HOST_NAME];
EXPECT_EQ(2, opt_lst.size());
} }
// Verify that the add value must be a string. // Verify that the add value must be a string.
@ -456,8 +470,13 @@ TEST_F(FlexOptionTest, optionConfigAdd4) {
EXPECT_TRUE(impl_->getErrMsg().empty()); EXPECT_TRUE(impl_->getErrMsg().empty());
auto map = impl_->getOptionConfigMap(); auto map = impl_->getOptionConfigMap();
FlexOptionImpl::OptionConfigList opt_lst;
ASSERT_NO_THROW(opt_lst = map.at(DHO_HOST_NAME));
ASSERT_FALSE(opt_lst.empty());
EXPECT_EQ(1, opt_lst.size());
FlexOptionImpl::OptionConfigPtr opt_cfg; FlexOptionImpl::OptionConfigPtr opt_cfg;
ASSERT_NO_THROW(opt_cfg = map.at(DHO_HOST_NAME)); ASSERT_NO_THROW(opt_cfg = opt_lst.front());
ASSERT_TRUE(opt_cfg); ASSERT_TRUE(opt_cfg);
EXPECT_EQ(DHO_HOST_NAME, opt_cfg->getCode()); EXPECT_EQ(DHO_HOST_NAME, opt_cfg->getCode());
EXPECT_EQ(FlexOptionImpl::ADD, opt_cfg->getAction()); EXPECT_EQ(FlexOptionImpl::ADD, opt_cfg->getAction());
@ -488,8 +507,13 @@ TEST_F(FlexOptionTest, optionConfigAdd6) {
EXPECT_TRUE(impl_->getErrMsg().empty()); EXPECT_TRUE(impl_->getErrMsg().empty());
auto map = impl_->getOptionConfigMap(); auto map = impl_->getOptionConfigMap();
FlexOptionImpl::OptionConfigList opt_lst;
ASSERT_NO_THROW(opt_lst = map.at(D6O_BOOTFILE_URL));
ASSERT_FALSE(opt_lst.empty());
EXPECT_EQ(1, opt_lst.size());
FlexOptionImpl::OptionConfigPtr opt_cfg; FlexOptionImpl::OptionConfigPtr opt_cfg;
ASSERT_NO_THROW(opt_cfg = map.at(D6O_BOOTFILE_URL)); ASSERT_NO_THROW(opt_cfg = opt_lst.front());
ASSERT_TRUE(opt_cfg); ASSERT_TRUE(opt_cfg);
EXPECT_EQ(D6O_BOOTFILE_URL, opt_cfg->getCode()); EXPECT_EQ(D6O_BOOTFILE_URL, opt_cfg->getCode());
EXPECT_EQ(FlexOptionImpl::ADD, opt_cfg->getAction()); EXPECT_EQ(FlexOptionImpl::ADD, opt_cfg->getAction());
@ -560,8 +584,13 @@ TEST_F(FlexOptionTest, optionConfigSupersede4) {
EXPECT_TRUE(impl_->getErrMsg().empty()); EXPECT_TRUE(impl_->getErrMsg().empty());
auto map = impl_->getOptionConfigMap(); auto map = impl_->getOptionConfigMap();
FlexOptionImpl::OptionConfigList opt_lst;
ASSERT_NO_THROW(opt_lst = map.at(DHO_HOST_NAME));
ASSERT_FALSE(opt_lst.empty());
EXPECT_EQ(1, opt_lst.size());
FlexOptionImpl::OptionConfigPtr opt_cfg; FlexOptionImpl::OptionConfigPtr opt_cfg;
ASSERT_NO_THROW(opt_cfg = map.at(DHO_HOST_NAME)); ASSERT_NO_THROW(opt_cfg = opt_lst.front());
ASSERT_TRUE(opt_cfg); ASSERT_TRUE(opt_cfg);
EXPECT_EQ(DHO_HOST_NAME, opt_cfg->getCode()); EXPECT_EQ(DHO_HOST_NAME, opt_cfg->getCode());
EXPECT_EQ(FlexOptionImpl::SUPERSEDE, opt_cfg->getAction()); EXPECT_EQ(FlexOptionImpl::SUPERSEDE, opt_cfg->getAction());
@ -592,8 +621,13 @@ TEST_F(FlexOptionTest, optionConfigSupersede6) {
EXPECT_TRUE(impl_->getErrMsg().empty()); EXPECT_TRUE(impl_->getErrMsg().empty());
auto map = impl_->getOptionConfigMap(); auto map = impl_->getOptionConfigMap();
FlexOptionImpl::OptionConfigList opt_lst;
ASSERT_NO_THROW(opt_lst = map.at(D6O_BOOTFILE_URL));
ASSERT_FALSE(opt_lst.empty());
EXPECT_EQ(1, opt_lst.size());
FlexOptionImpl::OptionConfigPtr opt_cfg; FlexOptionImpl::OptionConfigPtr opt_cfg;
ASSERT_NO_THROW(opt_cfg = map.at(D6O_BOOTFILE_URL)); ASSERT_NO_THROW(opt_cfg = opt_lst.front());
ASSERT_TRUE(opt_cfg); ASSERT_TRUE(opt_cfg);
EXPECT_EQ(D6O_BOOTFILE_URL, opt_cfg->getCode()); EXPECT_EQ(D6O_BOOTFILE_URL, opt_cfg->getCode());
EXPECT_EQ(FlexOptionImpl::SUPERSEDE, opt_cfg->getAction()); EXPECT_EQ(FlexOptionImpl::SUPERSEDE, opt_cfg->getAction());
@ -664,8 +698,13 @@ TEST_F(FlexOptionTest, optionConfigRemove4) {
EXPECT_TRUE(impl_->getErrMsg().empty()); EXPECT_TRUE(impl_->getErrMsg().empty());
auto map = impl_->getOptionConfigMap(); auto map = impl_->getOptionConfigMap();
FlexOptionImpl::OptionConfigList opt_lst;
ASSERT_NO_THROW(opt_lst = map.at(DHO_HOST_NAME));
ASSERT_FALSE(opt_lst.empty());
EXPECT_EQ(1, opt_lst.size());
FlexOptionImpl::OptionConfigPtr opt_cfg; FlexOptionImpl::OptionConfigPtr opt_cfg;
ASSERT_NO_THROW(opt_cfg = map.at(DHO_HOST_NAME)); ASSERT_NO_THROW(opt_cfg = opt_lst.front());
ASSERT_TRUE(opt_cfg); ASSERT_TRUE(opt_cfg);
EXPECT_EQ(DHO_HOST_NAME, opt_cfg->getCode()); EXPECT_EQ(DHO_HOST_NAME, opt_cfg->getCode());
EXPECT_EQ(FlexOptionImpl::REMOVE, opt_cfg->getAction()); EXPECT_EQ(FlexOptionImpl::REMOVE, opt_cfg->getAction());
@ -701,8 +740,13 @@ TEST_F(FlexOptionTest, optionConfigRemove6) {
EXPECT_TRUE(impl_->getErrMsg().empty()); EXPECT_TRUE(impl_->getErrMsg().empty());
auto map = impl_->getOptionConfigMap(); auto map = impl_->getOptionConfigMap();
FlexOptionImpl::OptionConfigList opt_lst;
ASSERT_NO_THROW(opt_lst = map.at(D6O_BOOTFILE_URL));
ASSERT_FALSE(opt_lst.empty());
EXPECT_EQ(1, opt_lst.size());
FlexOptionImpl::OptionConfigPtr opt_cfg; FlexOptionImpl::OptionConfigPtr opt_cfg;
ASSERT_NO_THROW(opt_cfg = map.at(D6O_BOOTFILE_URL)); ASSERT_NO_THROW(opt_cfg = opt_lst.front());
ASSERT_TRUE(opt_cfg); ASSERT_TRUE(opt_cfg);
EXPECT_EQ(D6O_BOOTFILE_URL, opt_cfg->getCode()); EXPECT_EQ(D6O_BOOTFILE_URL, opt_cfg->getCode());
EXPECT_EQ(FlexOptionImpl::REMOVE, opt_cfg->getAction()); EXPECT_EQ(FlexOptionImpl::REMOVE, opt_cfg->getAction());
@ -783,15 +827,25 @@ TEST_F(FlexOptionTest, optionConfigList) {
auto map = impl_->getOptionConfigMap(); auto map = impl_->getOptionConfigMap();
EXPECT_EQ(2, map.size()); EXPECT_EQ(2, map.size());
FlexOptionImpl::OptionConfigList opt1_lst;
ASSERT_NO_THROW(opt1_lst = map.at(DHO_HOST_NAME));
ASSERT_FALSE(opt1_lst.empty());
EXPECT_EQ(1, opt1_lst.size());
FlexOptionImpl::OptionConfigPtr opt1_cfg; FlexOptionImpl::OptionConfigPtr opt1_cfg;
ASSERT_NO_THROW(opt1_cfg = map.at(DHO_HOST_NAME)); ASSERT_NO_THROW(opt1_cfg = opt1_lst.front());
ASSERT_TRUE(opt1_cfg); ASSERT_TRUE(opt1_cfg);
EXPECT_EQ(DHO_HOST_NAME, opt1_cfg->getCode()); EXPECT_EQ(DHO_HOST_NAME, opt1_cfg->getCode());
EXPECT_EQ(FlexOptionImpl::ADD, opt1_cfg->getAction()); EXPECT_EQ(FlexOptionImpl::ADD, opt1_cfg->getAction());
EXPECT_EQ("'abc'", opt1_cfg->getText()); EXPECT_EQ("'abc'", opt1_cfg->getText());
FlexOptionImpl::OptionConfigList opt2_lst;
ASSERT_NO_THROW(opt2_lst = map.at(DHO_ROOT_PATH));
ASSERT_FALSE(opt2_lst.empty());
EXPECT_EQ(1, opt2_lst.size());
FlexOptionImpl::OptionConfigPtr opt2_cfg; FlexOptionImpl::OptionConfigPtr opt2_cfg;
ASSERT_NO_THROW(opt2_cfg = map.at(DHO_ROOT_PATH)); ASSERT_NO_THROW(opt2_cfg = opt2_lst.front());
ASSERT_TRUE(opt2_cfg); ASSERT_TRUE(opt2_cfg);
EXPECT_EQ(DHO_ROOT_PATH, opt2_cfg->getCode()); EXPECT_EQ(DHO_ROOT_PATH, opt2_cfg->getCode());
EXPECT_EQ(FlexOptionImpl::SUPERSEDE, opt2_cfg->getAction()); EXPECT_EQ(FlexOptionImpl::SUPERSEDE, opt2_cfg->getAction());
@ -818,7 +872,8 @@ TEST_F(FlexOptionTest, processNone) {
opt_cfg(new FlexOptionImpl::OptionConfig(D6O_BOOTFILE_URL, def)); opt_cfg(new FlexOptionImpl::OptionConfig(D6O_BOOTFILE_URL, def));
EXPECT_EQ(FlexOptionImpl::NONE, opt_cfg->getAction()); EXPECT_EQ(FlexOptionImpl::NONE, opt_cfg->getAction());
auto map = impl_->getMutableOptionConfigMap(); auto map = impl_->getMutableOptionConfigMap();
map[DHO_HOST_NAME] = opt_cfg; auto& opt_lst = map[DHO_HOST_NAME];
opt_lst.push_back(opt_cfg);
Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345)); Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345));
Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345)); Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345));
@ -1163,6 +1218,77 @@ TEST_F(FlexOptionTest, processSupersedeEmpty) {
EXPECT_EQ(0, memcmp(&buffer[0], "abc", 3)); EXPECT_EQ(0, memcmp(&buffer[0], "abc", 3));
} }
// Verify that SUPERSEDE if exists + ADD adds a not yet existing option.
TEST_F(FlexOptionTest, processSupersedeAddNotExisting) {
CfgMgr::instance().setFamily(AF_INET6);
ElementPtr options = Element::createList();
ElementPtr option1 = Element::createMap();
options->add(option1);
ElementPtr code = Element::create(D6O_BOOTFILE_URL);
option1->set("code", code);
string action = "ifelse(option[bootfile-url].exists,'supersede','')";
ElementPtr supersede = Element::create(action);
option1->set("supersede", supersede);
ElementPtr option2 = Element::createMap();
options->add(option2);
option2->set("code", code);
ElementPtr add = Element::create(string("'add'"));
option2->set("add", add);
EXPECT_NO_THROW(impl_->testConfigure(options));
EXPECT_TRUE(impl_->getErrMsg().empty());
Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345));
Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345));
EXPECT_FALSE(response->getOption(D6O_BOOTFILE_URL));
EXPECT_NO_THROW(impl_->process<Pkt6Ptr>(Option::V6, query, response));
OptionPtr opt = response->getOption(D6O_BOOTFILE_URL);
ASSERT_TRUE(opt);
EXPECT_EQ(D6O_BOOTFILE_URL, opt->getType());
const OptionBuffer& buffer = opt->getData();
ASSERT_EQ(3, buffer.size());
EXPECT_EQ(0, memcmp(&buffer[0], "add", 3));
}
// Verify that SUPERSEDE if exists + ADD supersedes an existing option.
TEST_F(FlexOptionTest, processSupersedeAddExisting) {
ElementPtr options = Element::createList();
ElementPtr option1 = Element::createMap();
options->add(option1);
ElementPtr code = Element::create(DHO_HOST_NAME);
option1->set("code", code);
string action = "ifelse(option[host-name].exists,'supersede','')";
ElementPtr supersede = Element::create(action);
option1->set("supersede", supersede);
ElementPtr option2 = Element::createMap();
options->add(option2);
option2->set("code", code);
ElementPtr add = Element::create(string("'add'"));
option2->set("add", add);
EXPECT_NO_THROW(impl_->testConfigure(options));
EXPECT_TRUE(impl_->getErrMsg().empty());
Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345));
Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345));
OptionStringPtr str(new OptionString(Option::V4, DHO_HOST_NAME, "foobar"));
// Be careful here: the expression is related to the query.
query->addOption(str);
response->addOption(str);
EXPECT_NO_THROW(impl_->process<Pkt4Ptr>(Option::V4, query, response));
OptionPtr opt = response->getOption(DHO_HOST_NAME);
ASSERT_TRUE(opt);
EXPECT_EQ(DHO_HOST_NAME, opt->getType());
const OptionBuffer& buffer = opt->getData();
ASSERT_EQ(9, buffer.size());
EXPECT_EQ(0, memcmp(&buffer[0], "supersede", 9));
}
// Verify that REMOVE action removes an already existing option. // Verify that REMOVE action removes an already existing option.
TEST_F(FlexOptionTest, processRemove) { TEST_F(FlexOptionTest, processRemove) {
CfgMgr::instance().setFamily(AF_INET6); CfgMgr::instance().setFamily(AF_INET6);
@ -1364,8 +1490,13 @@ TEST_F(FlexOptionTest, optionConfigGuardValid) {
EXPECT_TRUE(impl_->getErrMsg().empty()); EXPECT_TRUE(impl_->getErrMsg().empty());
auto map = impl_->getOptionConfigMap(); auto map = impl_->getOptionConfigMap();
FlexOptionImpl::OptionConfigList opt_lst;
ASSERT_NO_THROW(opt_lst = map.at(DHO_HOST_NAME));
ASSERT_FALSE(opt_lst.empty());
EXPECT_EQ(1, opt_lst.size());
FlexOptionImpl::OptionConfigPtr opt_cfg; FlexOptionImpl::OptionConfigPtr opt_cfg;
ASSERT_NO_THROW(opt_cfg = map.at(DHO_HOST_NAME)); ASSERT_NO_THROW(opt_cfg = opt_lst.front());
ASSERT_TRUE(opt_cfg); ASSERT_TRUE(opt_cfg);
EXPECT_EQ(DHO_HOST_NAME, opt_cfg->getCode()); EXPECT_EQ(DHO_HOST_NAME, opt_cfg->getCode());
EXPECT_EQ("foobar", opt_cfg->getClass()); EXPECT_EQ("foobar", opt_cfg->getClass());