Browse Source

[4095] Implemented new classes for storing client class definitions

src/lib/dhcpsrv/client_class_def.cc
src/lib/dhcpsrv/client_class_def.h
src/lib/dhcpsrv/tests/client_class_def_unittest.cc
    New files that define client class storage classes and tests

src/lib/dhcp/classify.h
    Amended commentary to reflect how these classes are now being used.

src/lib/dhcp/option.h
    Added typedefs:
        boost::shared_ptr<Option> OptionPtr;
        boost::shared_ptr<OptionCollection> OptionCollectionPtr;

src/lib/dhcpsrv/Makefile.am
    Added entries for new files

src/lib/dhcpsrv/tests/Makefile.am
    Added entries for new file

src/lib/eval/token.h b/src/lib/eval/token.h
    Added typedef:
        boost::shared_ptr<Expression> ExpressionPtr;
Thomas Markwalder 9 years ago
parent
commit
f3e7cffe3d

+ 9 - 6
src/lib/dhcp/classify.h

@@ -20,11 +20,14 @@
 
 /// @file   classify.h
 ///
-/// @brief Defines basic elements of client classification.
+/// @brief Defines elements for storing the names of client classes
 ///
-/// This file defines common elements used for client classification.
-/// It is simple for now, but the complexity involved in client
-/// classification is expected to grow significantly.
+/// This file defines common elements used to track the client classes
+/// that may be associated with a given packet.  In order to minimize the
+/// exposure of the DHCP library to server side concepts such as client
+/// classification the classes herein provide a mechanism to maintain lists
+/// of class names, rather than the classes they represent.  It is the
+/// upper layers' perogative to use these names as they see fit.
 ///
 /// @todo This file should be moved to dhcpsrv eventually as the classification
 /// is server side concept. Client has no notion of classifying incoming server
@@ -36,10 +39,10 @@ namespace isc {
 
 namespace dhcp {
 
-    /// Definition of a single class.
+    /// @brief Defines a single class name.
     typedef std::string ClientClass;
 
-    /// @brief Container for storing client classes
+    /// @brief Container for storing client class names
     ///
     /// Depending on how you look at it, this is either a little more than just
     /// a set of strings or a client classifier that performs access control.

+ 2 - 0
src/lib/dhcp/option.h

@@ -47,6 +47,8 @@ typedef boost::shared_ptr<Option> OptionPtr;
 
 /// A collection of DHCP (v4 or v6) options
 typedef std::multimap<unsigned int, OptionPtr> OptionCollection;
+/// A poitner to an OptionCollection
+typedef boost::shared_ptr<OptionCollection> OptionCollectionPtr;
 
 /// @brief This type describes a callback function to parse options from buffer.
 ///

+ 1 - 0
src/lib/dhcpsrv/Makefile.am

@@ -88,6 +88,7 @@ libkea_dhcpsrv_la_SOURCES += cfg_subnets4.cc cfg_subnets4.h
 libkea_dhcpsrv_la_SOURCES += cfg_subnets6.cc cfg_subnets6.h
 libkea_dhcpsrv_la_SOURCES += cfg_mac_source.cc cfg_mac_source.h
 libkea_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
+libkea_dhcpsrv_la_SOURCES += client_class_def.cc client_class_def.h
 libkea_dhcpsrv_la_SOURCES += csv_lease_file4.cc csv_lease_file4.h
 libkea_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h
 libkea_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h

+ 141 - 0
src/lib/dhcpsrv/client_class_def.cc

@@ -0,0 +1,141 @@
+// 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 "client_class_def.h"
+
+namespace isc {
+namespace dhcp {
+
+//********** ClientClassDef ******************//
+
+ClientClassDef::ClientClassDef(const std::string& name,
+                         const ExpressionPtr& match_expr,
+                         const OptionCollectionPtr& options)
+    : name_(name), match_expr_(match_expr), options_(options) {
+    // Name can't be blank
+    if (name_.empty()) {
+        isc_throw(BadValue, "ClientClassDef name cannot be empty");
+    }
+    // @todo Does it make sense for a class to NOT have match expression?
+
+    // For classes without options, make sure we have an empty collection
+    if (!options_) {
+        options_.reset(new OptionCollection());
+    }
+}
+
+ClientClassDef::~ClientClassDef() {
+}
+
+std::string
+ClientClassDef::getName() const {
+    return (name_);
+}
+
+void
+ClientClassDef::setName(const std::string& name) {
+  name_ = name;
+}
+
+const ExpressionPtr&
+ClientClassDef::getMatchExpr() const {
+    return (match_expr_);
+}
+
+void
+ClientClassDef::setMatchExpr(const ExpressionPtr& match_expr) {
+  match_expr_ = match_expr;
+}
+
+const OptionCollectionPtr&
+ClientClassDef::getOptions() const {
+    return (options_);
+}
+
+void
+ClientClassDef::setOptions(const OptionCollectionPtr& options) {
+    options_ = options;
+}
+
+OptionPtr
+ClientClassDef::findOption(uint16_t option_code) const {
+    if (options_) {
+        isc::dhcp::OptionCollection::iterator it = options_->find(option_code);
+        if (it != options_->end()) {
+            return ((*it).second);
+        }
+    }
+
+    return (OptionPtr());
+}
+
+std::ostream& operator<<(std::ostream& os, const ClientClassDef& x) {
+    os << "ClientClassDef:" << x.getName();
+    return (os);
+}
+
+//********** ClientClassDictionary ******************//
+
+ClientClassDictionary::ClientClassDictionary()
+    : classes_(new ClientClassDefMap()) {
+}
+
+ClientClassDictionary::~ClientClassDictionary() {
+}
+
+void
+ClientClassDictionary::addClass(const std::string& name,
+                          const ExpressionPtr& match_expr,
+                          const OptionCollectionPtr& options) {
+    ClientClassDefPtr cclass(new ClientClassDef(name, match_expr, options));
+    addClass(cclass);
+}
+
+void
+ClientClassDictionary::addClass(ClientClassDefPtr& class_def) {
+    if (!class_def) {
+        isc_throw(BadValue, "ClientClassDictionary::addClass "
+                            " - class definition cannot be null");
+    }
+
+    if (findClass(class_def->getName())) {
+        isc_throw(DuplicateClientClassDef, "Client Class: "
+                  << class_def->getName() << " has already been defined");
+    }
+
+   (*classes_)[class_def->getName()] = class_def;
+}
+
+ClientClassDefPtr
+ClientClassDictionary::findClass(const std::string& name) const {
+    ClientClassDefMap::iterator it = classes_->find(name);
+    if (it != classes_->end()) {
+        return (*it).second;
+    }
+
+    return(ClientClassDefPtr());
+}
+
+void
+ClientClassDictionary::removeClass(const std::string& name) {
+    classes_->erase(name);
+}
+
+const ClientClassDefMapPtr&
+ClientClassDictionary::getClasses() const {
+    return (classes_);
+}
+
+} // namespace isc::dhcp
+} // namespace isc

+ 181 - 0
src/lib/dhcpsrv/client_class_def.h

@@ -0,0 +1,181 @@
+// 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_H
+#define CLIENT_CLASS_DEF_H
+
+#include <dhcp/option.h>
+#include <eval/token.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+
+/// @file client_class_def.h
+///
+/// @brief Defines classes for storing client class definitions
+///
+/// The file defines the class, ClientClassDef, which houses the
+/// information for single client class such as the class name, the
+/// logical expression used to identify members of the class, and options
+/// that may be attributed to class members.
+///
+/// In addition it defines a continer class, ClientClassDictionary, which
+/// is houses class definitions keyed by class name.
+///
+namespace isc {
+namespace dhcp {
+
+/// @brief Error that occurs when an attempt is made to add a duplicate class
+/// to a class dictionary.
+class DuplicateClientClassDef : public isc::Exception {
+public:
+    DuplicateClientClassDef(const char* file, size_t line, const char* what)
+        : isc::Exception(file, line, what) {}
+};
+
+/// @brief Embodies a single client class definition
+class ClientClassDef {
+  public:
+    /// @brief Constructor
+    ///
+    /// @param name Name to assign to this class
+    /// @param match_expr Expression the class will use to determine membership
+    /// @param options Collection of options members should be given
+    ClientClassDef(const std::string& name, const ExpressionPtr& match_expr,
+                const OptionCollectionPtr& options = OptionCollectionPtr());
+
+    /// @brief Destructor
+    virtual ~ClientClassDef();
+
+    /// @brief Fetches the class's name
+    std::string getName() const;
+
+    /// @brief Sets the class's name
+    ///
+    /// @param name the name to assign the class
+    void setName(const std::string& name);
+
+    /// @brief Fetches the class's match expression
+    const ExpressionPtr& getMatchExpr() const;
+
+    /// @brief Sets the class's match expression
+    ///
+    /// @param match_expr the expression to assign the class
+    void setMatchExpr(const ExpressionPtr& match_expr);
+
+    /// @brief Fetches the class's option collection
+    const OptionCollectionPtr& getOptions() const;
+
+    /// @brief Sets the class's option collection
+    ///
+    /// @param options the option collection to assign the class
+    void setOptions(const OptionCollectionPtr& options);
+
+    /// @brief Fetches an option from the class's collection by code
+    ///
+    /// @param option_code Option code value of the desired option
+    /// @return A pointer to the option if found, otherwise an
+    /// empty pointer
+    OptionPtr findOption(uint16_t option_code) const;
+
+    /// @brief Provides a convenient text representation of the class
+    friend  std::ostream& operator<<(std::ostream& os, const ClientClassDef& x);
+
+  private:
+    /// @brief Unique text identifier by which this class is known.
+    std::string name_;
+
+    /// @brief The logical expression which deteremines membership in
+    /// this class.
+    ExpressionPtr match_expr_;
+
+    /// @brief The collection of options members should be given
+    /// Currently this is a multimap, not sure we need/want that complexity
+    OptionCollectionPtr options_;
+};
+
+/// @brief a pointer to an ClientClassDef
+typedef boost::shared_ptr<ClientClassDef> ClientClassDefPtr;
+
+/// @brief Defines a map of ClientClassDefes, keyed by the class name.
+typedef std::map<std::string,ClientClassDefPtr> ClientClassDefMap;
+
+/// @brief Defines a pointer to a ClientClassDictionary
+typedef boost::shared_ptr<ClientClassDefMap> ClientClassDefMapPtr;
+
+/// @brief Maintains a list of ClientClassDefes
+class ClientClassDictionary {
+
+  public:
+    /// @brief Constructor
+    ClientClassDictionary();
+
+    /// @brief Destructor
+    ~ClientClassDictionary();
+
+    /// @brief Adds a new class to the list
+    ///
+    /// @param name Name to assign to this class
+    /// @param match_expr Expression the class will use to determine membership
+    /// @param options Collection of options members should be given
+    ///
+    /// @throw DuplicateClientClassDef if class already exists within the
+    /// dictionary.  See @ref dhcp::ClientClassDef::ClientClassDef() for
+    /// others.
+    void addClass(const std::string& name, const ExpressionPtr& match_expr,
+                  const OptionCollectionPtr& options);
+
+    /// @brief Adds a new class to the list
+    ///
+    /// @param class_def pointer to class definition to add
+    ///
+    /// @throw DuplicateClientClassDef if class already exists within the
+    /// dictionary, BadValue if the pointer is empty.
+    void addClass(ClientClassDefPtr& class_def);
+
+    /// @brief Fetches the class definition for a given class name
+    ///
+    /// @param name the name of the desired class
+    ///
+    /// @return ClientClassDefPtr to the desired class if found, or
+    /// an empty pointer if not.
+    ClientClassDefPtr findClass(const std::string& name) const;
+
+    /// @brief Removes a given class definition from the dictionary
+    ///
+    /// Removes the class defintion from the map if it exists, otherwise
+    /// no harm, no foul.
+    ///
+    /// @param name the name of the class to remove
+    void removeClass(const std::string& name);
+
+    /// @brief Fetches the dictionary's map of classes
+    ///
+    /// @return ClientClassDefMapPtr to the map of classes
+    const ClientClassDefMapPtr& getClasses() const;
+
+  private:
+
+    /// @brief Map of the class definitions
+    ClientClassDefMapPtr classes_;
+
+};
+
+/// @brief Defines a pointer to a ClientClassDictionary
+typedef boost::shared_ptr<ClientClassDictionary> ClientClassDictionaryPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // CLIENT_CLASS_DEF_H

+ 1 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -75,6 +75,7 @@ libdhcpsrv_unittests_SOURCES += cfg_rsoo_unittest.cc
 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 += csv_lease_file4_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc

+ 172 - 0
src/lib/dhcpsrv/tests/client_class_def_unittest.cc

@@ -0,0 +1,172 @@
+// 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 <dhcpsrv/client_class_def.h>
+#include <exceptions/exceptions.h>
+#include <boost/scoped_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc;
+
+namespace {
+
+TEST(ClientClassDef, construction) {
+    boost::scoped_ptr<ClientClassDef> cclass;
+
+    std::string name = "class1";
+    ExpressionPtr expr;
+    OptionCollectionPtr options;
+
+    // Classes cannot have blank names
+    ASSERT_THROW(cclass.reset(new ClientClassDef("", expr, options)), BadValue);
+
+    // Verify we can create a class with a name, expression, and no options
+    ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
+    EXPECT_EQ(name, cclass->getName());
+    ASSERT_FALSE(cclass->getMatchExpr());
+
+    // Verify we get an empty collection of options
+    options = cclass->getOptions();
+    ASSERT_TRUE(options);
+    EXPECT_EQ(0, options->size());
+}
+
+TEST(ClientClassDef, optionsBasics) {
+    boost::scoped_ptr<ClientClassDef> cclass;
+
+    std::string name = "class1";
+    ExpressionPtr expr;
+    OptionCollectionPtr options;
+    OptionPtr opt;
+
+    // First construct the class with empty option pointer
+    ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr, options)));
+
+    // We should get back a collection with no entries,
+    // not an empty collection pointer
+    options = cclass->getOptions();
+    ASSERT_TRUE(options);
+    EXPECT_EQ(0, options->size());
+
+    // We should not be able find an option
+    opt = cclass->findOption(17);
+    ASSERT_FALSE(opt);
+
+    // Create an option container with two options
+    options.reset(new OptionCollection());
+    EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 17)));
+    options->insert(make_pair(17, opt));
+    EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 18)));
+    options->insert(make_pair(18, opt));
+
+    // Now remake the client class with options
+    ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr, options)));
+
+    options = cclass->getOptions();
+    ASSERT_TRUE(options);
+    EXPECT_EQ(2, options->size());
+
+    // We should be able to find option 17
+    opt = cclass->findOption(17);
+    ASSERT_TRUE(opt);
+    EXPECT_EQ(17, opt->getType());
+
+    // We should be able to find option 18
+    opt = cclass->findOption(18);
+    ASSERT_TRUE(opt);
+    EXPECT_EQ(18, opt->getType());
+
+    // We should not be able to find option 90
+    opt = cclass->findOption(90);
+    ASSERT_FALSE(opt);
+}
+
+TEST(ClientClassDictionary, basics) {
+    ClientClassDictionaryPtr dictionary;
+    ClientClassDefPtr cclass;
+    ExpressionPtr expr;
+    OptionCollectionPtr options;
+
+    // Verify constructor doesn't throw
+    ASSERT_NO_THROW(dictionary.reset(new ClientClassDictionary()));
+
+    // Verify we can fetch a pointer the map of classes and
+    // that we start with no classes defined
+    const ClientClassDefMapPtr classes = dictionary->getClasses();
+    ASSERT_TRUE(classes);
+    EXPECT_EQ(0, classes->size());
+
+    // Verify that we can add classes with both addClass variants
+    // First addClass(name, expression, options)
+    ASSERT_NO_THROW(dictionary->addClass("cc1", expr, options));
+    ASSERT_NO_THROW(dictionary->addClass("cc2", expr, options));
+
+    // Verify duplicate add attempt throws
+    ASSERT_THROW(dictionary->addClass("cc2", expr, options),
+                 DuplicateClientClassDef);
+
+    // Verify that you cannot add a class with no name.
+    ASSERT_THROW(dictionary->addClass("", expr, options), BadValue);
+
+    // Now with addClass(class pointer)
+    ASSERT_NO_THROW(cclass.reset(new ClientClassDef("cc3", expr, options)));
+    ASSERT_NO_THROW(dictionary->addClass(cclass));
+
+    // Verify duplicate add attempt throws
+    ASSERT_THROW(dictionary->addClass(cclass), DuplicateClientClassDef);
+
+    // Verify that you cannot add emtpy class pointer
+    cclass.reset();
+    ASSERT_THROW(dictionary->addClass(cclass), BadValue);
+
+    // Map should show 3 entries.
+    EXPECT_EQ(3, classes->size());
+
+    // Verify we can find them all.
+    ASSERT_NO_THROW(cclass = dictionary->findClass("cc1"));
+    ASSERT_TRUE(cclass);
+    EXPECT_EQ("cc1", cclass->getName());
+
+    ASSERT_NO_THROW(cclass = dictionary->findClass("cc2"));
+    ASSERT_TRUE(cclass);
+    EXPECT_EQ("cc2", cclass->getName());
+
+    ASSERT_NO_THROW(cclass = dictionary->findClass("cc3"));
+    ASSERT_TRUE(cclass);
+    EXPECT_EQ("cc3", cclass->getName());
+
+    // Verify the looking for non-existant returns empty pointer
+    ASSERT_NO_THROW(cclass = dictionary->findClass("bogus"));
+    EXPECT_FALSE(cclass);
+
+    // Verify that we can remove a class
+    ASSERT_NO_THROW(dictionary->removeClass("cc3"));
+    EXPECT_EQ(2, classes->size());
+
+    // Shouldn't be able to find anymore
+    ASSERT_NO_THROW(cclass = dictionary->findClass("cc3"));
+    EXPECT_FALSE(cclass);
+
+    // Verify that we can attempt to remove a non-existant class
+    // without harm.
+    ASSERT_NO_THROW(dictionary->removeClass("cc3"));
+    EXPECT_EQ(2, classes->size());
+}
+
+} // end of anonymous namespace

+ 2 - 0
src/lib/eval/token.h

@@ -35,6 +35,8 @@ typedef boost::shared_ptr<Token> TokenPtr;
 /// [2] = == operator (TokenEqual object)
 typedef std::vector<TokenPtr> Expression;
 
+typedef boost::shared_ptr<Expression> ExpressionPtr;
+
 /// Evaluated values are stored as a stack of strings
 typedef std::stack<std::string> ValueStack;