From: Thomas Markwalder Date: Mon, 16 Nov 2015 18:27:16 +0000 (-0500) Subject: [4096] Added parsers for client class definitions X-Git-Tag: trac4097a_base~1^2~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dd80413a61e11fda1cb41f7bfd62aeae56325a28;p=thirdparty%2Fkea.git [4096] Added parsers for client class definitions New Files: parsers/client_class_def_parser.cc parsers/client_class_def_parser.h tests/client_class_def_parser_unittest.cc src/lib/dhcpsrv/parsers/Makefile.am Added entries for new files Added EXTRA_DIST entries for several files that were missing. src/lib/dhcpsrv/parsers/dhcp_parsers.h Added typedef for OptionDataListParserPtr src/lib/dhcpsrv/tests/Makefile.am Added entries for new unitest file src/lib/dhcpsrv/tests/client_class_def_unittest.cc Fixed broken unit test TEST(ClientClassDef, cfgOptionBasics) --- diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index e11aa31bd1..b5ff75abae 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -26,13 +26,20 @@ AM_CXXFLAGS = $(KEA_CXXFLAGS) # 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 \ @@ -133,6 +140,8 @@ libkea_dhcpsrv_la_SOURCES += utils.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 @@ -156,6 +165,7 @@ libkea_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) 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 diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc new file mode 100644 index 0000000000..2c2df8b686 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc @@ -0,0 +1,154 @@ +// 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 +#include +#include +#include +#include + +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 + 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 diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.h b/src/lib/dhcpsrv/parsers/client_class_def_parser.h new file mode 100644 index 0000000000..21558ce352 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.h @@ -0,0 +1,193 @@ +// 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 +#include + +/// @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 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 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 ClientClassDefListParserPtr; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // CLIENT_CLASS_DEF_PARSER_H diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.h b/src/lib/dhcpsrv/parsers/dhcp_parsers.h index 5eb5aa863f..6a8da069dc 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.h +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.h @@ -711,6 +711,8 @@ private: }; +typedef boost::shared_ptr OptionDataListParserPtr; + /// @brief Parser for a single option definition. /// diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index 11a1f3012f..1878c8d36c 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -76,6 +76,7 @@ libdhcpsrv_unittests_SOURCES += cfg_subnets4_unittest.cc 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 @@ -147,6 +148,7 @@ libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la 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 diff --git a/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc b/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc new file mode 100644 index 0000000000..c1e6d6186b --- /dev/null +++ b/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc @@ -0,0 +1,459 @@ +// 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 + +#include +#include +#include +#include +#include +#include +#include + +/// @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 diff --git a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc index 8aaa713116..7770b3bb44 100644 --- a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc +++ b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc @@ -19,6 +19,9 @@ #include +/// @file client_class_def_unittest.cc Unit tests for client class storage +/// classes. + using namespace std; using namespace isc::dhcp; using namespace isc::util; @@ -35,7 +38,8 @@ TEST(ClientClassDef, construction) { 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))); @@ -87,7 +91,7 @@ TEST(ClientClassDef, cfgOptionBasics) { // 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_);