summaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorThomas Markwalder <tmark@isc.org>2024-09-23 16:51:50 +0200
committerThomas Markwalder <tmark@isc.org>2024-10-15 19:51:56 +0200
commit74a37d39e083035bce54e747c0701f81bcafc020 (patch)
tree3693bfb64b2c5c32dbc16a35c225b5b18e42f850 /src/lib
parent[#3552] Addressed comments (diff)
downloadkea-74a37d39e083035bce54e747c0701f81bcafc020.tar.xz
kea-74a37d39e083035bce54e747c0701f81bcafc020.zip
[#3583] Added client-class to OptionDescriptor
option-data.client-classes can be parsed and stored in OptionDescriptor. /src/bin/dhcp4/dhcp4_lexer.ll /src/bin/dhcp4/dhcp4_parser.yy /src/bin/dhcp6/dhcp6_lexer.ll /src/bin/dhcp6/dhcp6_parser.yy /src/lib/dhcp/classify.* Added ClientClasses copy and equality operators /src/lib/dhcp/tests/classify_unittest.cc Udpated unit tests /src/lib/dhcpsrv/cfg_option.cc Added OptionDecription::client_classes_ and supporting funcs /src/lib/dhcpsrv/parsers/option_data_parser.cc OptionDataParser::createOption() - modified to parse 'client-classes' /src/lib/dhcpsrv/parsers/simple_parser4.cc Added 'client-classes' to OPTION4_PARAMETERS /src/lib/dhcpsrv/parsers/simple_parser6.cc Added 'client-classes' to OPTION6_PARAMETERS /src/lib/dhcpsrv/tests/cfg_option_unittest.cc Updated tests /src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc TEST_F(ParseConfigTest, optionDataClientClasses4) TEST_F(ParseConfigTest, optionDataClientClasses6) TEST_F(ParseConfigTest, optionDataClientClassesEmpty4) TEST_F(ParseConfigTest, optionDataClientClassesEmpty6) - new tests
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/dhcp/classify.cc21
-rw-r--r--src/lib/dhcp/classify.h27
-rw-r--r--src/lib/dhcp/tests/classify_unittest.cc27
-rw-r--r--src/lib/dhcpsrv/cfg_option.cc20
-rw-r--r--src/lib/dhcpsrv/cfg_option.h14
-rw-r--r--src/lib/dhcpsrv/parsers/option_data_parser.cc7
-rw-r--r--src/lib/dhcpsrv/parsers/simple_parser4.cc21
-rw-r--r--src/lib/dhcpsrv/parsers/simple_parser6.cc21
-rw-r--r--src/lib/dhcpsrv/tests/cfg_option_unittest.cc33
-rw-r--r--src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc175
10 files changed, 336 insertions, 30 deletions
diff --git a/src/lib/dhcp/classify.cc b/src/lib/dhcp/classify.cc
index f42a9e3710..784cc4a895 100644
--- a/src/lib/dhcp/classify.cc
+++ b/src/lib/dhcp/classify.cc
@@ -36,6 +36,12 @@ ClientClasses::ClientClasses(const std::string& class_names)
}
}
+ClientClasses::ClientClasses(const ClientClasses& other) {
+ for (auto const& cclass : other) {
+ insert(cclass);
+ }
+}
+
void
ClientClasses::erase(const ClientClass& class_name) {
auto& idx = container_.get<ClassNameTag>();
@@ -75,5 +81,20 @@ ClientClasses::toElement() const {
return (result);
}
+bool
+ClientClasses::equals(const ClientClasses& other) const {
+ return ((size() == other.size()) && std::equal(cbegin(), cend(), other.cbegin()));
+}
+
+ClientClasses&
+ClientClasses::operator=(const ClientClasses& other) {
+ clear();
+ for (auto const& cclass : other) {
+ insert(cclass);
+ }
+
+ return (*this);
+}
+
} // end of namespace isc::dhcp
} // end of namespace isc
diff --git a/src/lib/dhcp/classify.h b/src/lib/dhcp/classify.h
index 34977a5989..cb5340dfe5 100644
--- a/src/lib/dhcp/classify.h
+++ b/src/lib/dhcp/classify.h
@@ -122,6 +122,33 @@ namespace dhcp {
/// with commas. The class names are trimmed before insertion to the set.
ClientClasses(const std::string& class_names);
+ /// @brief Copy constructor.
+ ///
+ /// @param ClientClasses object to be copied.
+ ClientClasses(const ClientClasses& other);
+
+ /// @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
+ ///
+ /// @return True if the two containers are equal, false otherwise.
+ bool operator==(const ClientClasses& other) const {
+ return(equals(other));
+ }
+
+ /// @brief Compares two ClientClasses container for inequality
+ ///
+ /// @return True if the two containers are not equal, false otherwise.
+ bool operator!=(const ClientClasses& other) const {
+ return(!equals(other));
+ }
+
/// @brief Insert an element.
///
/// @param class_name The name of the class to insert
diff --git a/src/lib/dhcp/tests/classify_unittest.cc b/src/lib/dhcp/tests/classify_unittest.cc
index 4cf7037e36..49bfc9c48f 100644
--- a/src/lib/dhcp/tests/classify_unittest.cc
+++ b/src/lib/dhcp/tests/classify_unittest.cc
@@ -28,7 +28,7 @@ TEST(ClassifyTest, ClientClasses) {
EXPECT_FALSE(classes.contains(""));
EXPECT_FALSE(classes.contains("alpha"));
EXPECT_FALSE(classes.contains("beta"));
- EXPECT_FALSE(classes.contains("gamma"));
+ EXPECT_FALSE(classes.contains("gamma"));
classes.insert("beta");
EXPECT_FALSE(classes.contains(""));
EXPECT_FALSE(classes.contains("alpha"));
@@ -40,6 +40,31 @@ TEST(ClassifyTest, ClientClasses) {
EXPECT_TRUE (classes.contains("alpha"));
EXPECT_TRUE (classes.contains("beta"));
EXPECT_TRUE (classes.contains("gamma"));
+
+ // Copy Constructor and equality operators.
+ ClientClasses classes2(classes);
+ EXPECT_EQ(classes, classes2);
+
+ // A bigger than B
+ classes.insert("zeta");
+ EXPECT_NE(classes, classes2);
+
+ // A and B equal again.
+ classes2.insert("zeta");
+ EXPECT_EQ(classes, classes2);
+
+ // B bigger than A.
+ classes2.insert("chi");
+ EXPECT_NE(classes, classes2);
+
+ // Order matters.
+ ClientClasses classes3;
+ classes3.insert("alpha");
+ classes3.insert("gamma");
+ classes3.insert("beta");
+ classes3.insert("zeta");
+ EXPECT_EQ(classes3.size(), classes.size());
+ EXPECT_NE(classes3, classes);
}
// Check if ClientClasses object can be created from the string of comma
diff --git a/src/lib/dhcpsrv/cfg_option.cc b/src/lib/dhcpsrv/cfg_option.cc
index 61137ed5eb..a25341ec8b 100644
--- a/src/lib/dhcpsrv/cfg_option.cc
+++ b/src/lib/dhcpsrv/cfg_option.cc
@@ -51,6 +51,14 @@ OptionDescriptor::equals(const OptionDescriptor& other) const {
option_->equals(other.option_));
}
+void
+OptionDescriptor::addClientClass(const std::string& class_name) {
+ std::string trimmed = util::str::trim(class_name);
+ if (!trimmed.empty()) {
+ client_classes_.insert(ClientClass(trimmed));
+ }
+}
+
CfgOption::CfgOption()
: encapsulated_(false) {
}
@@ -502,6 +510,11 @@ CfgOption::toElementWithMetadata(const bool include_metadata) const {
map->set("metadata", opt.getMetadata());
}
+ // Include client-classes if not empty.
+ if (!opt.client_classes_.empty()) {
+ map->set("client-classes", opt.client_classes_.toElement());
+ }
+
// Push on the list
result->add(map);
}
@@ -557,10 +570,17 @@ CfgOption::toElementWithMetadata(const bool include_metadata) const {
map->set("always-send", Element::create(opt.persistent_));
// Set the cancellation flag
map->set("never-send", Element::create(opt.cancelled_));
+
+ // Include client-classes if not empty.
+ if (!opt.client_classes_.empty()) {
+ map->set("client-classes", opt.client_classes_.toElement());
+ }
+
// Push on the list
result->add(map);
}
}
+
return (result);
}
diff --git a/src/lib/dhcpsrv/cfg_option.h b/src/lib/dhcpsrv/cfg_option.h
index 884e679c14..e3e9b52bb9 100644
--- a/src/lib/dhcpsrv/cfg_option.h
+++ b/src/lib/dhcpsrv/cfg_option.h
@@ -8,6 +8,7 @@
#define CFG_OPTION_H
#include <dhcp/option.h>
+#include <dhcp/classify.h>
#include <dhcp/option_space_container.h>
#include <cc/cfg_to_element.h>
#include <cc/stamped_element.h>
@@ -89,6 +90,10 @@ public:
/// won't be set.
std::string space_name_;
+ /// @brief Collection of classes for the which option is allowed.
+ /// An empty list means no class restrictions.
+ ClientClasses client_classes_;
+
/// @brief Constructor.
///
/// @param opt option instance.
@@ -122,7 +127,8 @@ public:
persistent_(desc.persistent_),
cancelled_(desc.cancelled_),
formatted_value_(desc.formatted_value_),
- space_name_(desc.space_name_) {
+ space_name_(desc.space_name_),
+ client_classes_(desc.client_classes_) {
setContext(desc.getContext());
};
@@ -138,6 +144,7 @@ public:
cancelled_ = other.cancelled_;
formatted_value_ = other.formatted_value_;
space_name_ = other.space_name_;
+ client_classes_ = other.client_classes_;
setContext(other.getContext());
}
return (*this);
@@ -198,6 +205,11 @@ public:
bool operator!=(const OptionDescriptor& other) const {
return (!equals(other));
}
+
+ /// @brief Adds new client class for which the option is allowed.
+ ///
+ /// @param class_name Class name.
+ void addClientClass(const std::string& class_name);
};
/// @brief Multi index container for DHCP option descriptors.
diff --git a/src/lib/dhcpsrv/parsers/option_data_parser.cc b/src/lib/dhcpsrv/parsers/option_data_parser.cc
index cd544d5833..2710ffe624 100644
--- a/src/lib/dhcpsrv/parsers/option_data_parser.cc
+++ b/src/lib/dhcpsrv/parsers/option_data_parser.cc
@@ -278,6 +278,7 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
Optional<std::string> data_param = extractData(option_data);
std::string space_param = extractSpace(option_data);
ConstElementPtr user_context = option_data->get("user-context");
+ ConstElementPtr client_classes = option_data->get("client-classes");
// Require that option code or option name is specified.
if (code_param.unspecified() && name_param.unspecified()) {
@@ -440,6 +441,12 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
desc.setContext(user_context);
}
+ if (client_classes) {
+ for (auto const& class_element : client_classes->listValue()) {
+ desc.addClientClass(class_element->stringValue());
+ }
+ }
+
// All went good, so we can set the option space name.
return (make_pair(desc, space_param));
}
diff --git a/src/lib/dhcpsrv/parsers/simple_parser4.cc b/src/lib/dhcpsrv/parsers/simple_parser4.cc
index 1666419441..de0f7a63ba 100644
--- a/src/lib/dhcpsrv/parsers/simple_parser4.cc
+++ b/src/lib/dhcpsrv/parsers/simple_parser4.cc
@@ -181,16 +181,17 @@ const SimpleDefaults SimpleParser4::OPTION4_DEF_DEFAULTS = {
/// list and map types for entries.
/// Order follows option_param rules in bison grammar.
const SimpleKeywords SimpleParser4::OPTION4_PARAMETERS = {
- { "name", Element::string },
- { "data", Element::string },
- { "code", Element::integer },
- { "space", Element::string },
- { "csv-format", Element::boolean },
- { "always-send", Element::boolean },
- { "never-send", Element::boolean },
- { "user-context", Element::map },
- { "comment", Element::string },
- { "metadata", Element::map }
+ { "name", Element::string },
+ { "data", Element::string },
+ { "code", Element::integer },
+ { "space", Element::string },
+ { "csv-format", Element::boolean },
+ { "always-send", Element::boolean },
+ { "never-send", Element::boolean },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "metadata", Element::map },
+ { "client-classes", Element::list }
};
/// @brief This table defines default values for options in DHCPv4.
diff --git a/src/lib/dhcpsrv/parsers/simple_parser6.cc b/src/lib/dhcpsrv/parsers/simple_parser6.cc
index c0a2d349ec..f58ceecb31 100644
--- a/src/lib/dhcpsrv/parsers/simple_parser6.cc
+++ b/src/lib/dhcpsrv/parsers/simple_parser6.cc
@@ -175,16 +175,17 @@ const SimpleDefaults SimpleParser6::OPTION6_DEF_DEFAULTS = {
/// list and map types for entries.
/// Order follows option_param rules in bison grammar.
const SimpleKeywords SimpleParser6::OPTION6_PARAMETERS = {
- { "name", Element::string },
- { "data", Element::string },
- { "code", Element::integer },
- { "space", Element::string },
- { "csv-format", Element::boolean },
- { "always-send", Element::boolean },
- { "never-send", Element::boolean },
- { "user-context", Element::map },
- { "comment", Element::string },
- { "metadata", Element::map }
+ { "name", Element::string },
+ { "data", Element::string },
+ { "code", Element::integer },
+ { "space", Element::string },
+ { "csv-format", Element::boolean },
+ { "always-send", Element::boolean },
+ { "never-send", Element::boolean },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "metadata", Element::map },
+ { "client-classes", Element::list }
};
/// @brief This table defines default values for options in DHCPv6.
diff --git a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc
index 2fa42ae065..09b43c9f29 100644
--- a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc
@@ -1256,6 +1256,8 @@ TEST_F(CfgOptionTest, unparse) {
OptionDescriptor desc2(opt2, false, true, "12, 12, 12, 12");
std::string ctx = "{ \"comment\": \"foo\", \"bar\": 1 }";
desc2.setContext(data::Element::fromJSON(ctx));
+ desc2.addClientClass("water");
+ desc2.addClientClass("melon");
cfg.add(desc2, "dns");
OptionPtr opt3(new Option(Option::V6, D6O_STATUS_CODE, OptionBuffer(2, 0)));
cfg.add(opt3, false, false, DHCP6_OPTION_SPACE);
@@ -1264,9 +1266,23 @@ TEST_F(CfgOptionTest, unparse) {
OptionPtr opt5(new Option(Option::V6, 111));
cfg.add(opt5, false, true, "vendor-5678");
+ OptionPtr opt6(new Option(Option::V6, 112));
+ OptionDescriptor desc6(opt6, false, false);
+ desc6.addClientClass("foo");
+ desc6.addClientClass("bar");
+ cfg.add(desc6, "vendor-9999");
+
// Unparse
std::string expected = "[\n"
"{\n"
+ " \"code\": 13,\n"
+ " \"name\": \"status-code\",\n"
+ " \"space\": \"dhcp6\",\n"
+ " \"csv-format\": false,\n"
+ " \"data\": \"0000\",\n"
+ " \"always-send\": false,\n"
+ " \"never-send\": false\n"
+ "},{\n"
" \"code\": 100,\n"
" \"space\": \"dns\",\n"
" \"csv-format\": false,\n"
@@ -1274,6 +1290,7 @@ TEST_F(CfgOptionTest, unparse) {
" \"always-send\": false,\n"
" \"never-send\": true\n"
"},{\n"
+ " \"client-classes\": [ \"water\", \"melon\" ],\n"
" \"code\": 101,\n"
" \"space\": \"dns\",\n"
" \"csv-format\": true,\n"
@@ -1282,14 +1299,6 @@ TEST_F(CfgOptionTest, unparse) {
" \"never-send\": true,\n"
" \"user-context\": { \"comment\": \"foo\", \"bar\": 1 }\n"
"},{\n"
- " \"code\": 13,\n"
- " \"name\": \"status-code\",\n"
- " \"space\": \"dhcp6\",\n"
- " \"csv-format\": false,\n"
- " \"data\": \"0000\",\n"
- " \"always-send\": false,\n"
- " \"never-send\": false\n"
- "},{\n"
" \"code\": 100,\n"
" \"space\": \"vendor-1234\",\n"
" \"csv-format\": false,\n"
@@ -1301,6 +1310,14 @@ TEST_F(CfgOptionTest, unparse) {
" \"space\": \"vendor-5678\",\n"
" \"always-send\": false,\n"
" \"never-send\": true\n"
+ "},{\n"
+ " \"always-send\": false,\n"
+ " \"client-classes\": [ \"foo\", \"bar\" ],\n"
+ " \"code\": 112,\n"
+ " \"csv-format\": false,\n"
+ " \"data\": \"\",\n"
+ " \"never-send\": false\n,"
+ " \"space\": \"vendor-9999\"\n"
"}]\n";
isc::test::runToElementTest<CfgOption>(expected, cfg);
}
diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
index a715ae93f5..6af3945a31 100644
--- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
+++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
@@ -8,6 +8,7 @@
#include <cc/command_interpreter.h>
#include <cc/data.h>
#include <cc/simple_parser.h>
+#include <dhcp/classify.h>
#include <dhcp/option.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
@@ -461,6 +462,22 @@ public:
return (option_ptr);
}
+ /// @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
+ OptionDescriptorPtr getOptionDescriptor(std::string space, uint32_t code) {
+ OptionDescriptorPtr od_ptr;
+ const auto &cfg_options = CfgMgr::instance().getStagingCfg()->getCfgOption();
+ auto od = cfg_options->get(space, code);
+ if (od.option_) {
+ od_ptr = OptionDescriptor::create(od);
+ }
+
+ return (od_ptr);
+ }
+
/// @brief Wipes the contents of the context to allowing another parsing
/// during a given test if needed.
/// @param family protocol family to use during the test, defaults
@@ -1881,6 +1898,164 @@ setHooksLibrariesConfig(const char* lib1 = NULL, const char* lib2 = NULL,
return (config);
}
+/// @brief Check parsing of a v4 option with a client-class list.
+TEST_F(ParseConfigTest, optionDataClientClasses4) {
+ family_ = AF_INET;
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 0,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ], "
+ " \"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 0,"
+ " \"data\": \"192.0.2.0\","
+ " \"csv-format\": true,"
+ " \"always-send\": false,"
+ " \"client-classes\": [ \"water\", \"melon\" ]"
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt_ptr = getOptionPtr("isc", 0);
+ ASSERT_TRUE(opt_ptr);
+
+ // Verify the option descriptor's client-classes lists is correct.
+ ClientClasses exp_cc("water, melon");
+ OptionDescriptorPtr od = getOptionDescriptor("isc", 0);
+ ASSERT_TRUE(od);
+ EXPECT_EQ(od->client_classes_, exp_cc);
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
+}
+
+/// @brief Check parsing of a v4 option with a client-class list.
+TEST_F(ParseConfigTest, optionDataClientClassesEmpty4) {
+ family_ = AF_INET;
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 0,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ], "
+ " \"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 0,"
+ " \"data\": \"192.0.2.0\","
+ " \"csv-format\": true,"
+ " \"always-send\": false,"
+ " \"client-classes\": []"
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt_ptr = getOptionPtr("isc", 0);
+ ASSERT_TRUE(opt_ptr);
+
+ // Verify the option descriptor's client-classes list is empty.
+ OptionDescriptorPtr od = getOptionDescriptor("isc", 0);
+ ASSERT_TRUE(od);
+ EXPECT_TRUE(od->client_classes_.empty());
+
+ // We skip unparse test because client-classes is only emitited if not empty.
+}
+
+/// @brief Check parsing of a v6 option with a client-class list.
+TEST_F(ParseConfigTest, optionDataClientClasses6) {
+ family_ = AF_INET6;
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 0,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ], "
+ " \"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 0,"
+ " \"data\": \"192.0.2.0\","
+ " \"csv-format\": true,"
+ " \"always-send\": false,"
+ " \"client-classes\": [ \"water\", \"melon\" ]"
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt_ptr = getOptionPtr("isc", 0);
+ ASSERT_TRUE(opt_ptr);
+
+ // Verify the option descriptor's client-classes lists is correct.
+ ClientClasses exp_cc("water, melon");
+ OptionDescriptorPtr od = getOptionDescriptor("isc", 0);
+ ASSERT_TRUE(od);
+ EXPECT_EQ(od->client_classes_, exp_cc);
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
+}
+
+/// @brief Check parsing of a v4 option with a client-class list.
+TEST_F(ParseConfigTest, optionDataClientClassesEmpty6) {
+ family_ = AF_INET6;
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 0,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ], "
+ " \"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 0,"
+ " \"data\": \"192.0.2.0\","
+ " \"csv-format\": true,"
+ " \"always-send\": false,"
+ " \"client-classes\": []"
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt_ptr = getOptionPtr("isc", 0);
+ ASSERT_TRUE(opt_ptr);
+
+ // Verify the option descriptor's client-classes list is empty.
+ OptionDescriptorPtr od = getOptionDescriptor("isc", 0);
+ ASSERT_TRUE(od);
+ EXPECT_TRUE(od->client_classes_.empty());
+
+ // We skip unparse test because client-classes is only emitited if not empty.
+}
+
// hooks-libraries element that does not contain anything.
TEST_F(ParseConfigTest, noHooksLibraries) {
// Check that no libraries are currently loaded