}]
}
+The ``client-classes`` list is allowed in an option specification at
+any scope. Option class-tagging is enforced at the time options are
+being added to the response which occurs after lease assignment just
+before the response is to be sent to the client.
+
+
When ``never-send`` for an option is true at any scope, all
``client-classes`` entries for that option are ignored. The
option will not included.
}
}
+ const auto& cclasses = query->getClasses();
for (uint32_t vendor_id : vendor_ids) {
std::set<uint8_t> cancelled_opts;
if (!vendor_rsp->getOption(opt)) {
for (auto const& copts : co_list) {
OptionDescriptor desc = copts->get(vendor_id, opt);
- if (desc.option_ && desc.allowedForClientClasses(query->getClasses())) {
+ if (desc.option_ && desc.allowedForClientClasses(cclasses)) {
vendor_rsp->addOption(desc.option_);
added = true;
break;
}
Pkt4Ptr resp = ex.getResponse();
+ const auto& cclasses = ex.getQuery()->getClasses();
// Try to find all 'required' options in the outgoing
// message. Those that are not present will be added.
for (auto const& copts : co_list) {
OptionDescriptor desc = copts->get(DHCP4_OPTION_SPACE, required);
/// @todo TKM - not sure if otion class-tagging should be allowed here?
- if (desc.option_ && desc.allowedForClientClasses(ex.getQuery()->getClasses())) {
+ if (desc.option_ && desc.allowedForClientClasses(cclasses)) {
resp->addOption(desc.option_);
break;
}
// Step 2: Try to set the values based on classes.
// Any values defined in classes will override those from subnet level.
- const ClientClasses classes = query->getClasses();
+ const ClientClasses& classes = query->getClasses();
if (!classes.empty()) {
// Let's get class definitions
}
}
+//This test verifies that duplicates in option-data.client-classes
+// are ignored and do not affect class order.
+TEST_F(Dhcp4ParserTest, optionClientClassesDuplicateCheck) {
+ std::string config = "{ " + genIfaceConfig() + ","
+ R"^(
+ "option-data": [{
+ "name": "domain-name",
+ "data": "example.com",
+ "client-classes": [ "foo", "bar", "foo", "bar" ]
+ }],
+ "rebind-timer": 2000,
+ "renew-timer": 1000,
+ "subnet4": [],
+ "valid-lifetime": 400
+ })^";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ checkResult(status, 0);
+
+ CfgOptionPtr cfg = CfgMgr::instance().getStagingCfg()->getCfgOption();
+ const auto desc = cfg->get(DHCP4_OPTION_SPACE, DHO_DOMAIN_NAME);
+ ASSERT_TRUE(desc.option_);
+ ASSERT_EQ(desc.client_classes_.size(), 2);
+ auto cclasses = desc.client_classes_.begin();
+ EXPECT_EQ(*cclasses, "foo");
+ ++cclasses;
+ EXPECT_EQ(*cclasses, "bar");
+}
+
} // namespace
}
}
+// This test verifies that duplicates in option-data.client-classes
+// are ignored and do not affect class order.
+TEST_F(Dhcp6ParserTest, optionClientClassesDuplicateCheck) {
+ std::string config = "{ " + genIfaceConfig() + ","
+ R"^(
+ "option-data": [{
+ "name": "domain-search",
+ "data": "example.com",
+ "client-classes": [ "foo", "bar", "foo", "bar" ]
+ }],
+ "rebind-timer": 2000,
+ "renew-timer": 1000,
+ "subnet6": [],
+ "valid-lifetime": 400
+ })^";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ checkResult(status, 0);
+
+ CfgOptionPtr cfg = CfgMgr::instance().getStagingCfg()->getCfgOption();
+ const auto desc = cfg->get(DHCP6_OPTION_SPACE, D6O_DOMAIN_SEARCH);
+ ASSERT_TRUE(desc.option_);
+ ASSERT_EQ(desc.client_classes_.size(), 2);
+ auto cclasses = desc.client_classes_.begin();
+ EXPECT_EQ(*cclasses, "foo");
+ ++cclasses;
+ EXPECT_EQ(*cclasses, "bar");
+}
+
} // namespace
cc_binding,
MySqlBinding::createString(tag),
MySqlBinding::createInteger<uint8_t>(option->option_->getType()),
- MySqlBinding::condCreateString(option->space_name_),
+ MySqlBinding::condCreateString(option->space_name_)
};
MySqlTransaction transaction(conn_);
cc_binding,
MySqlBinding::createString(shared_network_name),
MySqlBinding::createInteger<uint8_t>(option->option_->getType()),
- MySqlBinding::condCreateString(option->space_name_),
+ MySqlBinding::condCreateString(option->space_name_)
};
boost::scoped_ptr<MySqlTransaction> transaction;
}
void
-ClientClasses::fromElement(isc::data::ElementPtr cc_list) {
+ClientClasses::fromElement(isc::data::ConstElementPtr cc_list) {
if (cc_list) {
clear();
if (cc_list->getType() != Element::list) {
isc_throw(BadValue, "elements of list must be valid strings");
}
- insert(cclass->stringValue());
+ static_cast<void>(insert(cclass->stringValue()));
}
}
}
/// @brief Copy constructor.
///
- /// @param ClientClasses object to be copied.
+ /// @param other ClientClasses object to be copied.
ClientClasses(const ClientClasses& other);
- /// @brief Assigns the contents of on container to another
+ /// @brief Assigns the contents of on container to another.
ClientClasses& operator=(const ClientClasses& other);
/// @brief Compares two ClientClasses container for equality
/// @return True if the two containers are equal, false otherwise.
bool equals(const ClientClasses& other) const;
- /// @brief Compares two ClientClasses container for equality
+ /// @brief Compares two ClientClasses containers for equality.
///
/// @return True if the two containers are equal, false otherwise.
bool operator==(const ClientClasses& other) const {
/// @brief Sets contents from a ListElement
///
+ /// @param list JSON list of classes from which to populate
+ /// the container.
+ ///
/// @throw BadValue if the element is not a list or contents
/// are invalid
- void fromElement(isc::data::ElementPtr list);
+ void fromElement(isc::data::ConstElementPtr list);
private:
/// @brief container part
cclasses_element = Element::createList();
ASSERT_NO_THROW(classes.fromElement(cclasses_element));
EXPECT_TRUE(classes.empty());
+
+ cclasses_element->add(Element::create("foo"));
+ cclasses_element->add(Element::create("bar"));
+ cclasses_element->add(Element::create("foo"));
+ cclasses_element->add(Element::create("bar"));
+
+ ASSERT_NO_THROW(classes.fromElement(cclasses_element));
+ ASSERT_EQ(classes.size(), 2);
+ auto cclass = classes.begin();
+ EXPECT_EQ(*cclass, "foo");
+ ++cclass;
+ EXPECT_EQ(*cclass, "bar");
}
if (client_classes_.empty()) {
return (true);
}
-
- for (const auto& cclass : client_classes_) {
- if (cclasses.contains(cclass)) {
- return (true);
+
+ if (cclasses.size() > client_classes_.size()) {
+ for (const auto& cclass : client_classes_) {
+ if (cclasses.contains(cclass)) {
+ return (true);
+ }
+ }
+ }
+ else {
+ for (const auto& cclass : cclasses) {
+ if (client_classes_.contains(cclass)) {
+ return (true);
+ }
}
}
return (false);
}
-
CfgOption::CfgOption()
: encapsulated_(false) {
}
if (!desc.option_) {
isc_throw(isc::BadValue, "option being configured must not be NULL");
- } else if (!OptionSpace::validateName(option_space)) {
+ } else if (!OptionSpace::validateName(option_space)) {
isc_throw(isc::BadValue, "invalid option space name: '"
<< option_space << "'");
}
VendorOptionSpaceCollection vendor_options_;
};
-class CfgOption; // forward declaration
-
/// @name Pointers to the @c CfgOption objects.
//@{
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));
{ "never-send", Element::boolean },
{ "user-context", Element::map },
{ "comment", Element::string },
- { "metadata", Element::map },
- { "client-classes", Element::list }
+ { "client-classes", Element::list },
+ { "metadata", Element::map }
};
/// @brief This table defines default values for options in DHCPv4.
{ "never-send", Element::boolean },
{ "user-context", Element::map },
{ "comment", Element::string },
- { "metadata", Element::map },
- { "client-classes", Element::list }
+ { "client-classes", Element::list },
+ { "metadata", Element::map }
};
/// @brief This table defines default values for options in DHCPv6.
return (option_ptr);
}
- /// @brief Find the OptionDescriptor for a given space and code within the parser
- /// context.
+ /// @brief Find the OptionDescriptor for a given space and code within
+ /// the parser context.
+ ///
/// @param space is the space name of the desired option.
/// @param code is the numeric "type" of the desired option.
- /// @return an OptionDecriptorPtr to the descriptor found or an empty ptr
+ /// @return an OptionDecriptorPtr to the descriptor found or an empty ptr.
OptionDescriptorPtr getOptionDescriptor(std::string space, uint32_t code) {
OptionDescriptorPtr od_ptr;
const auto &cfg_options = CfgMgr::instance().getStagingCfg()->getCfgOption();
ASSERT_TRUE(od);
EXPECT_TRUE(od->client_classes_.empty());
- // We skip unparse test because client-classes is only emitited if not empty.
+ // We skip unparse test because client-classes is only emitted if not empty.
}
/// @brief Check parsing of a v6 option with a client-class list.
ASSERT_TRUE(od);
EXPECT_TRUE(od->client_classes_.empty());
- // We skip unparse test because client-classes is only emitited if not empty.
+ // We skip unparse test because client-classes is only emitted if not empty.
}
// hooks-libraries element that does not contain anything.
EXPECT_EQ(ref_option.persistent_, tested_option.persistent_);
EXPECT_EQ(ref_option.cancelled_, tested_option.cancelled_);
EXPECT_EQ(ref_option.space_name_, tested_option.space_name_);
+ EXPECT_EQ(ref_option.client_classes_, tested_option.client_classes_);
+ auto ref_ctx = ref_option.getContext();
+ auto test_ctx = tested_option.getContext();
+ if (ref_ctx) {
+ ASSERT_TRUE(test_ctx);
+ EXPECT_EQ(*test_ctx, *ref_ctx);
+ } else {
+ EXPECT_FALSE(test_ctx);
+ }
}
void