# Whenever new file is added to the parsers folder, it must be
# added here.
EXTRA_DIST =
+EXTRA_DIST += parsers/client_class_def_parser.cc
+EXTRA_DIST += parsers/client_class_def_parser.h
+EXTRA_DIST += parsers/dhcp_config_parser.h
EXTRA_DIST += parsers/dbaccess_parser.cc
EXTRA_DIST += parsers/dbaccess_parser.h
EXTRA_DIST += parsers/dhcp_parsers.cc
EXTRA_DIST += parsers/dhcp_parsers.h
+EXTRA_DIST += parsers/expiration_config_parser.cc
+EXTRA_DIST += parsers/expiration_config_parser.h
EXTRA_DIST += parsers/host_reservation_parser.cc
EXTRA_DIST += parsers/host_reservation_parser.h
EXTRA_DIST += parsers/host_reservations_list_parser.h
+EXTRA_DIST += parsers/ifaces_config_parser.cc
+EXTRA_DIST += parsers/ifaces_config_parser.h
# Define rule to build logging source files from message file
alloc_engine_messages.h alloc_engine_messages.cc dhcpsrv_messages.h \
libkea_dhcpsrv_la_SOURCES += writable_host_data_source.h
# Configuration parsers
+libkea_dhcpsrv_la_SOURCES += parsers/client_class_def_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/client_class_def_parser.h
libkea_dhcpsrv_la_SOURCES += parsers/dhcp_config_parser.h
libkea_dhcpsrv_la_SOURCES += parsers/dbaccess_parser.cc
libkea_dhcpsrv_la_SOURCES += parsers/dbaccess_parser.h
libkea_dhcpsrv_la_LIBADD = $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/stats/libkea-stats.la
libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/eval/libkea-eval.la
libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
+#include <boost/foreach.hpp>
+
+using namespace isc::data;
+
+/// @file client_class_def.cc
+///
+/// @brief Method implementations for client class definition parsing
+
+namespace isc {
+namespace dhcp {
+
+// ********************** ExpressionParser ****************************
+
+ExpressionParser::ExpressionParser(const std::string&,
+ ExpressionPtr& expression, ParserContextPtr global_context)
+ : local_expression_(ExpressionPtr()), expression_(expression),
+ global_context_(global_context) {
+}
+
+void
+ExpressionParser::build(ConstElementPtr expression_cfg) {
+ // Here is where we would call bison parser with our string
+ // For now we'll just create an expression with a string token
+ // containing the expression text
+ try {
+ if (expression_cfg->getType() != Element::string) {
+ isc_throw(TypeError, "expression value must be a string");
+ }
+ std::string expression_text = expression_cfg->str();
+ TokenPtr dummy(new TokenString(expression_text));
+ local_expression_.reset(new Expression());
+ local_expression_->push_back(dummy);
+ } catch (const std::exception& ex) {
+ // Append position if there is a failure.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << expression_cfg->getPosition() << ")");
+ }
+}
+
+void
+ExpressionParser::commit() {
+ expression_ = local_expression_;
+}
+
+// ********************** ClientClassDefParser ****************************
+
+ClientClassDefParser::ClientClassDefParser(const std::string&,
+ ClientClassDictionaryPtr& class_dictionary, ParserContextPtr global_context)
+ : string_values_(new StringStorage()),
+ match_expr_(ExpressionPtr()),
+ options_(new CfgOption()),
+ class_dictionary_(class_dictionary),
+ global_context_(global_context) {
+}
+
+void
+ClientClassDefParser::build(ConstElementPtr class_def_cfg) {
+ // Parse the elements that make up the option definition.
+ BOOST_FOREACH(ConfigPair param, class_def_cfg->mapValue()) {
+ std::string entry(param.first);
+ ParserPtr parser;
+ if (entry == "name") {
+ StringParserPtr str_parser(new StringParser(entry, string_values_));
+ parser = str_parser;
+ } else if (entry == "test") {
+ ExpressionParserPtr exp_parser(new ExpressionParser(entry,
+ match_expr_,
+ global_context_));
+ parser = exp_parser;
+ } else if (entry == "option-data") {
+ OptionDataListParserPtr opts_parser;
+ uint16_t family = (global_context_->universe_ == Option::V4 ?
+ AF_INET : AF_INET6);
+
+ opts_parser.reset(new OptionDataListParser(entry, options_, family));
+ parser = opts_parser;
+ } else {
+ isc_throw(DhcpConfigError, "invalid parameter '" << entry
+ << "' (" << param.second->getPosition() << ")");
+ }
+
+ parser->build(param.second);
+ parser->commit();
+ }
+
+ std::string name;
+ try {
+ name = string_values_->getParam("name");
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << class_def_cfg->getPosition() << ")");
+ }
+
+ try {
+ // an OptionCollectionPtr
+ class_dictionary_->addClass(name, match_expr_, options_);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what()
+ << " (" << class_def_cfg->getPosition() << ")");
+ }
+}
+
+// ****************** ClientClassDefListParser ************************
+
+ClientClassDefListParser::ClientClassDefListParser(const std::string&,
+ ParserContextPtr
+ global_context)
+ : local_dictionary_(new ClientClassDictionary()),
+ global_context_(global_context) {
+}
+
+void
+ClientClassDefListParser::build(ConstElementPtr client_class_def_list) {
+ if (!client_class_def_list) {
+ isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+ << " client class definitions is NULL ("
+ << client_class_def_list->getPosition() << ")");
+ }
+
+ BOOST_FOREACH(ConstElementPtr client_class_def,
+ client_class_def_list->listValue()) {
+ boost::shared_ptr<ClientClassDefParser>
+ parser(new ClientClassDefParser("client-class-def",
+ local_dictionary_,
+ global_context_));
+ parser->build(client_class_def);
+ }
+}
+
+void
+ClientClassDefListParser::commit() {
+ // CfgMgr::instance().setClientClassConfig(local_dictionary_);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef CLIENT_CLASS_DEF_PARSER_H
+#define CLIENT_CLASS_DEF_PARSER_H
+
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+
+/// @file client_class_def.h
+///
+/// @brief Parsers for client class definitions
+///
+/// These parsers are used to parse lists of client class definitions
+/// into a ClientClassDictionary of ClientClassDef instances. Each
+/// ClientClassDef consists of (at least) a name, an expression, and
+/// option-data. The latter two are currently optional.
+///
+/// There parsers defined are:
+///
+/// ClientClassDefListParser - creates a ClientClassDictionary from a list
+/// of element maps, where each map contains the entries that specifiy a
+/// single class. The names of the classes in the are expected to be
+/// unique. Attempting to define a duplicate class will result in an
+/// DhcpConfigError throw. Invoking @c commit() method causes the dictionary
+/// to be stored by the CfgMgr.
+///
+/// ClientClassDefParser - creates a ClientClassDefinition from an element
+/// map. The elements are as follows:
+///
+/// -# "name" - a string containing the name of the class
+///
+/// -# "test" - a string containing the logical expression used to determine
+/// membership in the class. @todo This is passed into the Bison parser.
+///
+/// -# "option-data" - a list which defines the options that should be
+/// assigned to memebers of the class. This element is optional and parsed
+/// using the @ref dhcp::OptionDataListParser.
+///
+/// ExpressionParser - creates an eval::Expression from a string element,
+/// using the Bison Parser @todo.
+///
+namespace isc {
+namespace dhcp {
+
+/// @brief Parser for a logical expression
+///
+/// This parser creates an instance of an Expression from a string. The
+/// string is passed to the Bison Parser and the resultant Expression is
+/// stored into the ExpressionPtr reference passed into the constructor.
+class ExpressionParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param dummy first argument is ignored, all Parser constructors
+ /// accept string as first argument.
+ /// @param expression variable in which to store the new expression
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ ExpressionParser(const std::string& dummy, ExpressionPtr& expression,
+ ParserContextPtr global_context);
+
+ /// @brief Parses an expression configuration element into an Expression
+ ///
+ /// @param expression_cfg the configuration entry to be parsed.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ void build(isc::data::ConstElementPtr expression_cfg);
+
+ /// @brief Stores the parsed expression to the supplied storage.
+ void commit();
+
+private:
+ /// @brief Local storage for the parsed expression
+ ExpressionPtr local_expression_;
+
+ /// @brief Storage into which the parsed expression should be committed
+ ExpressionPtr& expression_;
+
+ /// @brief Parsing context which contains global values, options and option
+ /// definitions.
+ ParserContextPtr global_context_;
+};
+
+typedef boost::shared_ptr<ExpressionParser> ExpressionParserPtr;
+
+/// @brief Parser for a single client class definition.
+///
+/// This parser creates an instance of a client class definition.
+class ClientClassDefParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param dummy first argument is ignored, all Parser constructors
+ /// accept string as first argument.
+ /// @param class_dictionary dictionary into which the class should be added
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ ClientClassDefParser(const std::string& dummy,
+ ClientClassDictionaryPtr& class_dictionary,
+ ParserContextPtr global_context);
+
+ /// @brief Parses an entry that describes single client class definition.
+ ///
+ /// Attempts to add the new class direclty into the given dictionary.
+ /// This done here to detect duplicate classes prior to commit().
+ /// @param client_class_def a configuration entry to be parsed.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ void build(isc::data::ConstElementPtr client_class_def);
+
+ /// @brief Does nothing.
+ void commit() {};
+
+private:
+
+ /// @brief Storage for class string values.
+ StringStoragePtr string_values_;
+
+ /// @brief Storage for the class match expression
+ ExpressionPtr match_expr_;
+
+ /// @brief Storage for the class options
+ CfgOptionPtr options_;
+
+ /// @brief Dictionary to which the new class should be added
+ ClientClassDictionaryPtr class_dictionary_;
+
+ /// @brief Parsing context which contains global values, options and option
+ /// definitions.
+ ParserContextPtr global_context_;
+};
+
+/// @brief Defines a pointer to a ClientClassDefParser
+typedef boost::shared_ptr<ClientClassDefParser> ClientClassDefParserPtr;
+
+/// @brief Parser for a list of client class definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// client classes and creates ClientClassDef instances for each.
+/// If the parsing done in build() is successful, the collection of
+/// created definitions is given to the @todo CfgMgr.
+class ClientClassDefListParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param dummy first argument is ignored, all Parser constructors
+ /// accept string as first argument.
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ ClientClassDefListParser(const std::string& dummy,
+ ParserContextPtr global_context);
+
+ /// @brief Parse configuration entries.
+ ///
+ /// This function parses configuration entries, creates instances
+ /// of client class definitions and tries to adds them to the a
+ /// local dictionary.
+ ///
+ /// @param class_def_list pointer to an element that holds entries
+ /// for client class definitions.
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void build(isc::data::ConstElementPtr option_def_list);
+
+ /// @brief Commits class definitions to CfgMgr's global storage.
+ void commit();
+
+ /// @brief Local class dictionary to store classes as they are being parsed
+ ClientClassDictionaryPtr local_dictionary_;
+
+ /// Parsing context which contains global values, options and option
+ /// definitions.
+ ParserContextPtr global_context_;
+};
+
+/// @brief Defines a pointer to a ClientClassDefListParser
+typedef boost::shared_ptr<ClientClassDefListParser> ClientClassDefListParserPtr;
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // CLIENT_CLASS_DEF_PARSER_H
};
+typedef boost::shared_ptr<OptionDataListParser> OptionDataListParserPtr;
+
/// @brief Parser for a single option definition.
///
libdhcpsrv_unittests_SOURCES += cfg_subnets6_unittest.cc
libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
libdhcpsrv_unittests_SOURCES += client_class_def_unittest.cc
+libdhcpsrv_unittests_SOURCES += client_class_def_parser_unittest.cc
libdhcpsrv_unittests_SOURCES += csv_lease_file4_unittest.cc
libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc
libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <stdint.h>
+#include <string>
+
+/// @file client_class_def_parser_unittest.cc Unit tests for client class
+/// definition parsing.
+
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test fixture class for @c ClientClassDefParser.
+class ClientClassDefParserTest : public ::testing::Test {
+protected:
+
+ /// @brief Convenience method for parsing a configuration
+ ///
+ /// Attempt to parse a given client class defintion.
+ ///
+ /// @param config - JSON string containing the client class configuration
+ /// to parse.
+ /// @param universe - the universe in which the parsing context should
+ /// occur.
+ /// @return Returns a pointer to class instance created, or NULL if
+ /// for some unforeseen reason it wasn't created in the local dictionary
+ /// @throw indirectly, execptions convertring the JSON text to elements,
+ /// or by the parsing itself are not caught
+ ClientClassDefPtr parseClientClassDef(const std::string& config,
+ Option::Universe universe) {
+ // Create local dicitonary to which the parser add the class.
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ // Create the "global" context for the parser.
+ ParserContextPtr context(new ParserContext(universe));
+
+ // Turn config into elements. This may emit exceptions.
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration. This may emit exceptions.
+ ClientClassDefParser parser("", dictionary, context);
+ parser.build(config_element);
+
+ // If we didn't throw, then return the first and only class
+ ClientClassDefMapPtr classes = dictionary->getClasses();
+ ClientClassDefMap::iterator it = classes->begin();
+ if (it != classes->end()) {
+ return (*it).second;
+ }
+
+ // Return NULL if for some reason the class doesn't exist.
+ return (ClientClassDefPtr());
+ }
+};
+
+/// @brief Test fixture class for @c ClientClassDefListParser.
+class ClientClassDefListParserTest : public ::testing::Test {
+protected:
+
+ /// @brief Convenience method for parsing a list of client class
+ /// definitions.
+ ///
+ /// Attempt to parse a given list of client class defintions into a
+ /// ClientClassDictionary.
+ ///
+ /// @param config - JSON string containing the list of definitions to parse.
+ /// @param universe - the universe in which the parsing context should
+ /// occur.
+ /// @return Returns a pointer to class dictionary created
+ /// @throw indirectly, execptions convertring the JSON text to elements,
+ /// or by the parsing itself are not caught
+ ClientClassDictionaryPtr parseClientClassDefList(const std::string& config,
+ Option::Universe universe)
+ {
+ // Create the "global" context for the parser.
+ ParserContextPtr context(new ParserContext(universe));
+
+ // Turn config into elements. This may emit exceptions.
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration. This may emit exceptions.
+ ClientClassDefListParser parser("", context);
+ parser.build(config_element);
+
+ // Return the parser's local dicationary
+ return (parser.local_dictionary_);
+ }
+};
+
+
+// Verifies basic operation of an ExpressionParser. Until we tie
+// this into the actual Bison parsing there's not much to test.
+TEST(ExpressionParserTest, simpleStringExpression) {
+ ParserContextPtr context(new ParserContext(Option::V4));
+ ExpressionParserPtr parser;
+ ExpressionPtr parsed_expr;
+
+ // Turn config into elements. This may emit exceptions.
+ std::string cfg_txt = "\"astring\"";
+ ElementPtr config_element = Element::fromJSON(cfg_txt);
+
+ // Create the parser.
+ ASSERT_NO_THROW(parser.reset(new ExpressionParser("", parsed_expr,
+ context)));
+ // Expression should parse and commit.
+ ASSERT_NO_THROW(parser->build(config_element));
+ ASSERT_NO_THROW(parser->commit());
+
+ // Parsed expression should exist.
+ ASSERT_TRUE(parsed_expr);
+
+ // Evaluate it. For now the result will be the
+ // expression string as dummy ExpressionParser
+ // just makes an expression of one TokenString
+ // containing the expression string itself.
+ ValueStack vstack;
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
+ (*parsed_expr)[0]->evaluate(*pkt4, vstack);
+ EXPECT_EQ(vstack.top(), "\"astring\"");
+}
+
+// Verifies that given an invalid expression, the Expression parser
+// will throw a DhdpConfigError. Note this is not intended to be
+// an exhaustive test or expression syntax. It is simply to ensure
+// that if the parser fails, it does so properly. For now, since
+// our parser is a dummy parser which only checks that it's given
+// Element::string so send it an integer.
+TEST(ExpressionParserTest, invalidExpression) {
+ ParserContextPtr context(new ParserContext(Option::V4));
+ ExpressionParserPtr parser;
+ ExpressionPtr parsed_expr;
+
+ // Turn config into elements.
+ std::string cfg_txt = "777";
+ ElementPtr config_element = Element::fromJSON(cfg_txt);
+
+ // Create the parser.
+ ASSERT_NO_THROW(parser.reset(new ExpressionParser("", parsed_expr,
+ context)));
+ // Expressionn build() should fail.
+ ASSERT_THROW(parser->build(config_element), DhcpConfigError);
+}
+
+// Verifies you can create a class with only a name
+// Whether that's useful or not, remains to be seen.
+// For now the class allows it.
+TEST_F(ClientClassDefParserTest, nameOnlyValid) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // CfgOption should be a non-null pointer but there
+ // should be no options. Currently there's no good
+ // way to test that there no options.
+ CfgOptionPtr cfg_option;
+ cfg_option = cclass->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ OptionContainerPtr oc;
+ ASSERT_TRUE(oc = cclass->getCfgOption()->getAll("dhcp4"));
+ EXPECT_EQ(0, oc->size());
+
+ // Verify we have no expression.
+ ASSERT_FALSE(cclass->getMatchExpr());
+}
+
+// Verifies you can create a class with a name, expression,
+// but no options.
+TEST_F(ClientClassDefParserTest, nameAndExpressionClass) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"test\": \"vendor-class-identifier == 'MSFT'\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // CfgOption should be a non-null pointer but there
+ // should be no options. Currently there's no good
+ // way to test that there no options.
+ CfgOptionPtr cfg_option;
+ cfg_option = cclass->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ OptionContainerPtr oc;
+ ASSERT_TRUE(oc = cclass->getCfgOption()->getAll("dhcp4"));
+ EXPECT_EQ(0, oc->size());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Evaluate it. For now the result will be the
+ // expression string as dummy ExpressionParser
+ // just makes an expression of one TokenString
+ // containing the expression string itself.
+ ValueStack vstack;
+ Pkt4Ptr pkt4;
+ pkt4.reset(new Pkt4(DHCPDISCOVER, 12345));
+ (*match_expr)[0]->evaluate(*pkt4, vstack);
+ EXPECT_EQ(vstack.top(), "\"vendor-class-identifier == 'MSFT'\"");
+}
+
+// Verifies you can create a class with a name and options,
+// but no expression.
+TEST_F(ClientClassDefParserTest, nameAndOptionsClass) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"code\": 6, \n"
+ " \"space\": \"dhcp4\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // Our one option should exist.
+ OptionDescriptor od = cclass->getCfgOption()->get("dhcp4", 6);
+ ASSERT_TRUE(od.option_);
+ EXPECT_EQ(6, od.option_->getType());
+
+ // Verify we have no expression
+ ASSERT_FALSE(cclass->getMatchExpr());
+}
+
+
+// Verifies you can create a class with a name, expression,
+// and options.
+TEST_F(ClientClassDefParserTest, basicValidClass) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"test\": \"vendor-class-identifier == 'MSFT'\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"code\": 6, \n"
+ " \"space\": \"dhcp4\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // Our one option should exist.
+ OptionDescriptor od = cclass->getCfgOption()->get("dhcp4", 6);
+ ASSERT_TRUE(od.option_);
+ EXPECT_EQ(6, od.option_->getType());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Evaluate it. For now the result will be the
+ // expression string as dummy ExpressionParser
+ // just makes an expression of one TokenString
+ // containing the expression string itself.
+ ValueStack vstack;
+ Pkt4Ptr pkt4;
+ pkt4.reset(new Pkt4(DHCPDISCOVER, 12345));
+ (*match_expr)[0]->evaluate(*pkt4, vstack);
+ EXPECT_EQ(vstack.top(), "\"vendor-class-identifier == 'MSFT'\"");
+}
+
+// Verifies that a class with no name, fails to parse.
+TEST_F(ClientClassDefParserTest, noClassName) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"test\": \"vendor-class-identifier == 'MSFT'\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"code\": 6, \n"
+ " \"space\": \"dhcp4\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
+ DhcpConfigError);
+}
+
+// Verifies that a class with an unknown element, fails to parse.
+TEST_F(ClientClassDefParserTest, unknownElement) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"one\", \n"
+ " \"bogus\": \"bad\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
+ DhcpConfigError);
+}
+
+// Verifies that a class with an invalid expression, fails to parse.
+TEST_F(ClientClassDefParserTest, invalidExpression) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"one\", \n"
+ " \"test\": 777 \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
+ DhcpConfigError);
+}
+
+// Verifies that a class with invalid option-data, fails to parse.
+TEST_F(ClientClassDefParserTest, invalidOptionData) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"one\", \n"
+ " \"option-data\": [ \n"
+ " { \"bogus\": \"bad\" } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
+ DhcpConfigError);
+}
+
+
+// Verifies that a valid list of client classes will parse.
+TEST_F(ClientClassDefListParserTest, simpleValidList) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"three\" \n"
+ " } \n"
+ "] \n";
+
+ // Parsing the list should succeed.
+ ClientClassDictionaryPtr dictionary;
+ ASSERT_NO_THROW(dictionary = parseClientClassDefList(cfg_text, Option::V4));
+ ASSERT_TRUE(dictionary);
+
+ // We should have three classes in the dictionary.
+ EXPECT_EQ(3, dictionary->getClasses()->size());
+
+ // Make sure we can find all three.
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = dictionary->findClass("one"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("one", cclass->getName());
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("two"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("two", cclass->getName());
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("three"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("three", cclass->getName());
+
+ // For good measure, make sure we can't find a non-existant class.
+ ASSERT_NO_THROW(cclass = dictionary->findClass("bogus"));
+ EXPECT_FALSE(cclass);
+}
+
+// Verifies that class list containing a duplicate class entries, fails
+// to parse.
+TEST_F(ClientClassDefListParserTest, duplicateClass) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\" \n"
+ " } \n"
+ "] \n";
+
+ ClientClassDictionaryPtr dictionary;
+ ASSERT_THROW(dictionary = parseClientClassDefList(cfg_text, Option::V4),
+ DhcpConfigError);
+}
+
+// Verifies that a class list containing an invalid class entry, fails to
+// parse.
+TEST_F(ClientClassDefListParserTest, invalidClass) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"bogus\": \"bad\" \n"
+ " } \n"
+ "] \n";
+
+ ClientClassDictionaryPtr dictionary;
+ ASSERT_THROW(dictionary = parseClientClassDefList(cfg_text, Option::V4),
+ DhcpConfigError);
+}
+
+} // end of anonymous namespace
#include <gtest/gtest.h>
+/// @file client_class_def_unittest.cc Unit tests for client class storage
+/// classes.
+
using namespace std;
using namespace isc::dhcp;
using namespace isc::util;
CfgOptionPtr cfg_option;
// Classes cannot have blank names
- ASSERT_THROW(cclass.reset(new ClientClassDef("", expr, cfg_option)), BadValue);
+ ASSERT_THROW(cclass.reset(new ClientClassDef("", expr, cfg_option)),
+ BadValue);
// Verify we can create a class with a name, expression, and no cfg_option
ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
// Now make sure we can find all the options
OptionDescriptor opt_desc = class_options->get("dhcp4",17);
ASSERT_TRUE(opt_desc.option_);
- EXPECT_EQ(100, opt_desc.option_->getType());
+ EXPECT_EQ(17, opt_desc.option_->getType());
opt_desc = class_options->get("isc",101);
ASSERT_TRUE(opt_desc.option_);