Browse Source

[3186] Added support for loading user data from file

Added UserFile derivation of UserDataSource which allows the UserRegistry
to be populated from a JSON formatted file.
Thomas Markwalder 11 years ago
parent
commit
14918661a1

+ 1 - 0
configure.ac

@@ -1472,6 +1472,7 @@ AC_OUTPUT([doc/version.ent
            src/bin/d2/spec_config.h.pre
            src/bin/d2/spec_config.h.pre
            src/bin/d2/tests/test_data_files_config.h
            src/bin/d2/tests/test_data_files_config.h
            src/bin/tests/process_rename_test.py
            src/bin/tests/process_rename_test.py
+           src/hooks/dhcp/user_chk/tests/test_data_files_config.h
            src/lib/config/tests/data_def_unittests_config.h
            src/lib/config/tests/data_def_unittests_config.h
            src/lib/dhcpsrv/tests/test_libraries.h
            src/lib/dhcpsrv/tests/test_libraries.h
            src/lib/python/isc/config/tests/config_test
            src/lib/python/isc/config/tests/config_test

+ 2 - 1
src/hooks/dhcp/user_chk/Makefile.am

@@ -32,8 +32,9 @@ CLEANFILES = *.gcno *.gcda
 lib_LTLIBRARIES = libdhcp_user_chk.la
 lib_LTLIBRARIES = libdhcp_user_chk.la
 libdhcp_user_chk_la_SOURCES  =
 libdhcp_user_chk_la_SOURCES  =
 libdhcp_user_chk_la_SOURCES += load_unload.cc
 libdhcp_user_chk_la_SOURCES += load_unload.cc
-libdhcp_user_chk_la_SOURCES += user.cc user.h 
+libdhcp_user_chk_la_SOURCES += user.cc user.h
 libdhcp_user_chk_la_SOURCES += user_data_source.cc user_data_source.h
 libdhcp_user_chk_la_SOURCES += user_data_source.cc user_data_source.h
+libdhcp_user_chk_la_SOURCES += user_file.cc user_file.h
 libdhcp_user_chk_la_SOURCES += user_registry.cc user_registry.h
 libdhcp_user_chk_la_SOURCES += user_registry.cc user_registry.h
 libdhcp_user_chk_la_SOURCES += version.cc
 libdhcp_user_chk_la_SOURCES += version.cc
 
 

+ 3 - 0
src/hooks/dhcp/user_chk/tests/Makefile.am

@@ -32,11 +32,13 @@ TESTS += libdhcp_user_chk_unittests
 libdhcp_user_chk_unittests_SOURCES  = 
 libdhcp_user_chk_unittests_SOURCES  = 
 libdhcp_user_chk_unittests_SOURCES += ../user.cc ../user.h
 libdhcp_user_chk_unittests_SOURCES += ../user.cc ../user.h
 libdhcp_user_chk_unittests_SOURCES += ../user_data_source.cc ../user_data_source.h
 libdhcp_user_chk_unittests_SOURCES += ../user_data_source.cc ../user_data_source.h
+libdhcp_user_chk_unittests_SOURCES += ../user_file.cc ../user_file.h
 libdhcp_user_chk_unittests_SOURCES += ../user_registry.cc ../user_registry.h
 libdhcp_user_chk_unittests_SOURCES += ../user_registry.cc ../user_registry.h
 libdhcp_user_chk_unittests_SOURCES += run_unittests.cc
 libdhcp_user_chk_unittests_SOURCES += run_unittests.cc
 libdhcp_user_chk_unittests_SOURCES += userid_unittests.cc
 libdhcp_user_chk_unittests_SOURCES += userid_unittests.cc
 libdhcp_user_chk_unittests_SOURCES += user_unittests.cc
 libdhcp_user_chk_unittests_SOURCES += user_unittests.cc
 libdhcp_user_chk_unittests_SOURCES += user_registry_unittests.cc
 libdhcp_user_chk_unittests_SOURCES += user_registry_unittests.cc
+libdhcp_user_chk_unittests_SOURCES += user_file_unittests.cc
 
 
 libdhcp_user_chk_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 libdhcp_user_chk_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 
 
@@ -54,6 +56,7 @@ libdhcp_user_chk_unittests_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
 libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 #libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/hooks/dhcp/user_chk/libdhcp_user_chk.la
 #libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/hooks/dhcp/user_chk/libdhcp_user_chk.la
 libdhcp_user_chk_unittests_LDADD += ${BOTAN_LIBS} ${BOTAN_RPATH}
 libdhcp_user_chk_unittests_LDADD += ${BOTAN_LIBS} ${BOTAN_RPATH}
 libdhcp_user_chk_unittests_LDADD += $(GTEST_LDADD)
 libdhcp_user_chk_unittests_LDADD += $(GTEST_LDADD)

+ 16 - 0
src/hooks/dhcp/user_chk/tests/test_data_files_config.h.in

@@ -0,0 +1,16 @@
+// Copyright (C) 2009  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.
+
+/// @brief Path to local dir so tests can locate test data files 
+#define USER_CHK_TEST_DIR "@abs_top_srcdir@/src/hooks/dhcp/user_chk/tests"

+ 3 - 0
src/hooks/dhcp/user_chk/tests/test_users_1.txt

@@ -0,0 +1,3 @@
+{ "type" : "HW_ADDR", "id" : "01AC00F03344", "opt1" : "true" }
+{ "type" : "CLIENT_ID", "id" : "0899e0cc0707", "opt1" : "false" }
+{ "type" : "DUID", "id" : "225060de0a0b", "opt1" : "true" }

+ 111 - 0
src/hooks/dhcp/user_chk/tests/user_file_unittests.cc

@@ -0,0 +1,111 @@
+// Copyright (C) 2013  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 <exceptions/exceptions.h>
+#include <test_data_files_config.h>
+#include <user_file.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+
+namespace {
+
+std::string testFilePath(const std::string& name) {
+    return (std::string(USER_CHK_TEST_DIR) + "/" + name);
+}
+
+TEST(UserFile, construction) {
+    ASSERT_THROW(UserFile(""), UserFileError);
+
+    ASSERT_NO_THROW(UserFile("someName"));
+}
+
+TEST(UserFile, openFile) {
+    UserFilePtr user_file;
+
+    // Construct a user file that refers to a non existant file.
+    ASSERT_NO_THROW(user_file.reset(new UserFile("NoSuchFile")));
+    EXPECT_FALSE(user_file->isOpen());
+
+    // Verify a non-existant file fails to open
+    ASSERT_THROW(user_file->open(), UserFileError);
+    EXPECT_FALSE(user_file->isOpen());
+
+    // Construct a user file that should exist.
+    ASSERT_NO_THROW(user_file.reset(new
+                                    UserFile(testFilePath("test_users_1.txt"))));
+    EXPECT_FALSE(user_file->isOpen());
+
+    // Verify that we can open it.
+    ASSERT_NO_THROW(user_file->open());
+    EXPECT_TRUE(user_file->isOpen());
+
+    // Verify that we cannot open an already open file.
+    ASSERT_THROW(user_file->open(), UserFileError);
+
+    // Verifyt we can close it.
+    ASSERT_NO_THROW(user_file->close());
+    EXPECT_FALSE(user_file->isOpen());
+
+    // Verify that we can reopen it.
+    ASSERT_NO_THROW(user_file->open());
+    EXPECT_TRUE(user_file->isOpen());
+}
+
+TEST(UserFile, readFile) {
+    UserFilePtr user_file;
+
+    // Construct an open a known file.
+    ASSERT_NO_THROW(user_file.reset(new
+                                    UserFile(testFilePath("test_users_1.txt"))));
+    ASSERT_NO_THROW(user_file->open());
+    EXPECT_TRUE(user_file->isOpen());
+
+    UserPtr user;
+    int i = 0;
+    do {
+        ASSERT_NO_THROW(user = user_file->readNextUser());
+        switch (i++) {
+            case 0:
+                EXPECT_EQ(UserId::HW_ADDRESS, user->getUserId().getType());
+                EXPECT_EQ("01ac00f03344", user->getUserId().toText());
+                EXPECT_EQ("true", user->getProperty("opt1"));
+                break;
+            case 1:
+                EXPECT_EQ(UserId::CLIENT_ID, user->getUserId().getType());
+                EXPECT_EQ("0899e0cc0707", user->getUserId().toText());
+                EXPECT_EQ("false", user->getProperty("opt1"));
+                break;
+            case 2:
+                EXPECT_EQ(UserId::DUID, user->getUserId().getType());
+                EXPECT_EQ("225060de0a0b", user->getUserId().toText());
+                EXPECT_EQ("true", user->getProperty("opt1"));
+                break;
+            default:
+                // this is an error, TBD
+                break;
+        }
+    } while (user);
+
+
+    ASSERT_NO_THROW(user_file->close());
+}
+
+
+
+} // end of anonymous namespace

+ 67 - 6
src/hooks/dhcp/user_chk/tests/user_registry_unittests.cc

@@ -15,6 +15,8 @@
 #include <dhcp/hwaddr.h>
 #include <dhcp/hwaddr.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <user_registry.h>
 #include <user_registry.h>
+#include <user_file.h>
+#include <test_data_files_config.h>
 
 
 #include <boost/function.hpp>
 #include <boost/function.hpp>
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
@@ -25,6 +27,10 @@ using namespace std;
 
 
 namespace {
 namespace {
 
 
+std::string testFilePath(const std::string& name) {
+    return (std::string(USER_CHK_TEST_DIR) + "/" + name);
+}
+
 TEST(UserRegistry, constructor) {
 TEST(UserRegistry, constructor) {
     UserRegistryPtr reg;
     UserRegistryPtr reg;
     ASSERT_NO_THROW(reg.reset(new UserRegistry()));
     ASSERT_NO_THROW(reg.reset(new UserRegistry()));
@@ -41,7 +47,7 @@ TEST(UserRegistry, userBasics) {
 
 
     UserPtr user, user2;
     UserPtr user, user2;
     // Verify that a blank user cannot be added.
     // Verify that a blank user cannot be added.
-    ASSERT_THROW(reg->addUser(user), isc::BadValue);
+    ASSERT_THROW(reg->addUser(user), UserRegistryError);
 
 
     // Make new users.
     // Make new users.
     ASSERT_NO_THROW(user.reset(new User(*id)));
     ASSERT_NO_THROW(user.reset(new User(*id)));
@@ -54,7 +60,7 @@ TEST(UserRegistry, userBasics) {
     UserPtr found_user;
     UserPtr found_user;
     ASSERT_NO_THROW(found_user = reg->findUser(*id));
     ASSERT_NO_THROW(found_user = reg->findUser(*id));
     EXPECT_TRUE(found_user);
     EXPECT_TRUE(found_user);
-    EXPECT_EQ(*id, found_user->getUserId());
+    EXPECT_EQ(found_user->getUserId(), *id);
 
 
     // Verify user not added cannot be found.
     // Verify user not added cannot be found.
     ASSERT_NO_THROW(found_user = reg->findUser(*id2));
     ASSERT_NO_THROW(found_user = reg->findUser(*id2));
@@ -78,14 +84,14 @@ TEST(UserRegistry, findByHWAddr) {
     ASSERT_NO_THROW(reg->addUser(user));
     ASSERT_NO_THROW(reg->addUser(user));
 
 
     // Make a HWAddr instance using the same id value.
     // Make a HWAddr instance using the same id value.
-    isc::dhcp::HWAddr hwaddr(user->getUserId().getId(), 
+    isc::dhcp::HWAddr hwaddr(user->getUserId().getId(),
                              isc::dhcp::HTYPE_ETHER);
                              isc::dhcp::HTYPE_ETHER);
 
 
     // Verify user can be found by HWAddr.
     // Verify user can be found by HWAddr.
     UserPtr found_user;
     UserPtr found_user;
     ASSERT_NO_THROW(found_user = reg->findUser(hwaddr));
     ASSERT_NO_THROW(found_user = reg->findUser(hwaddr));
     EXPECT_TRUE(found_user);
     EXPECT_TRUE(found_user);
-    EXPECT_EQ(user->getUserId(), found_user->getUserId());
+    EXPECT_EQ(found_user->getUserId(), user->getUserId());
 }
 }
 
 
 TEST(UserRegistry, findByDUID) {
 TEST(UserRegistry, findByDUID) {
@@ -106,7 +112,7 @@ TEST(UserRegistry, findByDUID) {
     UserPtr found_user;
     UserPtr found_user;
     ASSERT_NO_THROW(found_user = reg->findUser(duid));
     ASSERT_NO_THROW(found_user = reg->findUser(duid));
     EXPECT_TRUE(found_user);
     EXPECT_TRUE(found_user);
-    EXPECT_EQ(user->getUserId(), found_user->getUserId());
+    EXPECT_EQ(found_user->getUserId(), user->getUserId());
 }
 }
 
 
 TEST(UserRegistry, findByClientId) {
 TEST(UserRegistry, findByClientId) {
@@ -127,7 +133,62 @@ TEST(UserRegistry, findByClientId) {
     UserPtr found_user;
     UserPtr found_user;
     ASSERT_NO_THROW(found_user = reg->findUser(client_id));
     ASSERT_NO_THROW(found_user = reg->findUser(client_id));
     EXPECT_TRUE(found_user);
     EXPECT_TRUE(found_user);
-    EXPECT_EQ(user->getUserId(), found_user->getUserId());
+    EXPECT_EQ(found_user->getUserId(), user->getUserId());
+}
+
+TEST(UserRegistry, oneOfEach) {
+    UserRegistryPtr reg;
+    ASSERT_NO_THROW(reg.reset(new UserRegistry()));
+
+    // Make user ids.
+    UserIdPtr idh, idc, idd;
+    ASSERT_NO_THROW(idh.reset(new UserId(UserId::HW_ADDRESS, "01010101")));
+    ASSERT_NO_THROW(idc.reset(new UserId(UserId::CLIENT_ID, "02020202")));
+    ASSERT_NO_THROW(idd.reset(new UserId(UserId::DUID, "03030303")));
+
+    UserPtr user;
+    user.reset(new User(*idh));
+    ASSERT_NO_THROW(reg->addUser(user));
+
+    user.reset(new User(*idc));
+    ASSERT_NO_THROW(reg->addUser(user));
+    user.reset(new User(*idd));
+    ASSERT_NO_THROW(reg->addUser(user));
+
+    UserPtr found_user;
+    ASSERT_NO_THROW(found_user = reg->findUser(*idh));
+    ASSERT_NO_THROW(found_user = reg->findUser(*idc));
+    ASSERT_NO_THROW(found_user = reg->findUser(*idd));
+}
+
+TEST(UserRegistry, userFileTest) {
+    UserRegistryPtr reg;
+    ASSERT_NO_THROW(reg.reset(new UserRegistry()));
+
+    // Create the data source.
+    UserDataSourcePtr user_file;
+    ASSERT_NO_THROW(user_file.reset(new
+                                    UserFile(testFilePath("test_users_1.txt"))));
+
+    // Set the registry's data source and refresh the registry.
+    ASSERT_NO_THROW(reg->setSource(user_file));
+    //ASSERT_NO_THROW(reg->refresh());
+    (reg->refresh());
+
+    // Verify we can find all the expected users.
+    UserPtr found_user;
+    UserIdPtr id;
+    ASSERT_NO_THROW(id.reset(new UserId(UserId::HW_ADDRESS, "01ac00f03344")));
+    ASSERT_NO_THROW(found_user = reg->findUser(*id));
+    EXPECT_TRUE(found_user);
+
+    ASSERT_NO_THROW(id.reset(new UserId(UserId::CLIENT_ID, "0899e0cc0707")));
+    ASSERT_NO_THROW(found_user = reg->findUser(*id));
+    EXPECT_TRUE(found_user);
+
+    ASSERT_NO_THROW(id.reset(new UserId(UserId::DUID, "225060de0a0b")));
+    ASSERT_NO_THROW(found_user = reg->findUser(*id));
+    EXPECT_TRUE(found_user);
 }
 }
 
 
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 9 - 9
src/hooks/dhcp/user_chk/tests/user_unittests.cc

@@ -35,28 +35,28 @@ TEST(UserTest, construction) {
     ASSERT_NO_THROW(user.reset(new User(*id)));
     ASSERT_NO_THROW(user.reset(new User(*id)));
 
 
     // Verify construction from a type and an address vector.
     // Verify construction from a type and an address vector.
-    ASSERT_NO_THROW(user.reset(new User(UserId::HW_ADDRESS, id->getId())));   
-    ASSERT_NO_THROW(user.reset(new User(UserId::DUID, id->getId())));   
-    ASSERT_NO_THROW(user.reset(new User(UserId::CLIENT_ID, id->getId())));   
+    ASSERT_NO_THROW(user.reset(new User(UserId::HW_ADDRESS, id->getId())));
+    ASSERT_NO_THROW(user.reset(new User(UserId::DUID, id->getId())));
+    ASSERT_NO_THROW(user.reset(new User(UserId::CLIENT_ID, id->getId())));
 
 
     // Verify construction from a type and an address string.
     // Verify construction from a type and an address string.
-    ASSERT_NO_THROW(user.reset(new User(UserId::HW_ADDRESS, test_address)));   
-    ASSERT_NO_THROW(user.reset(new User(UserId::DUID, test_address)));   
-    ASSERT_NO_THROW(user.reset(new User(UserId::CLIENT_ID, test_address)));   
+    ASSERT_NO_THROW(user.reset(new User(UserId::HW_ADDRESS, test_address)));
+    ASSERT_NO_THROW(user.reset(new User(UserId::DUID, test_address)));
+    ASSERT_NO_THROW(user.reset(new User(UserId::CLIENT_ID, test_address)));
 }
 }
 
 
 TEST(UserTest, properties) {
 TEST(UserTest, properties) {
     UserPtr user;
     UserPtr user;
-    ASSERT_NO_THROW(user.reset(new User(UserId::DUID, "01020304050607")));   
+    ASSERT_NO_THROW(user.reset(new User(UserId::DUID, "01020304050607")));
 
 
     std::string value = "";
     std::string value = "";
     EXPECT_NO_THROW(user->setProperty("one","1"));
     EXPECT_NO_THROW(user->setProperty("one","1"));
     EXPECT_NO_THROW(value = user->getProperty("one"));
     EXPECT_NO_THROW(value = user->getProperty("one"));
-    EXPECT_EQ(value, "1");
+    EXPECT_EQ("1", value);
 
 
     EXPECT_NO_THROW(user->setProperty("one","1.0"));
     EXPECT_NO_THROW(user->setProperty("one","1.0"));
     EXPECT_NO_THROW(value = user->getProperty("one"));
     EXPECT_NO_THROW(value = user->getProperty("one"));
-    EXPECT_EQ(value, "1.0");
+    EXPECT_EQ("1.0", value);
 
 
     EXPECT_NO_THROW(user->delProperty("one"));
     EXPECT_NO_THROW(user->delProperty("one"));
     EXPECT_NO_THROW(value = user->getProperty("one"));
     EXPECT_NO_THROW(value = user->getProperty("one"));

+ 22 - 4
src/hooks/dhcp/user_chk/tests/userid_unittests.cc

@@ -147,8 +147,9 @@ TEST(UserIdTest, client_id_type) {
     EXPECT_FALSE(*id < *id2);
     EXPECT_FALSE(*id < *id2);
 }
 }
 
 
-TEST(UserIdTest, type_compare) {
+TEST(UserIdTest, mixed_type_compare) {
     UserIdPtr hw, duid, client;
     UserIdPtr hw, duid, client;
+    // Address are the same
     ASSERT_NO_THROW(hw.reset(new UserId(UserId::HW_ADDRESS,
     ASSERT_NO_THROW(hw.reset(new UserId(UserId::HW_ADDRESS,
                                         "01FF02AC030B0709")));
                                         "01FF02AC030B0709")));
     ASSERT_NO_THROW(duid.reset(new UserId(UserId::DUID,
     ASSERT_NO_THROW(duid.reset(new UserId(UserId::DUID,
@@ -157,9 +158,26 @@ TEST(UserIdTest, type_compare) {
                                             "01FF02AC030B0709")));
                                             "01FF02AC030B0709")));
 
 
     // Verify that UserIdType influences logical comparators.
     // Verify that UserIdType influences logical comparators.
-    EXPECT_NE(*hw, *duid);
-    EXPECT_NE(*hw , *client);
-    EXPECT_NE(*duid, *client);
+    EXPECT_TRUE(*hw < *duid);
+    EXPECT_TRUE(*duid < *client);
+
+
+    // Now use different addresses.
+    ASSERT_NO_THROW(hw.reset(new UserId(UserId::HW_ADDRESS,
+                                        "01010101")));
+    ASSERT_NO_THROW(duid.reset(new UserId(UserId::DUID,
+                                         "02020202")));
+    ASSERT_NO_THROW(client.reset(new UserId(UserId::CLIENT_ID,
+                                            "03030303")));
+
+    EXPECT_FALSE(*hw == *duid);
+    EXPECT_TRUE(*hw != *duid);
+    EXPECT_TRUE(*hw < *duid);
+
+    EXPECT_FALSE(*duid == *client);
+    EXPECT_TRUE(*duid != *client);
+    EXPECT_TRUE(*duid < *client);
 }
 }
 
 
+
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 16 - 5
src/hooks/dhcp/user_chk/user.cc

@@ -112,7 +112,8 @@ UserId::operator !=(const UserId & other) const {
 
 
 bool
 bool
 UserId::operator <(const UserId & other) const {
 UserId::operator <(const UserId & other) const {
-    return ((this->id_type_ == other.id_type_) && (this->id_ < other.id_));
+    return ((this->id_type_ < other.id_type_) ||
+            ((this->id_type_ == other.id_type_) && (this->id_ < other.id_)));
 }
 }
 
 
 void
 void
@@ -160,11 +161,11 @@ UserId::lookupTypeStr(UserIdType type) {
 
 
 UserId::UserIdType
 UserId::UserIdType
 UserId::lookupType(const std::string& type_str) {
 UserId::lookupType(const std::string& type_str) {
-    if (type_str == HW_ADDRESS_STR) {
+    if (type_str.compare(HW_ADDRESS_STR) == 0) {
         return (HW_ADDRESS);
         return (HW_ADDRESS);
-    } else if (type_str == DUID_STR) {
+    } else if (type_str.compare(DUID_STR) == 0) {
         return (DUID);
         return (DUID);
-    } else if (type_str == CLIENT_ID_STR) {
+    } else if (type_str.compare(CLIENT_ID_STR) == 0) {
         return (CLIENT_ID);
         return (CLIENT_ID);
     }
     }
 
 
@@ -187,13 +188,23 @@ User::User(UserId::UserIdType id_type, const std::vector<uint8_t>& id)
     : user_id_(id_type, id) {
     : user_id_(id_type, id) {
 }
 }
 
 
-User::User(UserId::UserIdType id_type, const std::string& id_str) 
+User::User(UserId::UserIdType id_type, const std::string& id_str)
     : user_id_(id_type, id_str) {
     : user_id_(id_type, id_str) {
 }
 }
 
 
 User::~User() {
 User::~User() {
 }
 }
 
 
+const PropertyMap&
+User::getProperties() const {
+    return (properties_);
+}
+
+void
+User::setProperties(const PropertyMap& properties) {
+    properties_ = properties;
+}
+
 void User::setProperty(const std::string& name, const std::string& value) {
 void User::setProperty(const std::string& name, const std::string& value) {
     if (name.empty()) {
     if (name.empty()) {
         isc_throw (isc::BadValue, "User property name cannot be blank");
         isc_throw (isc::BadValue, "User property name cannot be blank");

+ 13 - 7
src/hooks/dhcp/user_chk/user.h

@@ -22,15 +22,17 @@
 
 
 class UserId {
 class UserId {
 public:
 public:
+    // Use explicit value to ensure consistent numeric ordering for key
+    // comparisions.
     enum UserIdType {
     enum UserIdType {
-        HW_ADDRESS,
-        DUID,
-        CLIENT_ID
+        HW_ADDRESS = 0,
+        DUID = 1,
+        CLIENT_ID = 2
     };
     };
 
 
     static const char* HW_ADDRESS_STR;
     static const char* HW_ADDRESS_STR;
     static const char* DUID_STR;
     static const char* DUID_STR;
-    static const char* CLIENT_ID_STR; 
+    static const char* CLIENT_ID_STR;
 
 
     UserId(UserIdType id_type, const std::vector<uint8_t>& id);
     UserId(UserIdType id_type, const std::vector<uint8_t>& id);
 
 
@@ -41,7 +43,7 @@ public:
     /// @brief Returns a const reference to the actual id value
     /// @brief Returns a const reference to the actual id value
     const std::vector<uint8_t>& getId() const;
     const std::vector<uint8_t>& getId() const;
 
 
-    /// @brief Returns the UserIdType 
+    /// @brief Returns the UserIdType
     UserIdType getType() const;
     UserIdType getType() const;
 
 
     /// @brief Returns textual representation of a id (e.g. 00:01:02:03:ff)
     /// @brief Returns textual representation of a id (e.g. 00:01:02:03:ff)
@@ -63,10 +65,10 @@ public:
 private:
 private:
     void decodeHex(const std::string& input, std::vector<uint8_t>& bytes) const;
     void decodeHex(const std::string& input, std::vector<uint8_t>& bytes) const;
 
 
-    /// @brief The type of id value 
+    /// @brief The type of id value
     UserIdType id_type_;
     UserIdType id_type_;
 
 
-    /// @brief The id value 
+    /// @brief The id value
     std::vector<uint8_t> id_;
     std::vector<uint8_t> id_;
 
 
 };
 };
@@ -89,6 +91,10 @@ public:
 
 
     ~User();
     ~User();
 
 
+    const PropertyMap& getProperties() const;
+
+    void setProperties(const PropertyMap& properties);
+
     void setProperty(const std::string& name, const std::string& value);
     void setProperty(const std::string& name, const std::string& value);
 
 
     std::string getProperty(const std::string& name) const;
     std::string getProperty(const std::string& name) const;

+ 5 - 5
src/hooks/dhcp/user_chk/user_data_source.cc

@@ -23,27 +23,27 @@ UserDataSource::~UserDataSource() {
     }
     }
 }
 }
 
 
-void 
+void
 UserDataSource::open() {
 UserDataSource::open() {
     open_flag_ = false;
     open_flag_ = false;
 }
 }
 
 
-UserPtr 
+UserPtr
 UserDataSource::readNextUser() {
 UserDataSource::readNextUser() {
     return (UserPtr());
     return (UserPtr());
 }
 }
 
 
-void 
+void
 UserDataSource::close() {
 UserDataSource::close() {
     open_flag_ = false;
     open_flag_ = false;
 }
 }
 
 
-bool 
+bool
 UserDataSource::isOpen() const {
 UserDataSource::isOpen() const {
     return open_flag_;
     return open_flag_;
 }
 }
 
 
-void 
+void
 UserDataSource::setOpenFlag(bool value) {
 UserDataSource::setOpenFlag(bool value) {
   open_flag_ = value;
   open_flag_ = value;
 }
 }

+ 4 - 4
src/hooks/dhcp/user_chk/user_data_source.h

@@ -21,13 +21,13 @@ class UserDataSource {
 public:
 public:
     UserDataSource();
     UserDataSource();
 
 
-    ~UserDataSource();
+    virtual ~UserDataSource();
 
 
-    void open();
+    virtual void open();
 
 
-    UserPtr readNextUser();
+    virtual UserPtr readNextUser();
 
 
-    void close();
+    virtual void close();
 
 
     bool isOpen() const;
     bool isOpen() const;
 
 

+ 136 - 0
src/hooks/dhcp/user_chk/user_file.cc

@@ -0,0 +1,136 @@
+// Copyright (C) 2013 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 <user.h>
+#include <user_file.h>
+
+#include <boost/foreach.hpp>
+
+UserFile::UserFile(const std::string& fname) : fname_(fname) {
+    if (fname_.empty()) {
+        isc_throw(UserFileError, "file name cannot be blank");
+    }
+}
+
+UserFile::~UserFile(){
+};
+
+void
+UserFile::open() {
+    if (isOpen()) {
+        isc_throw (UserFileError, "file is already open");
+    }
+
+    ifs_.open(fname_.c_str(), std::ifstream::in);
+    if (!ifs_.is_open()) {
+        isc_throw(UserFileError, "cannot open file:" << fname_);
+    }
+
+    setOpenFlag(true);
+}
+
+UserPtr
+UserFile::readNextUser() {
+    if (!isOpen()) {
+        isc_throw (UserFileError, "cannot read, file is not open");
+    }
+
+    if (ifs_.good()) {
+        char buf[4096];
+
+        // get the next line
+        ifs_.getline(buf, sizeof(buf));
+
+        // we got something, try to make a user out of it.
+        if (ifs_.gcount() > 0) {
+            return(makeUser(buf));
+        }
+    }
+
+    // returns an empty user
+    return (UserDataSource::readNextUser());
+}
+
+UserPtr
+UserFile::makeUser(const std::string& user_string) {
+    // This method leverages the existing JSON parsing provided by isc::data
+    // library.  Should this prove to be a performance issue, it may be that
+    // lighter weight solution would be appropriate.
+
+    // Turn the string of JSON text into an Element set.
+    isc::data::ElementPtr elements;
+    try {
+        elements = isc::data::Element::fromJSON(user_string);
+    } catch (isc::data::JSONError& ex) {
+        isc_throw(UserFileError,
+                  "UserFile entry is malformed JSON: " << ex.what());
+    }
+
+    // Get a map of the Elements, keyed by element name.
+    isc::data::ConstElementPtr element;
+    PropertyMap properties;
+    std::string id_type_str;
+    std::string id_str;
+
+    std::pair<std::string, isc::data::ConstElementPtr> element_pair;
+    BOOST_FOREACH (element_pair, elements->mapValue()) {
+        std::string label = element_pair.first;
+        std::string value = "";
+        element_pair.second->getValue(value);
+
+        if (label == "type") {
+            id_type_str = value;
+        } else if (label == "id") {
+            id_str = value;
+        } else {
+            if (properties.find(label) != properties.end()) {
+                isc_throw (UserFileError,
+                           "UserFile entry contains duplicate values: "
+                           << user_string);
+            }
+            properties[label]=value;
+        }
+    }
+
+    UserId::UserIdType id_type;
+    try {
+        id_type = UserId::lookupType(id_type_str);
+    } catch (const std::exception& ex) {
+        isc_throw (UserFileError, "UserFile entry has invalid type: "
+                                  << user_string << " " << ex.what());
+    }
+
+    UserPtr user;
+    try {
+        user.reset(new User(id_type, id_str));
+    } catch (const std::exception& ex) {
+        isc_throw (UserFileError, "UserFile cannot create user form entry: "
+                                  << user_string << " " << ex.what());
+   }
+
+
+    user->setProperties(properties);
+    return (user);
+}
+
+void
+UserFile::close() {
+    if (ifs_.is_open()) {
+        ifs_.close();
+    }
+
+    setOpenFlag(false);
+}
+

+ 61 - 0
src/hooks/dhcp/user_chk/user_file.h

@@ -0,0 +1,61 @@
+// Copyright (C) 2013 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 _USER_FILE_H
+#define _USER_FILE_H
+
+#include <exceptions/exceptions.h>
+
+#include <user_data_source.h>
+#include <user.h>
+
+#include <boost/shared_ptr.hpp>
+#include <fstream>
+#include <string>
+
+using namespace std;
+
+/// @brief Thrown UserFile encounters an error
+class UserFileError : public isc::Exception {
+public:
+    UserFileError(const char* file, size_t line,
+                               const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+class UserFile : public UserDataSource {
+public:
+    UserFile(const std::string& fname);
+
+    virtual ~UserFile();
+
+    // must throw if open fails.
+    void open();
+
+    UserPtr readNextUser();
+
+    void close();
+
+    UserPtr makeUser(const std::string& user_string);
+
+private:
+    string fname_;
+
+    std::ifstream ifs_;
+
+};
+
+typedef boost::shared_ptr<UserFile> UserFilePtr;
+
+#endif

+ 34 - 9
src/hooks/dhcp/user_chk/user_registry.cc

@@ -21,31 +21,34 @@ UserRegistry::UserRegistry() {
 UserRegistry::~UserRegistry(){
 UserRegistry::~UserRegistry(){
 }
 }
 
 
-void 
+void
 UserRegistry::addUser(UserPtr& user) {
 UserRegistry::addUser(UserPtr& user) {
     if (!user) {
     if (!user) {
-        isc_throw (isc::BadValue, "UserRegistry cannot add blank user");
+        isc_throw (UserRegistryError, "UserRegistry cannot add blank user");
     }
     }
 
 
-    if (findUser(user->getUserId())) {
-        isc_throw (isc::BadValue, "Duplicate User!");
+    UserPtr found_user;
+    if ((found_user = findUser(user->getUserId()))) {
+        isc_throw (UserRegistryError, "UserRegistry duplicate user: "
+                   << user->getUserId());
     }
     }
 
 
     users_[user->getUserId()] = user;
     users_[user->getUserId()] = user;
 }
 }
 
 
-const UserPtr& 
+const UserPtr&
 UserRegistry::findUser(const UserId& id) const {
 UserRegistry::findUser(const UserId& id) const {
     static UserPtr empty;
     static UserPtr empty;
     UserMap::const_iterator it = users_.find(id);
     UserMap::const_iterator it = users_.find(id);
     if (it != users_.end()) {
     if (it != users_.end()) {
+        const UserPtr tmp = (*it).second;
         return ((*it).second);
         return ((*it).second);
     }
     }
 
 
     return empty;
     return empty;
 }
 }
 
 
-void 
+void
 UserRegistry::removeUser(const UserId& id) {
 UserRegistry::removeUser(const UserId& id) {
     static UserPtr empty;
     static UserPtr empty;
     UserMap::iterator it = users_.find(id);
     UserMap::iterator it = users_.find(id);
@@ -54,25 +57,47 @@ UserRegistry::removeUser(const UserId& id) {
     }
     }
 }
 }
 
 
-const UserPtr& 
+const UserPtr&
 UserRegistry::findUser(const isc::dhcp::HWAddr& hwaddr) const {
 UserRegistry::findUser(const isc::dhcp::HWAddr& hwaddr) const {
     UserId id(UserId::HW_ADDRESS, hwaddr.hwaddr_);
     UserId id(UserId::HW_ADDRESS, hwaddr.hwaddr_);
     return (findUser(id));
     return (findUser(id));
 }
 }
 
 
-const UserPtr& 
+const UserPtr&
 UserRegistry::findUser(const isc::dhcp::ClientId& client_id) const {
 UserRegistry::findUser(const isc::dhcp::ClientId& client_id) const {
     UserId id(UserId::CLIENT_ID, client_id.getClientId());
     UserId id(UserId::CLIENT_ID, client_id.getClientId());
     return (findUser(id));
     return (findUser(id));
 }
 }
 
 
-const UserPtr& 
+const UserPtr&
 UserRegistry::findUser(const isc::dhcp::DUID& duid) const {
 UserRegistry::findUser(const isc::dhcp::DUID& duid) const {
     UserId id(UserId::DUID, duid.getDuid());
     UserId id(UserId::DUID, duid.getDuid());
     return (findUser(id));
     return (findUser(id));
 }
 }
 
 
 void UserRegistry::refresh() {
 void UserRegistry::refresh() {
+    if (!source_) {
+        isc_throw(UserRegistryError,
+                  "UserRegistry: cannot refresh, no data source");
+    }
+
+    if (!source_->isOpen()) {
+        source_->open();
+    }
+
+    clearall();
+    try {
+        UserPtr user;
+        while ((user = source_->readNextUser())) {
+            addUser(user);
+        }
+    } catch (const std::exception& ex) {
+        source_->close();
+        isc_throw (UserRegistryError, "UserRegistry: refresh failed during read"
+                   << ex.what());
+    }
+
+    source_->close();
 }
 }
 
 
 void UserRegistry::clearall() {
 void UserRegistry::clearall() {

+ 9 - 0
src/hooks/dhcp/user_chk/user_registry.h

@@ -16,6 +16,7 @@
 
 
 #include <dhcp/hwaddr.h>
 #include <dhcp/hwaddr.h>
 #include <dhcp/duid.h>
 #include <dhcp/duid.h>
+#include <exceptions/exceptions.h>
 #include <user.h>
 #include <user.h>
 #include <user_data_source.h>
 #include <user_data_source.h>
 
 
@@ -23,6 +24,14 @@
 
 
 using namespace std;
 using namespace std;
 
 
+/// @brief Thrown UserRegistry encounters an error
+class UserRegistryError : public isc::Exception {
+public:
+    UserRegistryError(const char* file, size_t line,
+                               const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
 typedef std::map<UserId,UserPtr> UserMap;
 typedef std::map<UserId,UserPtr> UserMap;
 
 
 class UserRegistry {
 class UserRegistry {