Browse Source

[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)
Thomas Markwalder 9 years ago
parent
commit
dd80413a61

+ 10 - 0
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

+ 154 - 0
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 <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

+ 193 - 0
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 <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

+ 2 - 0
src/lib/dhcpsrv/parsers/dhcp_parsers.h

@@ -711,6 +711,8 @@ private:
 
 };
 
+typedef boost::shared_ptr<OptionDataListParser> OptionDataListParserPtr;
+
 
 /// @brief Parser for a single option definition.
 ///

+ 2 - 0
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

+ 459 - 0
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 <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

+ 6 - 2
src/lib/dhcpsrv/tests/client_class_def_unittest.cc

@@ -19,6 +19,9 @@
 
 #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;
@@ -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_);