Commit dd80413a authored by Thomas Markwalder's avatar Thomas Markwalder

[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)
parent ceda1e2c
......@@ -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
......
// 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
// 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
......@@ -711,6 +711,8 @@ private:
};
typedef boost::shared_ptr<OptionDataListParser> OptionDataListParserPtr;
/// @brief Parser for a single option definition.
///
......
......@@ -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
......
// 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);