Browse Source

[master] Merge branch 'trac3534'

Conflicts:
	src/lib/dhcpsrv/tests/daemon_unittest.cc
Marcin Siodelski 10 years ago
parent
commit
4ecee3c0c9
45 changed files with 1773 additions and 581 deletions
  1. 0 1
      configure.ac
  2. 34 5
      src/bin/d2/d_controller.cc
  3. 1 1
      src/bin/dhcp4/bundy_controller.cc
  4. 10 12
      src/bin/dhcp4/ctrl_dhcp4_srv.cc
  5. 5 6
      src/bin/dhcp4/json_config_parser.cc
  6. 18 5
      src/bin/dhcp4/kea_controller.cc
  7. 6 0
      src/bin/dhcp4/main.cc
  8. 4 2
      src/bin/dhcp4/tests/config_parser_unittest.cc
  9. 2 2
      src/bin/dhcp4/tests/dhcp4_test_utils.cc
  10. 8 13
      src/bin/dhcp6/ctrl_dhcp6_srv.cc
  11. 6 0
      src/bin/dhcp6/dhcp6.dox
  12. 0 6
      src/bin/dhcp6/dhcp6_srv.cc
  13. 5 6
      src/bin/dhcp6/json_config_parser.cc
  14. 18 2
      src/bin/dhcp6/kea_controller.cc
  15. 7 1
      src/bin/dhcp6/main.cc
  16. 6 6
      src/bin/dhcp6/tests/config_parser_unittest.cc
  17. 5 4
      src/bin/keactrl/Makefile.am
  18. 0 0
      src/bin/keactrl/kea.conf.pre
  19. 4 3
      src/lib/dhcpsrv/Makefile.am
  20. 21 13
      src/lib/dhcpsrv/cfg_iface.cc
  21. 39 29
      src/lib/dhcpsrv/cfg_iface.h
  22. 86 3
      src/lib/dhcpsrv/cfgmgr.cc
  23. 153 6
      src/lib/dhcpsrv/cfgmgr.h
  24. 0 64
      src/lib/dhcpsrv/configuration.cc
  25. 0 149
      src/lib/dhcpsrv/configuration.h
  26. 18 32
      src/lib/dhcpsrv/daemon.cc
  27. 4 12
      src/lib/dhcpsrv/daemon.h
  28. 8 21
      src/lib/dhcpsrv/dhcp_parsers.cc
  29. 6 17
      src/lib/dhcpsrv/dhcp_parsers.h
  30. 42 6
      src/lib/dhcpsrv/libdhcpsrv.dox
  31. 8 4
      src/lib/dhcpsrv/logging.cc
  32. 3 3
      src/lib/dhcpsrv/logging.h
  33. 145 0
      src/lib/dhcpsrv/logging_info.cc
  34. 131 0
      src/lib/dhcpsrv/logging_info.h
  35. 138 0
      src/lib/dhcpsrv/srv_config.cc
  36. 230 0
      src/lib/dhcpsrv/srv_config.h
  37. 2 1
      src/lib/dhcpsrv/tests/Makefile.am
  38. 94 43
      src/lib/dhcpsrv/tests/cfg_iface_unittest.cc
  39. 157 5
      src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
  40. 10 8
      src/lib/dhcpsrv/tests/daemon_unittest.cc
  41. 14 10
      src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
  42. 160 0
      src/lib/dhcpsrv/tests/logging_info_unittest.cc
  43. 36 36
      src/lib/dhcpsrv/tests/logging_unittest.cc
  44. 128 44
      src/lib/dhcpsrv/tests/configuration_unittest.cc
  45. 1 0
      tools/.gitignore

+ 0 - 1
configure.ac

@@ -1391,7 +1391,6 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/bin/dhcp6/tests/test_data_files_config.h
                  src/bin/dhcp6/tests/test_libraries.h
                  src/bin/keactrl/Makefile
-                 src/bin/keactrl/kea.conf
                  src/bin/keactrl/keactrl
                  src/bin/keactrl/keactrl.conf
                  src/bin/keactrl/tests/Makefile

+ 34 - 5
src/bin/d2/d_controller.cc

@@ -18,7 +18,7 @@
 #include <d2/d_controller.h>
 #include <exceptions/exceptions.h>
 #include <log/logger_support.h>
-#include <dhcpsrv/configuration.h>
+#include <dhcpsrv/cfgmgr.h>
 
 #include <sstream>
 #include <unistd.h>
@@ -59,6 +59,16 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
         throw; // rethrow it
     }
 
+    // It is important that we set a default logger name because this name
+    // will be used when the user doesn't provide the logging configuration
+    // in the Kea configuration file.
+    isc::dhcp::CfgMgr::instance().setDefaultLoggerName(bin_name_);
+
+    // Logger's default configuration depends on whether we are in the
+    // verbose mode or not. CfgMgr manages the logger configuration so
+    // the verbose mode is set for CfgMgr.
+    isc::dhcp::CfgMgr::instance().setVerbose(verbose_);
+
     // Do not initialize logger here if we are running unit tests. It would
     // replace an instance of unit test specific logger.
     if (!test_mode) {
@@ -218,8 +228,13 @@ DControllerBase::initProcess() {
 
 isc::data::ConstElementPtr
 DControllerBase::configFromFile() {
+    // Rollback any previous staging configuration. For D2, only a
+    // logger configuration is used here.
+    isc::dhcp::CfgMgr::instance().rollback();
+    // Will hold configuration.
     isc::data::ConstElementPtr module_config;
-
+    // Will receive configuration result.
+    isc::data::ConstElementPtr answer;
     try {
         std::string config_file = getConfigFile();
         if (config_file.empty()) {
@@ -236,11 +251,12 @@ DControllerBase::configFromFile() {
         // so we can log things during configuration process.
 
         // Temporary storage for logging configuration
-        isc::dhcp::ConfigurationPtr storage(new isc::dhcp::Configuration());
+        isc::dhcp::SrvConfigPtr storage =
+            isc::dhcp::CfgMgr::instance().getStagingCfg();
 
         // Get 'Logging' element from the config and use it to set up
         // logging. If there's no such element, we'll just pass NULL.
-        Daemon::configureLogger(whole_config->get("Logging"), storage, verbose_);
+        Daemon::configureLogger(whole_config->get("Logging"), storage);
 
         // Extract derivation-specific portion of the configuration.
         module_config = whole_config->get(getAppName());
@@ -249,7 +265,20 @@ DControllerBase::configFromFile() {
                                 " does not include '" <<
                                  getAppName() << "' entry.");
         }
+
+        answer = updateConfig(module_config);
+        int rcode = 0;
+        isc::config::parseAnswer(rcode, answer);
+        if (!rcode) {
+            // Configuration successful, so apply the logging configuration
+            // to log4cplus.
+            isc::dhcp::CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
+            isc::dhcp::CfgMgr::instance().commit();
+        }
+
     } catch (const std::exception& ex) {
+        // Rollback logging configuration.
+        isc::dhcp::CfgMgr::instance().rollback();
         // build an error result
         isc::data::ConstElementPtr error =
             isc::config::createAnswer(1,
@@ -257,7 +286,7 @@ DControllerBase::configFromFile() {
         return (error);
     }
 
-    return (updateConfig(module_config));
+    return (answer);
 }
 
 

+ 1 - 1
src/bin/dhcp4/bundy_controller.cc

@@ -177,7 +177,7 @@ void ControlledDhcpv4Srv::init(const std::string& config_file) {
 
         // Configuration may disable or enable interfaces so we have to
         // reopen sockets according to new configuration.
-        CfgMgr::instance().getConfiguration()->cfg_iface_
+        CfgMgr::instance().getCurrentCfg()->cfg_iface_
             .openSockets(getPort(), useBroadcast());
 
     } catch (const std::exception& ex) {

+ 10 - 12
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -145,18 +145,16 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
     }
 
     // Configuration may change active interfaces. Therefore, we have to reopen
-    // sockets according to new configuration. This operation is not exception
-    // safe and we really don't want to emit exceptions to whoever called this
-    // method. Instead, catch an exception and create appropriate answer.
-    try {
-        CfgMgr::instance().getConfiguration()->cfg_iface_
-            .openSockets(srv->getPort(), getInstance()->useBroadcast());
+    // sockets according to new configuration. It is possible that this
+    // operation will fail for some interfaces but the openSockets function
+    // guards against exceptions and invokes a callback function to
+    // log warnings. Since we allow that this fails for some interfaces there
+    // is no need to rollback configuration if socket fails to open on any
+    // of the interfaces.
+    CfgMgr::instance().getStagingCfg()->
+        getCfgIface().openSockets(AF_INET, srv->getPort(),
+                                  getInstance()->useBroadcast());
 
-    } catch (std::exception& ex) {
-        err << "failed to open sockets after server reconfiguration: "
-            << ex.what();
-        answer = isc::config::createAnswer(1, err.str());
-    }
     return (answer);
 }
 
@@ -176,7 +174,7 @@ void ControlledDhcpv4Srv::shutdown() {
 
 ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
     cleanup();
-    
+
     server_ = NULL; // forget this instance. Noone should call any handlers at
                     // this stage.
 }

+ 5 - 6
src/bin/dhcp4/json_config_parser.cc

@@ -443,7 +443,7 @@ namespace dhcp {
         parser = new Uint32Parser(config_id,
                                  globalContext()->uint32_values_);
     } else if (config_id.compare("interfaces") == 0) {
-        parser = new InterfaceListConfigParser(config_id);
+        parser = new InterfaceListConfigParser(config_id, globalContext());
     } else if (config_id.compare("subnet4") == 0) {
         parser = new Subnets4ListConfigParser(config_id);
     } else if (config_id.compare("option-data") == 0) {
@@ -619,9 +619,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
                 subnet_parser->commit();
             }
 
-            if (iface_parser) {
-                iface_parser->commit();
-            }
+            // No need to commit interface names as this is handled by the
+            // CfgMgr::commit() function.
 
             // Apply global options
             commitGlobalOptions();
@@ -653,8 +652,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
     }
 
     LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE)
-        .arg(CfgMgr::instance().getConfiguration()->
-             getConfigSummary(Configuration::CFGSEL_ALL4));
+        .arg(CfgMgr::instance().getCurrentCfg()->
+             getConfigSummary(SrvConfig::CFGSEL_ALL4));
 
     // Everything was fine. Configuration is successful.
     answer = isc::config::createAnswer(0, "Configuration successful.");

+ 18 - 5
src/bin/dhcp4/kea_controller.cc

@@ -41,6 +41,11 @@ void configure(const std::string& file_name) {
     // This is a configuration backend implementation that reads the
     // configuration from a JSON file.
 
+    // We are starting the configuration process so we should remove any
+    // staging configuration that has been created during previous
+    // configuration attempts.
+    CfgMgr::instance().rollback();
+
     isc::data::ConstElementPtr json;
     isc::data::ConstElementPtr dhcp4;
     isc::data::ConstElementPtr logger;
@@ -60,14 +65,10 @@ void configure(const std::string& file_name) {
             isc_throw(isc::BadValue, "no configuration found");
         }
 
-        // Let's configure logging before applying the configuration,
-        // so we can log things during configuration process.
-
         // If there's no logging element, we'll just pass NULL pointer,
         // which will be handled by configureLogger().
         Daemon::configureLogger(json->get("Logging"),
-                                CfgMgr::instance().getConfiguration(),
-                                ControlledDhcpv4Srv::getInstance()->getVerbose());
+                                CfgMgr::instance().getStagingCfg());
 
         // Get Dhcp4 component from the config
         dhcp4 = json->get("Dhcp4");
@@ -97,7 +98,19 @@ void configure(const std::string& file_name) {
             isc_throw(isc::BadValue, reason);
         }
 
+        // If configuration was parsed successfully, apply the new logger
+        // configuration to log4cplus. It is done before commit in case
+        // something goes wrong.
+        CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
+
+        // Use new configuration.
+        CfgMgr::instance().commit();
+
     }  catch (const std::exception& ex) {
+        // If configuration failed at any stage, we drop the staging
+        // configuration and continue to use the previous one.
+        CfgMgr::instance().rollback();
+
         LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
             .arg(file_name).arg(ex.what());
         isc_throw(isc::BadValue, "configuration error using file '"

+ 6 - 0
src/bin/dhcp4/main.cc

@@ -16,6 +16,7 @@
 
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <log/logger_support.h>
 #include <log/logger_manager.h>
 
@@ -122,6 +123,11 @@ main(int argc, char* argv[]) {
     int ret = EXIT_SUCCESS;
 
     try {
+        // It is important that we set a default logger name because this name
+        // will be used when the user doesn't provide the logging configuration
+        // in the Kea configuration file.
+        CfgMgr::instance().setDefaultLoggerName(DHCP4_LOGGER_NAME);
+
         // Initialize logging.  If verbose, we'll use maximum verbosity.
         Daemon::loggerInit(DHCP4_LOGGER_NAME, verbose_mode);
         LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)

+ 4 - 2
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -2917,7 +2917,8 @@ TEST_F(Dhcp4ParserTest, selectedInterfaces) {
     ASSERT_TRUE(status);
     checkResult(status, 0);
 
-    CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+    CfgMgr::instance().getStagingCfg()->
+        getCfgIface().openSockets(AF_INET, 10000);
 
     // eth0 and eth1 were explicitly selected. eth2 was not.
     EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET));
@@ -2952,7 +2953,8 @@ TEST_F(Dhcp4ParserTest, allInterfaces) {
     ASSERT_TRUE(status);
     checkResult(status, 0);
 
-    CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+    CfgMgr::instance().getStagingCfg()->
+        getCfgIface().openSockets(AF_INET, 10000);
 
     // All interfaces should be now active.
     ASSERT_TRUE(test_config.socketOpen("eth0", AF_INET));

+ 2 - 2
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -45,7 +45,7 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
     pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
     subnet_->addPool(pool_);
 
-    CfgMgr::instance().getConfiguration()->cfg_iface_.reset();
+    CfgMgr::instance().clear();
     CfgMgr::instance().deleteSubnets4();
     CfgMgr::instance().addSubnet4(subnet_);
 
@@ -58,7 +58,7 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
 Dhcpv4SrvTest::~Dhcpv4SrvTest() {
 
     // Make sure that we revert to default value
-    CfgMgr::instance().getConfiguration()->cfg_iface_.reset();
+    CfgMgr::instance().clear();
     CfgMgr::instance().echoClientId(true);
 }
 

+ 8 - 13
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -140,19 +140,14 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
     }
 
     // Configuration may change active interfaces. Therefore, we have to reopen
-    // sockets according to new configuration. This operation is not exception
-    // safe and we really don't want to emit exceptions to the callback caller.
-    // Instead, catch an exception and create appropriate answer.
-    try {
-        CfgMgr::instance().getConfiguration()->cfg_iface_
-            .openSockets(srv->getPort());
-
-    } catch (const std::exception& ex) {
-        std::ostringstream err;
-        err << "failed to open sockets after server reconfiguration: "
-            << ex.what();
-        answer = isc::config::createAnswer(1, err.str());
-    }
+    // sockets according to new configuration. It is possible that this
+    // operation will fail for some interfaces but the openSockets function
+    // guards against exceptions and invokes a callback function to
+    // log warnings. Since we allow that this fails for some interfaces there
+    // is no need to rollback configuration if socket fails to open on any
+    // of the interfaces.
+    CfgMgr::instance().getStagingCfg()->
+        getCfgIface().openSockets(AF_INET6, srv->getPort());
 
     return (answer);
 }

+ 6 - 0
src/bin/dhcp6/dhcp6.dox

@@ -55,6 +55,12 @@ following section applies only to the Bundy backend.
  build, the configuration is then applied ("committed") and commit() method is
  called.
 
+ \note With the implementation of the Kea ticket #3534 we're moving away from
+ the concept of commit being called for individual parsers. When this ticket
+ and a couple of followup tickets are implemented, the commit will be a
+ single operation executed for the whole configuration received from many
+ parsers.
+
  All parsers are defined in src/bin/dhcp6/config_parser.cc file. Some of them
  are generic (e.g. Uint32Parser that is able to handle any
  unsigned 32 bit integer), but some are very specialized (e.g.

+ 0 - 6
src/bin/dhcp6/dhcp6_srv.cc

@@ -150,12 +150,6 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
         // Instantiate allocation engine
         alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
 
-        // We have to point out to the CfgMgr that the we are in the IPv6
-        // domain, so as the IPv6 sockets are opened rather than IPv4 sockets
-        // which are the default.
-        CfgMgr::instance().getConfiguration()
-            ->cfg_iface_.setFamily(CfgIface::V6);
-
         /// @todo call loadLibraries() when handling configuration changes
 
     } catch (const std::exception &e) {

+ 5 - 6
src/bin/dhcp6/json_config_parser.cc

@@ -662,7 +662,7 @@ namespace dhcp {
         parser = new Uint32Parser(config_id,
                                  globalContext()->uint32_values_);
     } else if (config_id.compare("interfaces") == 0) {
-        parser = new InterfaceListConfigParser(config_id);
+        parser = new InterfaceListConfigParser(config_id, globalContext());
     } else if (config_id.compare("subnet6") == 0) {
         parser = new Subnets6ListConfigParser(config_id);
     } else if (config_id.compare("option-data") == 0) {
@@ -821,9 +821,8 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
                 subnet_parser->commit();
             }
 
-            if (iface_parser) {
-                iface_parser->commit();
-            }
+            // No need to commit interface names as this is handled by the
+            // CfgMgr::commit() function.
 
             // This occurs last as if it succeeds, there is no easy way to
             // revert it.  As a result, the failure to commit a subsequent
@@ -854,8 +853,8 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
     }
 
     LOG_INFO(dhcp6_logger, DHCP6_CONFIG_COMPLETE)
-        .arg(CfgMgr::instance().getConfiguration()->
-             getConfigSummary(Configuration::CFGSEL_ALL6));
+        .arg(CfgMgr::instance().getCurrentCfg()->
+             getConfigSummary(SrvConfig::CFGSEL_ALL6));
 
     // Everything was fine. Configuration is successful.
     answer = isc::config::createAnswer(0, "Configuration successful.");

+ 18 - 2
src/bin/dhcp6/kea_controller.cc

@@ -45,6 +45,11 @@ void configure(const std::string& file_name) {
     // This is a configuration backend implementation that reads the
     // configuration from a JSON file.
 
+    // We are starting the configuration process so we should remove any
+    // staging configuration that has been created during previous
+    // configuration attempts.
+    CfgMgr::instance().rollback();
+
     isc::data::ConstElementPtr json;
     isc::data::ConstElementPtr dhcp6;
     isc::data::ConstElementPtr logger;
@@ -69,8 +74,7 @@ void configure(const std::string& file_name) {
         // If there's no logging element, we'll just pass NULL pointer,
         // which will be handled by configureLogger().
         Daemon::configureLogger(json->get("Logging"),
-                                CfgMgr::instance().getConfiguration(),
-                                ControlledDhcpv6Srv::getInstance()->getVerbose());
+                                CfgMgr::instance().getStagingCfg());
 
         // Get Dhcp6 component from the config
         dhcp6 = json->get("Dhcp6");
@@ -101,7 +105,19 @@ void configure(const std::string& file_name) {
             isc_throw(isc::BadValue, reason);
         }
 
+        // If configuration was parsed successfully, apply the new logger
+        // configuration to log4cplus. It is done before commit in case
+        // something goes wrong.
+        CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
+
+        // Use new configuration.
+        CfgMgr::instance().commit();
+
     }  catch (const std::exception& ex) {
+        // If configuration failed at any stage, we drop the staging
+        // configuration and continue to use the previous one.
+        CfgMgr::instance().rollback();
+
         LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
             .arg(file_name).arg(ex.what());
         isc_throw(isc::BadValue, "configuration error using file '"

+ 7 - 1
src/bin/dhcp6/main.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 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
@@ -16,6 +16,7 @@
 
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <log/logger_support.h>
 #include <log/logger_manager.h>
 #include <exceptions/exceptions.h>
@@ -122,6 +123,11 @@ main(int argc, char* argv[]) {
 
     int ret = EXIT_SUCCESS;
     try {
+        // It is important that we set a default logger name because this name
+        // will be used when the user doesn't provide the logging configuration
+        // in the Kea configuration file.
+        CfgMgr::instance().setDefaultLoggerName(DHCP6_LOGGER_NAME);
+
         // Initialize logging.  If verbose, we'll use maximum verbosity.
         Daemon::loggerInit(DHCP6_LOGGER_NAME, verbose_mode);
 

+ 6 - 6
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -359,9 +359,7 @@ public:
         // properly test interface configuration we disable listening on
         // all interfaces before each test and later check that this setting
         // has been overriden by the configuration used in the test.
-        CfgMgr::instance().getConfiguration()->cfg_iface_.reset();
-        CfgMgr::instance().getConfiguration()->
-            cfg_iface_.setFamily(CfgIface::V6);
+        CfgMgr::instance().clear();
         // Create fresh context.
         globalContext()->copyContext(ParserContext(Option::V6));
     }
@@ -3057,7 +3055,8 @@ TEST_F(Dhcp6ParserTest, selectedInterfaces) {
     // as the pool does not belong to that subnet
     checkResult(status, 0);
 
-    CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+    CfgMgr::instance().getStagingCfg()->
+        getCfgIface().openSockets(AF_INET6, 10000);
 
     // eth0 and eth1 were explicitly selected. eth2 was not.
     EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET6));
@@ -3075,7 +3074,7 @@ TEST_F(Dhcp6ParserTest, allInterfaces) {
     ConstElementPtr status;
 
     // This configuration specifies two interfaces on which server should listen
-    // bu also includes keyword 'all'. This keyword switches server into the
+    // but also includes '*'. This keyword switches server into the
     // mode when it listens on all interfaces regardless of what interface names
     // were specified in the "interfaces" parameter.
     string config = "{ \"interfaces\": [ \"eth0\", \"eth1\", \"*\" ],"
@@ -3090,7 +3089,8 @@ TEST_F(Dhcp6ParserTest, allInterfaces) {
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
     checkResult(status, 0);
 
-    CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+    CfgMgr::instance().getStagingCfg()->
+        getCfgIface().openSockets(AF_INET6, 10000);
 
     // All interfaces should be now active.
     EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET6));

+ 5 - 4
src/bin/keactrl/Makefile.am

@@ -8,8 +8,9 @@ sbin_SCRIPTS  = keactrl
 CONFIGFILES = keactrl.conf kea.conf
 
 man_MANS = keactrl.8
-DISTCLEANFILES = keactrl $(CONFIGFILES) $(man_MANS)
-EXTRA_DIST = keactrl.in keactrl.conf.in kea.conf.in $(man_MANS) keactrl.xml
+DISTCLEANFILES = keactrl keactrl.conf $(man_MANS)
+CLEANFILES = kea.conf
+EXTRA_DIST = keactrl.in keactrl.conf.in kea.conf.pre $(man_MANS) keactrl.xml
 
 # kea.conf is not really a source used for building other targets, but we need
 # this file to be generated before make install is called.
@@ -28,8 +29,8 @@ $(man_MANS):
 
 endif
 
-kea.conf: kea.conf.in
-	$(top_srcdir)/tools/path_replacer.sh $< $@
+kea.conf: kea.conf.pre
+	$(top_srcdir)/tools/path_replacer.sh $(top_srcdir)/src/bin/keactrl/kea.conf.pre $@
 
 if INSTALL_CONFIGURATIONS
 

src/bin/keactrl/kea.conf.in → src/bin/keactrl/kea.conf.pre


+ 4 - 3
src/lib/dhcpsrv/Makefile.am

@@ -45,6 +45,8 @@ libkea_dhcpsrv_la_SOURCES  =
 libkea_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
 libkea_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
 libkea_dhcpsrv_la_SOURCES += callout_handle_store.h
+libkea_dhcpsrv_la_SOURCES += cfg_iface.cc cfg_iface.h
+libkea_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.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
@@ -52,16 +54,14 @@ libkea_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h
 libkea_dhcpsrv_la_SOURCES += daemon.cc daemon.h
 libkea_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
-libkea_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
 libkea_dhcpsrv_la_SOURCES += dhcp_config_parser.h
 libkea_dhcpsrv_la_SOURCES += dhcp_parsers.cc dhcp_parsers.h
-libkea_dhcpsrv_la_SOURCES += cfg_iface.cc cfg_iface.h
 libkea_dhcpsrv_la_SOURCES += key_from_key.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
 libkea_dhcpsrv_la_SOURCES += logging.cc logging.h
-libkea_dhcpsrv_la_SOURCES += configuration.h configuration.cc
+libkea_dhcpsrv_la_SOURCES += logging_info.cc logging_info.h
 libkea_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
 
 if HAVE_MYSQL
@@ -72,6 +72,7 @@ libkea_dhcpsrv_la_SOURCES += pgsql_lease_mgr.cc pgsql_lease_mgr.h
 endif
 libkea_dhcpsrv_la_SOURCES += option_space_container.h
 libkea_dhcpsrv_la_SOURCES += pool.cc pool.h
+libkea_dhcpsrv_la_SOURCES += srv_config.cc srv_config.h
 libkea_dhcpsrv_la_SOURCES += subnet.cc subnet.h
 libkea_dhcpsrv_la_SOURCES += triplet.h
 libkea_dhcpsrv_la_SOURCES += utils.h

+ 21 - 13
src/lib/dhcpsrv/cfg_iface.cc

@@ -25,24 +25,31 @@ namespace dhcp {
 
 const char* CfgIface::ALL_IFACES_KEYWORD = "*";
 
-CfgIface::CfgIface(Family family)
-    : family_(family),
-      wildcard_used_(false) {
+CfgIface::CfgIface()
+    : wildcard_used_(false) {
 }
 
 void
-CfgIface::closeSockets() {
+CfgIface::closeSockets() const {
     IfaceMgr::instance().closeSockets();
 }
 
+bool
+CfgIface::equals(const CfgIface& other) const {
+    return (iface_set_ == other.iface_set_ &&
+            unicast_map_ == other.unicast_map_ &&
+            wildcard_used_ == other.wildcard_used_);
+}
+
 void
-CfgIface::openSockets(const uint16_t port, const bool use_bcast) {
+CfgIface::openSockets(const uint16_t family, const uint16_t port,
+                      const bool use_bcast) const {
     // If wildcard interface '*' was not specified, set all interfaces to
     // inactive state. We will later enable them selectively using the
     // interface names specified by the user. If wildcard interface was
     // specified, mark all interfaces active. In all cases, mark loopback
     // inactive.
-    setState(!wildcard_used_, true);
+    setState(family, !wildcard_used_, true);
     // Remove selection of unicast addresses from all interfaces.
     IfaceMgr::instance().clearUnicasts();
     // If there is no wildcard interface specified, we will have to iterate
@@ -61,7 +68,7 @@ CfgIface::openSockets(const uint16_t port, const bool use_bcast) {
                           << *iface_name << "' as this interface doesn't"
                           " exist");
 
-            } else if (getFamily() == V4) {
+            } else if (family == AF_INET) {
                 iface->inactive4_ = false;
 
             } else {
@@ -71,7 +78,7 @@ CfgIface::openSockets(const uint16_t port, const bool use_bcast) {
     }
 
     // Select unicast sockets. It works only for V6. Ignore for V4.
-    if (getFamily() == V6) {
+    if (family == AF_INET6) {
         for (UnicastMap::const_iterator unicast = unicast_map_.begin();
              unicast != unicast_map_.end(); ++unicast) {
             Iface* iface = IfaceMgr::instance().getIface(unicast->first);
@@ -95,7 +102,7 @@ CfgIface::openSockets(const uint16_t port, const bool use_bcast) {
     IfaceMgrErrorMsgCallback error_callback =
         boost::bind(&CfgIface::socketOpenErrorHandler, _1);
     bool sopen;
-    if (getFamily() == V4) {
+    if (family == AF_INET) {
         sopen = IfaceMgr::instance().openSockets4(port, use_bcast,
                                                   error_callback);
     } else {
@@ -117,12 +124,13 @@ CfgIface::reset() {
 }
 
 void
-CfgIface::setState(const bool inactive, const bool loopback_inactive) {
+CfgIface::setState(const uint16_t family, const bool inactive,
+                   const bool loopback_inactive) const {
     IfaceMgr::IfaceCollection ifaces = IfaceMgr::instance().getIfaces();
     for (IfaceMgr::IfaceCollection::iterator iface = ifaces.begin();
          iface != ifaces.end(); ++iface) {
         Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
-        if (getFamily() == V4) {
+        if (family == AF_INET) {
             iface_ptr->inactive4_ = iface_ptr->flag_loopback_ ?
                 loopback_inactive : inactive;
         } else {
@@ -138,7 +146,7 @@ CfgIface::socketOpenErrorHandler(const std::string& errmsg) {
 }
 
 void
-CfgIface::use(const std::string& iface_name) {
+CfgIface::use(const uint16_t family, const std::string& iface_name) {
     // The interface name specified may have two formats, e.g.:
     // - eth0
     // - eth0/2001:db8:1::1.
@@ -184,7 +192,7 @@ CfgIface::use(const std::string& iface_name) {
 
         }
 
-    } else if (getFamily() == V4) {
+    } else if (family == AF_INET) {
         isc_throw(InvalidIfaceName, "unicast addresses in the format of: "
                   "iface-name/unicast-addr_stress can only be specified for"
                   " IPv6 addr_stress family");

+ 39 - 29
src/lib/dhcpsrv/cfg_iface.h

@@ -64,6 +64,11 @@ public:
 ///
 /// Once interfaces have been specified the sockets (either IPv4 or IPv6)
 /// can be opened by calling @c CfgIface::openSockets function.
+///
+/// @warning This class makes use of the AF_INET and AF_INET6 family literals,
+/// but it doesn't verify that the address family value passed as @c uint16_t
+/// parameter is equal to one of them. It is a callers responsibility to
+/// guarantee that the address family value is correct.
 class CfgIface {
 public:
     /// @brief Keyword used to enable all interfaces.
@@ -72,26 +77,18 @@ public:
     /// that DHCP server should listen on all interfaces.
     static const char* ALL_IFACES_KEYWORD;
 
-    /// @brief Protocol family: IPv4 or IPv6.
-    ///
-    /// Depending on the family specified, the IPv4 or IPv6 sockets are
-    /// opened.
-    enum Family {
-        V4, V6
-    };
-
     /// @brief Constructor.
-    ///
-    /// @param family Protocol family (default is V4).
-    CfgIface(Family family = V4);
+    CfgIface();
 
     /// @brief Convenience function which closes all open sockets.
-    void closeSockets();
+    void closeSockets() const;
 
-    /// @brief Returns protocol family used by the @c CfgIface.
-    Family getFamily() const {
-        return (family_);
-    }
+    /// @brief Compares two @c CfgIface objects for equality.
+    ///
+    /// @param other An object to be compared with this object.
+    ///
+    /// @return true if objects are equal, false otherwise.
+    bool equals(const CfgIface& other) const;
 
     /// @brief Tries to open sockets on selected interfaces.
     ///
@@ -100,24 +97,19 @@ public:
     /// documentation for details how to specify interfaces and unicast
     /// addresses to bind the sockets to.
     ///
+    /// @param family Address family (AF_INET or AF_INET6).
     /// @param port Port number to be used to bind sockets to.
     /// @param use_bcast A boolean flag which indicates if the broadcast
     /// traffic should be received through the socket. This parameter is
     /// ignored for IPv6.
-    void openSockets(const uint16_t port, const bool use_bcast = true);
+    void openSockets(const uint16_t family, const uint16_t port,
+                     const bool use_bcast = true) const;
 
     /// @brief Puts the interface configuration into default state.
     ///
     /// This function removes interface names from the set.
     void reset();
 
-    /// @brief Sets protocol family.
-    ///
-    /// @param family New family value (V4 or V6).
-    void setFamily(Family family) {
-        family_ = family;
-    }
-
     /// @brief Select interface to be used to receive DHCP traffic.
     ///
     /// This function controls the selection of the interface on which the
@@ -137,6 +129,7 @@ public:
     /// not allowed when specifying a unicast address. For example:
     /// */2001:db8:1::1 is not allowed.
     ///
+    /// @param family Address family (AF_INET or AF_INET6).
     /// @param iface_name Explicit interface name, a wildcard name (*) of
     /// the interface(s) or the pair of iterface/unicast-address to be used
     /// to receive DHCP traffic.
@@ -148,7 +141,25 @@ public:
     /// @throw DuplicateIfaceName If the interface is already selected, i.e.
     /// @throw IOError when specified unicast address is invalid.
     /// @c CfgIface::use has been already called for this interface.
-    void use(const std::string& iface_name);
+    void use(const uint16_t family, const std::string& iface_name);
+
+    /// @brief Equality operator.
+    ///
+    /// @param other Object to be compared with this object.
+    ///
+    /// @return true if objects are equal, false otherwise.
+    bool operator==(const CfgIface& other) const {
+        return (equals(other));
+    }
+
+    /// @brief Inequality operator.
+    ///
+    /// @param other Object to be compared with this object.
+    ///
+    /// @return true if objects are not equal, false otherwise.
+    bool operator!=(const CfgIface& other) const {
+        return (!equals(other));
+    }
 
 private:
 
@@ -157,12 +168,14 @@ private:
     /// This function selects all interfaces to receive DHCP traffic or
     /// deselects all interfaces so as none of them receives a DHCP traffic.
     ///
+    /// @param family Address family (AF_INET or AF_INET6).
     /// @param inactive A boolean value which indicates if all interfaces
     /// (except loopback) should be selected or deselected.
     /// @param loopback_inactive A boolean value which indicates if loopback
     /// interface should be selected or deselected.
     /// should be deselected/inactive (true) or selected/active (false).
-    void setState(const bool inactive, const bool loopback_inactive);
+    void setState(const uint16_t family, const bool inactive,
+                  const bool loopback_inactive) const;
 
     /// @brief Error handler for executed when opening a socket fail.
     ///
@@ -174,9 +187,6 @@ private:
     /// @param errmsg Error message being logged by the function.
     static void socketOpenErrorHandler(const std::string& errmsg);
 
-    /// @brief Protocol family.
-    Family family_;
-
     /// @brief Represents a set of interface names.
     typedef std::set<std::string> IfaceSet;
 

+ 86 - 3
src/lib/dhcpsrv/cfgmgr.cc

@@ -25,6 +25,8 @@ using namespace isc::util;
 namespace isc {
 namespace dhcp {
 
+const size_t CfgMgr::CONFIG_LIST_SIZE = 10;
+
 CfgMgr&
 CfgMgr::instance() {
     static CfgMgr cfg_mgr;
@@ -357,14 +359,95 @@ CfgMgr::getD2ClientMgr() {
     return (d2_client_mgr_);
 }
 
-ConfigurationPtr
-CfgMgr::getConfiguration() {
+void
+CfgMgr::ensureCurrentAllocated() {
+    if (!configuration_ || configs_.empty()) {
+        configuration_.reset(new SrvConfig());
+        configs_.push_back(configuration_);
+    }
+}
+
+void
+CfgMgr::clear() {
+    configs_.clear();
+    ensureCurrentAllocated();
+}
+
+void
+CfgMgr::commit() {
+    ensureCurrentAllocated();
+    if (!configs_.back()->sequenceEquals(*configuration_)) {
+        configuration_ = configs_.back();
+        // Keep track of the maximum size of the configs history. Before adding
+        // new element, we have to remove the oldest one.
+        if (configs_.size() > CONFIG_LIST_SIZE) {
+            SrvConfigList::iterator it = configs_.begin();
+            std::advance(it, configs_.size() - CONFIG_LIST_SIZE);
+            configs_.erase(configs_.begin(), it);
+        }
+    }
+}
+
+void
+CfgMgr::rollback() {
+    ensureCurrentAllocated();
+    if (!configuration_->sequenceEquals(*configs_.back())) {
+        configs_.pop_back();
+    }
+}
+
+void
+CfgMgr::revert(const size_t index) {
+    ensureCurrentAllocated();
+    if (index == 0) {
+        isc_throw(isc::OutOfRange, "invalid commit index 0 when reverting"
+                  " to an old configuration");
+    } else if (index > configs_.size() - 1) {
+        isc_throw(isc::OutOfRange, "unable to revert to commit index '"
+                  << index << "', only '" << configs_.size() - 1
+                  << "' previous commits available");
+    }
+
+    // Let's rollback an existing configuration to make sure that the last
+    // configuration on the list is the current one. Note that all remaining
+    // operations in this function should be exception free so there shouldn't
+    // be a problem that the revert operation fails and the staging
+    // configuration is destroyed by this rollback.
+    rollback();
+
+    // Get the iterator to the current configuration and then advance to the
+    // desired one.
+    SrvConfigList::const_reverse_iterator it = configs_.rbegin();
+    std::advance(it, index);
+
+    // Copy the desired configuration to the new staging configuration. The
+    // staging configuration is re-created here because we rolled back earlier
+    // in this function.
+    (*it)->copy(*getStagingCfg());
+
+    // Make the staging configuration a current one.
+    commit();
+}
+
+ConstSrvConfigPtr
+CfgMgr::getCurrentCfg() {
+    ensureCurrentAllocated();
     return (configuration_);
 }
 
+SrvConfigPtr
+CfgMgr::getStagingCfg() {
+    ensureCurrentAllocated();
+    if (configuration_->sequenceEquals(*configs_.back())) {
+        uint32_t sequence = configuration_->getSequence();
+        configs_.push_back(SrvConfigPtr(new SrvConfig(++sequence)));
+    }
+    return (configs_.back());
+}
+
 CfgMgr::CfgMgr()
     : datadir_(DHCP_DATA_DIR), echo_v4_client_id_(true),
-      d2_client_mgr_(), configuration_(new Configuration()) {
+      d2_client_mgr_(), verbose_mode_(false) {
     // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
     // Note: the definition of DHCP_DATA_DIR needs to include quotation marks
     // See AM_CPPFLAGS definition in Makefile.am

+ 153 - 6
src/lib/dhcpsrv/cfgmgr.h

@@ -24,7 +24,7 @@
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>
-#include <dhcpsrv/configuration.h>
+#include <dhcpsrv/srv_config.h>
 #include <util/buffer.h>
 
 #include <boost/shared_ptr.hpp>
@@ -95,6 +95,11 @@ public:
 class CfgMgr : public boost::noncopyable {
 public:
 
+    /// @brief A number of configurations held by @c CfgMgr.
+    ///
+    /// @todo Make it configurable.
+    static const size_t CONFIG_LIST_SIZE;
+
     /// @brief returns a single instance of Configuration Manager
     ///
     /// CfgMgr is a singleton and this method is the only way of
@@ -373,11 +378,130 @@ public:
     /// @return a reference to the DHCP-DDNS manager.
     D2ClientMgr& getD2ClientMgr();
 
+    /// @name Methods managing the collection of configurations.
+    ///
+    /// The following methods manage the process of preparing a configuration
+    /// without affecting a currently used configuration and then commiting
+    /// the configuration to replace current configuration atomically.
+    /// They also allow for keeping a history of previous configurations so
+    /// as the @c CfgMgr can revert to the historical configuration when
+    /// required.
+    ///
+    /// @todo Migrate all configuration parameters to use the model supported
+    /// by these functions.
+    ///
+    /// @todo Make the size of the configurations history configurable.
+    ///
+    //@{
+
+    /// @brief Removes current, staging and all previous configurations.
+    ///
+    /// This function removes all configurations, including current and
+    /// staging configurations. It creates a new current configuration with
+    /// default settings.
+    ///
+    /// This function is exception safe.
+    void clear();
+
+    /// @brief Commits the staging configuration.
+    ///
+    /// The staging configuration becomes current configuration when this
+    /// function is called. It removes the oldest configuration held in the
+    /// history so as the size of the list of configuration does not exceed
+    /// the @c CONFIG_LIST_SIZE.
+    ///
+    /// This function is exception safe.
+    void commit();
+
+    /// @brief Removes staging configuration.
+    ///
+    /// This function should be called when there is a staging configuration
+    /// (likely created in the previous configuration attempt) but the entirely
+    /// new configuration should be created. It removes the existing staging
+    /// configuration and the next call to @c CfgMgr::getStagingCfg will return a
+    /// fresh (default) configuration.
+    ///
+    /// This function is exception safe.
+    void rollback();
+
+    /// @brief Reverts to one of the previous configurations.
+    ///
+    /// This function reverts to selected previous configuration. The previous
+    /// configuration is entirely copied to a new @c SrvConfig instance. This
+    /// new instance has a unique sequence id (sequence id is not copied). The
+    /// previous configuration (being copied) is not modified by this operation.
+    ///
+    /// The configuration to be copied is identified by the index value which
+    /// is the distance between the current (most recent) and desired
+    /// configuration. If the index is out of range an exception is thrown.
+    ///
+    /// @warning Revert operation will rollback any changes to the staging
+    /// configuration (if it exists).
+    ///
+    /// @param index A distance from the current configuration to the
+    /// past configuration to be reverted. The minimal value is 1 which points
+    /// to the nearest configuration.
+    ///
+    /// @throw isc::OutOfRange if the specified index is out of range.
+    void revert(const size_t index);
 
-    /// @brief Returns the current configuration.
+    /// @brief Returns a pointer to the current configuration.
     ///
-    /// @return a pointer to the current configuration.
-    ConfigurationPtr getConfiguration();
+    /// This function returns pointer to the current configuration. If the
+    /// current configuration is not set it will create a default configuration
+    /// and return it. Current configuration returned is read-only.
+    ///
+    /// @return Non-null const pointer to the current configuration.
+    ConstSrvConfigPtr getCurrentCfg();
+
+    /// @brief Returns a pointer to the staging configuration.
+    ///
+    /// The staging configuration is used by the configuration parsers to
+    /// create new configuration. The staging configuration doesn't affect the
+    /// server's operation until it is committed. The staging configuration
+    /// is a non-const object which can be modified by the caller.
+    ///
+    /// Multiple consecutive calls to this function return the same object
+    /// which can be modified from various places of the code (e.g. various
+    /// configuration parsers).
+    ///
+    /// @return non-null pointer to the staging configuration.
+    SrvConfigPtr getStagingCfg();
+
+    //@}
+
+    /// @name Methods setting/accessing global configuration for the process.
+    ///
+    //@{
+    /// @brief Sets verbose mode.
+    ///
+    /// @param verbose A boolean value indicating if the process should run
+    /// in verbose (true) or non-verbose mode.
+    void setVerbose(const bool verbose) {
+        verbose_mode_ = verbose;
+    }
+
+    /// @brief Checks if the process has been run in verbose mode.
+    ///
+    /// @return true if verbose mode enabled, false otherwise.
+    bool isVerbose() const {
+        return (verbose_mode_);
+    }
+
+    /// @brief Sets the default logger name.
+    ///
+    /// This name is used in cases when a user doesn't provide a configuration
+    /// for logger in the Kea configuration file.
+    void setDefaultLoggerName(const std::string& name) {
+        default_logger_name_ = name;
+    }
+
+    /// @brief Returns default logger name.
+    std::string getDefaultLoggerName() const {
+        return (default_logger_name_);
+    }
+
+    //@}
 
 protected:
 
@@ -410,6 +534,13 @@ protected:
 
 private:
 
+    /// @brief Checks if current configuration is created and creates it if needed.
+    ///
+    /// This private method is called to ensure that the current configuration
+    /// is created. If current configuration is not set, it creates the
+    /// default current configuration.
+    void ensureCurrentAllocated();
+
     /// @brief Checks that the IPv4 subnet with the given id already exists.
     ///
     /// @param subnet Subnet for which this function will check if the other
@@ -446,13 +577,29 @@ private:
     /// @brief Manages the DHCP-DDNS client and its configuration.
     D2ClientMgr d2_client_mgr_;
 
-    /// @brief Configuration
+    /// @brief Server configuration
     ///
     /// This is a structure that will hold all configuration.
     /// @todo: migrate all other parameters to that structure.
     /// @todo: maybe this should be a vector<Configuration>, so we could keep
     ///        previous configurations and do a rollback if needed?
-    ConfigurationPtr configuration_;
+    SrvConfigPtr configuration_;
+
+    /// @name Configuration List.
+    ///
+    //@{
+    /// @brief Server configuration list type.
+    typedef std::list<SrvConfigPtr> SrvConfigList;
+
+    /// @brief Container holding all previous and current configurations.
+    SrvConfigList configs_;
+    //@}
+
+    /// @brief Indicates if a process has been ran in the verbose mode.
+    bool verbose_mode_;
+
+    /// @brief Default logger name.
+    std::string default_logger_name_;
 };
 
 } // namespace isc::dhcp

+ 0 - 64
src/lib/dhcpsrv/configuration.cc

@@ -1,64 +0,0 @@
-// Copyright (C) 2014 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 <dhcpsrv/cfgmgr.h>
-#include <dhcpsrv/configuration.h>
-#include <sstream>
-
-namespace isc {
-namespace dhcp {
-
-std::string
-Configuration::getConfigSummary(const uint32_t selection) const {
-    std::ostringstream s;
-    size_t subnets_num;
-    if ((selection & CFGSEL_SUBNET4) == CFGSEL_SUBNET4) {
-        subnets_num = CfgMgr::instance().getSubnets4()->size();
-        if (subnets_num > 0) {
-            s << "added IPv4 subnets: " << subnets_num;
-        } else {
-            s << "no IPv4 subnets!";
-        }
-        s << "; ";
-    }
-
-    if ((selection & CFGSEL_SUBNET6) == CFGSEL_SUBNET6) {
-        subnets_num = CfgMgr::instance().getSubnets6()->size();
-        if (subnets_num > 0) {
-            s << "added IPv6 subnets: " << subnets_num;
-        } else {
-            s << "no IPv6 subnets!";
-        }
-        s << "; ";
-    }
-
-    if ((selection & CFGSEL_DDNS) == CFGSEL_DDNS) {
-        bool ddns_enabled = CfgMgr::instance().ddnsEnabled();
-        s << "DDNS: " << (ddns_enabled ? "enabled" : "disabled") << "; ";
-    }
-
-    if (s.tellp() == static_cast<std::streampos>(0)) {
-        s << "no config details available";
-    }
-
-    std::string summary = s.str();
-    size_t last_separator_pos = summary.find_last_of(";");
-    if (last_separator_pos == summary.length() - 2) {
-        summary.erase(last_separator_pos);
-    }
-    return (summary);
-}
-
-}
-}

+ 0 - 149
src/lib/dhcpsrv/configuration.h

@@ -1,149 +0,0 @@
-// Copyright (C) 2014 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 DHCPSRV_CONFIGURATION_H
-#define DHCPSRV_CONFIGURATION_H
-
-#include <dhcpsrv/cfg_iface.h>
-#include <log/logger_level.h>
-#include <boost/shared_ptr.hpp>
-#include <vector>
-#include <stdint.h>
-
-namespace isc {
-namespace dhcp {
-
-class CfgMgr;
-
-/// @brief Defines single logging destination
-///
-/// This structure is used to keep log4cplus configuration parameters.
-struct LoggingDestination {
-
-    /// @brief defines logging destination output
-    ///
-    /// Values accepted are: stdout, stderr, syslog, syslog:name.
-    /// Any other destination will be considered a file name.
-    std::string output_;
-
-    /// @brief Maximum number of log files in rotation
-    int maxver_;
-
-    /// @brief Maximum log file size
-    uint64_t maxsize_;
-};
-
-/// @brief structure that describes one logging entry
-///
-/// This is a structure that conveys one logger entry configuration.
-/// The structure in JSON form has the following syntax:
-///        {
-///            "name": "*",
-///            "output_options": [
-///                {
-///                    "output": "/path/to/the/logfile.log",
-///                    "maxver": 8,
-///                    "maxsize": 204800
-///                }
-///            ],
-///            "severity": "WARN",
-///            "debuglevel": 99
-///        },
-struct LoggingInfo {
-
-    /// @brief logging name
-    std::string name_;
-
-    /// @brief describes logging severity
-    isc::log::Severity severity_;
-
-    /// @brief debuglevel (used when severity_ == DEBUG)
-    ///
-    /// We use range 0(least verbose)..99(most verbose)
-    int debuglevel_;
-
-    /// @brief specific logging destinations
-    std::vector<LoggingDestination> destinations_;
-};
-
-/// @brief storage for logging information in log4cplus format
-typedef std::vector<isc::dhcp::LoggingInfo> LoggingInfoStorage;
-
-/// @brief Specifies current DHCP configuration
-///
-/// @todo Migrate all other configuration parameters from cfgmgr.h here
-struct Configuration {
-
-    /// @name Constants for selection of parameters returned by @c getConfigSummary
-    ///
-    //@{
-    /// Nothing selected
-    static const uint32_t CFGSEL_NONE    = 0x00000000;
-    /// Number of IPv4 subnets
-    static const uint32_t CFGSEL_SUBNET4 = 0x00000001;
-    /// Number of IPv6 subnets
-    static const uint32_t CFGSEL_SUBNET6 = 0x00000002;
-    /// Number of enabled ifaces
-    static const uint32_t CFGSEL_IFACE4  = 0x00000004;
-    /// Number of v6 ifaces
-    static const uint32_t CFGSEL_IFACE6  = 0x00000008;
-    /// DDNS enabled/disabled
-    static const uint32_t CFGSEL_DDNS    = 0x00000010;
-    /// Number of all subnets
-    static const uint32_t CFGSEL_SUBNET  = 0x00000003;
-    /// IPv4 related config
-    static const uint32_t CFGSEL_ALL4    = 0x00000015;
-    /// IPv6 related config
-    static const uint32_t CFGSEL_ALL6    = 0x0000001A;
-    /// Whole config
-    static const uint32_t CFGSEL_ALL     = 0xFFFFFFFF;
-    //@}
-
-    /// @brief logging specific information
-    LoggingInfoStorage logging_info_;
-
-    /// @brief Interface configuration.
-    ///
-    /// Used to select interfaces on which the DHCP server will listen to
-    /// queries.
-    CfgIface cfg_iface_;
-
-    /// @brief Returns summary of the configuration in the textual format.
-    ///
-    /// This method returns the brief text describing the current configuration.
-    /// It may be used for logging purposes, e.g. when the new configuration is
-    /// committed to notify a user about the changes in configuration.
-    ///
-    /// @todo Currently this method uses @c CfgMgr accessors to get the
-    /// configuration parameters. Once these parameters are migrated from the
-    /// @c CfgMgr this method will have to be modified accordingly.
-    ///
-    /// @todo Implement reporting a summary of interfaces being used for
-    /// receiving and sending DHCP messages. This will be implemented with
-    /// ticket #3512.
-    ///
-    /// @param selection Is a bitfield which describes the parts of the
-    /// configuration to be returned.
-    ///
-    /// @return Summary of the configuration in the textual format.
-    std::string getConfigSummary(const uint32_t selection) const;
-};
-
-/// @brief pointer to the configuration
-typedef boost::shared_ptr<Configuration> ConfigurationPtr;
-
-} // namespace isc::dhcp
-} // namespace isc
-
-#endif // DHCPSRV_CONFIGURATION_H

+ 18 - 32
src/lib/dhcpsrv/daemon.cc

@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <config.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/daemon.h>
 #include <exceptions/exceptions.h>
 #include <cc/data.h>
@@ -31,7 +32,7 @@ namespace dhcp {
 std::string Daemon::config_file_ = "";
 
 Daemon::Daemon()
-    : signal_set_(), signal_handler_(), verbose_(false) {
+    : signal_set_(), signal_handler_() {
 }
 
 Daemon::~Daemon() {
@@ -56,40 +57,25 @@ void Daemon::handleSignal() {
 }
 
 void Daemon::configureLogger(const isc::data::ConstElementPtr& log_config,
-                             const ConfigurationPtr& storage,
-                             bool verbose) {
-
-    // This is utility class that translates JSON structures into formats
-    // understandable by log4cplus.
-    LogConfigParser parser(storage);
-
-    if (!log_config) {
-        // There was no logger configuration. Let's clear any config
-        // and revert to the default.
-
-        parser.applyDefaultConfiguration(verbose); // Set up default logging
-        return;
+                             const SrvConfigPtr& storage) {
+
+    if (log_config) {
+        isc::data::ConstElementPtr loggers = log_config->get("loggers");
+        if (loggers) {
+            LogConfigParser parser(storage);
+            parser.parseConfiguration(loggers, CfgMgr::instance().isVerbose());
+        }
     }
+}
 
-    isc::data::ConstElementPtr loggers;
-    loggers = log_config->get("loggers");
-    if (!loggers) {
-        // There is Logging structure, but it doesn't have loggers
-        // array in it. Let's clear any old logging configuration
-        // we may have and revert to the default.
-
-        parser.applyDefaultConfiguration(verbose); // Set up default logging
-        return;
-    }
-
-    // Translate JSON structures into log4cplus formats
-    parser.parseConfiguration(loggers, verbose);
-
-    // Apply the configuration
+void
+Daemon::setVerbose(bool verbose) {
+    CfgMgr::instance().setVerbose(verbose);
+}
 
-    /// @todo: Once configuration unrolling is implemented,
-    /// this call will be moved to a separate method.
-    parser.applyConfiguration();
+bool
+Daemon::getVerbose() const {
+    return (CfgMgr::instance().isVerbose());
 }
 
 };

+ 4 - 12
src/lib/dhcpsrv/daemon.h

@@ -17,7 +17,7 @@
 
 #include <config.h>
 #include <cc/data.h>
-#include <dhcpsrv/configuration.h>
+#include <dhcpsrv/srv_config.h>
 #include <util/signal_set.h>
 #include <boost/noncopyable.hpp>
 #include <string>
@@ -128,10 +128,8 @@ public:
     ///
     /// @param log_config JSON structures that describe logging
     /// @param storage configuration will be stored here
-    /// @param verbose specifies if verbose mode should be enabled
     static void configureLogger(const isc::data::ConstElementPtr& log_config,
-                                const isc::dhcp::ConfigurationPtr& storage,
-                                bool verbose);
+                                const isc::dhcp::SrvConfigPtr& storage);
 
     /// @brief Sets or clears verbose mode
     ///
@@ -140,16 +138,12 @@ public:
     /// config file are ignored.
     ///
     /// @param verbose specifies if verbose should be set or not
-    void setVerbose(bool verbose) {
-        verbose_ = verbose;
-    }
+    void setVerbose(const bool verbose);
 
     /// @brief Returns if running in verbose mode
     ///
     /// @return verbose mode
-    bool getVerbose() const {
-        return (verbose_);
-    }
+    bool getVerbose() const;
 
     /// @brief returns Kea version on stdout and exits.
     ///
@@ -195,8 +189,6 @@ private:
     /// @brief Config file name or empty if config file not used.
     static std::string config_file_;
 
-    /// @brief Verbose mode
-    bool verbose_;
 };
 
 }; // end of isc::dhcp namespace

+ 8 - 21
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -179,8 +179,9 @@ template <> void ValueParser<std::string>::build(ConstElementPtr value) {
 // ******************** InterfaceListConfigParser *************************
 
 InterfaceListConfigParser::
-InterfaceListConfigParser(const std::string& param_name)
-    : param_name_(param_name) {
+InterfaceListConfigParser(const std::string& param_name,
+                          ParserContextPtr global_context)
+    : param_name_(param_name), global_context_(global_context) {
     if (param_name_ != "interfaces") {
         isc_throw(BadValue, "Internal error. Interface configuration "
             "parser called for the wrong parameter: " << param_name);
@@ -189,38 +190,24 @@ InterfaceListConfigParser(const std::string& param_name)
 
 void
 InterfaceListConfigParser::build(ConstElementPtr value) {
-    // Copy the current interface configuration.
-    ConfigurationPtr config = CfgMgr::instance().getConfiguration();
-    cfg_iface_ = config->cfg_iface_;
-    cfg_iface_.reset();
+    CfgIface cfg_iface;
     BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
         std::string iface_name = iface->stringValue();
         try {
-            cfg_iface_.use(iface_name);
+            cfg_iface.use(global_context_->universe_ == Option::V4 ?
+                          AF_INET : AF_INET6, iface_name);
 
         } catch (const std::exception& ex) {
             isc_throw(DhcpConfigError, "Failed to select interface: "
                       << ex.what() << " (" << value->getPosition() << ")");
         }
     }
+    CfgMgr::instance().getStagingCfg()->setCfgIface(cfg_iface);
 }
 
 void
 InterfaceListConfigParser::commit() {
-    // Use the new configuration created in a build time.
-    CfgMgr::instance().getConfiguration()->cfg_iface_ = cfg_iface_;
-}
-
-bool
-InterfaceListConfigParser::isIfaceAdded(const std::string& iface) const {
-
-    for (IfaceListStorage::const_iterator it = interfaces_.begin();
-         it != interfaces_.end(); ++it) {
-        if (iface == *it) {
-            return (true);
-        }
-    }
-    return (false);
+    // Nothing to do.
 }
 
 // ******************** HooksLibrariesParser *************************

+ 6 - 17
src/lib/dhcpsrv/dhcp_parsers.h

@@ -396,8 +396,10 @@ public:
     /// "interface" parameter only. All other types will throw exception.
     ///
     /// @param param_name name of the configuration parameter being parsed
+    /// @param global_context Global parser context.
     /// @throw BadValue if supplied parameter name is not "interface"
-    InterfaceListConfigParser(const std::string& param_name);
+    InterfaceListConfigParser(const std::string& param_name,
+                              ParserContextPtr global_context);
 
     /// @brief parses parameters value
     ///
@@ -407,29 +409,16 @@ public:
     /// @param value pointer to the content of parsed values
     virtual void build(isc::data::ConstElementPtr value);
 
-    /// @brief Assignes a parsed list of interfaces to the configuration.
-    ///
-    /// This is exception safe operation.
+    /// @brief Does nothing.
     virtual void commit();
 
 private:
-    /// @brief Check that specified interface exists in
-    /// @c InterfaceListConfigParser::interfaces_.
-    ///
-    /// @param iface A name of the interface.
-    ///
-    /// @return true if specified interface name was found.
-    bool isIfaceAdded(const std::string& iface) const;
-
-    /// contains list of network interfaces
-    typedef std::list<std::string> IfaceListStorage;
-    IfaceListStorage interfaces_;
 
     // Parsed parameter name
     std::string param_name_;
 
-    /// Holds the configuration created during
-    CfgIface cfg_iface_;
+    /// Global parser context.
+    ParserContextPtr global_context_;
 };
 
 /// @brief Parser for hooks library list

+ 42 - 6
src/lib/dhcpsrv/libdhcpsrv.dox

@@ -1,4 +1,4 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012, 2014 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
@@ -39,11 +39,47 @@ only available backend is MySQL (see \ref isc::dhcp::MySqlLeaseMgr).
 
 @section cfgmgr Configuration Manager
 
-Configuration Manager (\ref isc::dhcp::CfgMgr) stores configuration information
-necessary for DHCPv4 and DHCPv6 server operation. In particular, it stores
-subnets (\ref isc::dhcp::Subnet4 and \ref isc::dhcp::Subnet6) together with
-their pools (\ref isc::dhcp::Pool4 and \ref isc::dhcp::Pool6), options and
-other information specified by the user in the Kea configuration.
+Configuration Manager (\ref isc::dhcp::CfgMgr) is a singleton object which
+holds configuration information necessary for the operation of Kea daemons.
+A complete collection of information for the daemon is stored in the
+\ref isc::dhcp::SrvConfig object. Internally, the Configuration Manager
+holds a list of \ref isc::dhcp::SrvConfig objects, from which one
+is marked as "current configuration".
+
+When the server starts up or is being reconfigured a new
+\ref isc::dhcp::SrvConfig object, referred to as "staging configuration",
+is created. The staging configuration is held at the tip of the list of
+configurations. The object can be accessed by calling the
+\ref isc::dhcp::CfgMgr::getStagingCfg. This object can be accessed
+from different stages of the configuration parsing and modified as needed.
+Modifications of the staging configuration do not affect the current
+configuration. The staging configuration is unused until the
+\ref isc::dhcp::CfgMgr::commit function is called. This exception safe method
+marks the staging object as "current configuration". The const pointer to the
+current configuration can be accessed by calling a
+\ref isc::dhcp::CfgMgr::getCurrentCfg.
+
+The staging configuration can be discarded at any time before it is committed
+by calling the \ref isc::dhcp::CfgMgr::rollback. This removes the
+\ref isc::dhcp::SrvConfig object from the Configuration Manager. When
+the \ref isc::dhcp::CfgMgr::getStagingCfg is called again a fresh/default
+\ref isc::dhcp::SrvConfig object is returned.
+
+The Configuration Manager stores previous configurations, i.e. configurations
+which occurred prior to the most current configuration. This is currently
+unused (except for unit tests) by the deamons, but in the future this
+mechanism can be used to trigger a rollover of the server configuration
+to a last good configuration that the administrator prefers.
+
+The previous configurations are identified by the value which specifies a
+distance between the current configuration and the previous
+configuration. For example: the value of 1 identifies an immediate
+predecessor of the current configuration, the value of 2 identifies the
+one that occurred before it etc.
+
+@todo Currently, only a subset of configuration information is stored in
+the \ref isc::dhcp::SrvConfig object. Kea developers are actively working
+on migrating the other configuration parameters to it.
 
 @section allocengine Allocation Engine
 

+ 8 - 4
src/lib/dhcpsrv/logging.cc

@@ -26,7 +26,7 @@ using namespace isc::log;
 namespace isc {
 namespace dhcp {
 
-LogConfigParser::LogConfigParser(const ConfigurationPtr& storage)
+LogConfigParser::LogConfigParser(const SrvConfigPtr& storage)
     :config_(storage), verbose_(false) {
     if (!storage) {
         isc_throw(BadValue, "LogConfigParser needs a pointer to the "
@@ -55,6 +55,8 @@ void LogConfigParser::parseConfigEntry(isc::data::ConstElementPtr entry) {
     }
 
     LoggingInfo info;
+    // Remove default destinations as we are going to replace them.
+    info.clearDestinations();
 
     // Get a name
     isc::data::ConstElementPtr name_ptr = entry->get("name");
@@ -113,7 +115,7 @@ void LogConfigParser::parseConfigEntry(isc::data::ConstElementPtr entry) {
         parseOutputOptions(info.destinations_, output_options);
     }
     
-    config_->logging_info_.push_back(info);
+    config_->addLoggingInfo(info);
 }
 
 void LogConfigParser::parseOutputOptions(std::vector<LoggingDestination>& destination,
@@ -121,6 +123,7 @@ void LogConfigParser::parseOutputOptions(std::vector<LoggingDestination>& destin
     if (!output_options) {
         isc_throw(BadValue, "Missing 'output_options' structure in 'loggers'");
     }
+
     BOOST_FOREACH(ConstElementPtr output_option, output_options->listValue()) {
 
         LoggingDestination dest;
@@ -159,8 +162,9 @@ void LogConfigParser::applyConfiguration() {
     std::vector<LoggerSpecification> specs;
 
     // Now iterate through all specified loggers
-    for (LoggingInfoStorage::const_iterator it = config_->logging_info_.begin();
-         it != config_->logging_info_.end(); ++it) {
+    const LoggingInfoStorage& logging_info = config_->getLoggingInfo();
+    for (LoggingInfoStorage::const_iterator it = logging_info.begin();
+         it != logging_info.end(); ++it) {
 
         // Prepare the objects to define the logging specification
         LoggerSpecification spec(it->name_,

+ 3 - 3
src/lib/dhcpsrv/logging.h

@@ -16,7 +16,7 @@
 #define DHCPSRV_LOGGING_H
 
 #include <cc/data.h>
-#include <dhcpsrv/configuration.h>
+#include <dhcpsrv/srv_config.h>
 #include <vector>
 
 namespace isc {
@@ -52,7 +52,7 @@ public:
     /// @brief Constructor
     ///
     /// @param storage parsed logging configuration will be stored here
-    LogConfigParser(const ConfigurationPtr& storage);
+    LogConfigParser(const SrvConfigPtr& storage);
 
     /// @brief Parses specified configuration
     ///
@@ -101,7 +101,7 @@ private:
     /// @brief Configuration is stored here
     ///
     /// LogConfigParser class uses only config_->logging_info_ field.
-    ConfigurationPtr config_;
+    SrvConfigPtr config_;
 
     /// @brief Verbose mode
     ///

+ 145 - 0
src/lib/dhcpsrv/logging_info.cc

@@ -0,0 +1,145 @@
+// Copyright (C) 2014 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 <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/logging_info.h>
+#include <log/logger_name.h>
+
+using namespace isc::log;
+
+namespace isc {
+namespace dhcp {
+
+bool
+LoggingDestination::equals(const LoggingDestination& other) const {
+    return (output_ == other.output_ &&
+            maxver_ == other.maxver_ &&
+            maxsize_ == other.maxsize_);
+}
+
+LoggingInfo::LoggingInfo()
+    : name_("kea"), severity_(isc::log::INFO), debuglevel_(0) {
+    // If configuration Manager is in the verbose mode, we need to modify the
+    // default settings.
+    if (CfgMgr::instance().isVerbose()) {
+        severity_ = isc::log::DEBUG;
+        debuglevel_ = 99;
+    }
+
+    // If the process has set the non-empty name for the default logger,
+    // let's use this name.
+    std::string logger_name = CfgMgr::instance().getDefaultLoggerName();
+    if (!logger_name.empty()) {
+        name_ = logger_name;
+    }
+
+    // Add a default logging destination in case use hasn't provided a
+    // logger specification.
+    LoggingDestination dest;
+    dest.output_ = "stdout";
+    destinations_.push_back(dest);
+}
+
+bool
+LoggingInfo::equals(const LoggingInfo& other) const {
+    // If number of destinations aren't equal, the objects are not equal.
+    if (destinations_.size() != other.destinations_.size()) {
+        return (false);
+    }
+    // If there is the same number of logging destinations verify that the
+    // destinations are equal. The order doesn't matter to we don't expect
+    // that they are at the same index of the vectors.
+    for (std::vector<LoggingDestination>::const_iterator
+             it_this = destinations_.begin();
+         it_this != destinations_.end();
+         ++it_this) {
+        bool match = false;
+        for (std::vector<LoggingDestination>::const_iterator
+                 it_other = other.destinations_.begin();
+             it_other != other.destinations_.end();
+             ++it_other) {
+            if (it_this->equals(*it_other)) {
+                match = true;
+                break;
+            }
+        }
+        if (!match) {
+            return (false);
+        }
+    }
+
+    // Logging destinations are equal. Check the rest of the parameters for
+    // equality.
+    return (name_ == other.name_ &&
+            severity_ == other.severity_ &&
+            debuglevel_ == other.debuglevel_);
+}
+
+LoggerSpecification
+LoggingInfo::toSpec() const {
+    static const std::string STDOUT = "stdout";
+    static const std::string STDERR = "stderr";
+    static const std::string SYSLOG = "syslog";
+    static const std::string SYSLOG_COLON = "syslog:";
+
+    LoggerSpecification spec(name_, severity_, debuglevel_);
+
+    // Go over logger destinations and create output options accordinly.
+    for (std::vector<LoggingDestination>::const_iterator dest =
+             destinations_.begin(); dest != destinations_.end(); ++dest) {
+
+        OutputOption option;
+        // Set up output option according to destination specification
+        if (dest->output_ == STDOUT) {
+            option.destination = OutputOption::DEST_CONSOLE;
+            option.stream = OutputOption::STR_STDOUT;
+
+        } else if (dest->output_ == STDERR) {
+            option.destination = OutputOption::DEST_CONSOLE;
+            option.stream = OutputOption::STR_STDERR;
+
+        } else if (dest->output_ == SYSLOG) {
+            option.destination = OutputOption::DEST_SYSLOG;
+            // Use default specified in OutputOption constructor for the
+            // syslog destination
+
+        } else if (dest->output_.find(SYSLOG_COLON) == 0) {
+            option.destination = OutputOption::DEST_SYSLOG;
+            // Must take account of the string actually being "syslog:"
+            if (dest->output_ == SYSLOG_COLON) {
+                // The expected syntax is syslog:facility. User skipped
+                // the logging name, so we'll just use the default ("kea")
+                option.facility = isc::log::getDefaultRootLoggerName();
+
+            } else {
+                // Everything else in the string is the facility name
+                option.facility = dest->output_.substr(SYSLOG_COLON.size());
+            }
+
+        } else {
+            // Not a recognised destination, assume a file.
+            option.destination = OutputOption::DEST_FILE;
+            option.filename = dest->output_;
+        }
+
+        // ... and set the destination
+        spec.addOutputOption(option);
+    }
+
+    return (spec);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

+ 131 - 0
src/lib/dhcpsrv/logging_info.h

@@ -0,0 +1,131 @@
+// Copyright (C) 2014 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 DHCPSRV_LOGGING_INFO_H
+#define DHCPSRV_LOGGING_INFO_H
+
+#include <log/logger_level.h>
+#include <log/logger_specification.h>
+#include <stdint.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Defines single logging destination
+///
+/// This structure is used to keep log4cplus configuration parameters.
+struct LoggingDestination {
+
+    /// @brief defines logging destination output
+    ///
+    /// Values accepted are: stdout, stderr, syslog, syslog:name.
+    /// Any other destination will be considered a file name.
+    std::string output_;
+
+    /// @brief Maximum number of log files in rotation
+    int maxver_;
+
+    /// @brief Maximum log file size
+    uint64_t maxsize_;
+
+    /// @brief Compares two objects for equality.
+    ///
+    /// @param other Object to be compared with this object.
+    ///
+    /// @return true if objects are equal, false otherwise.
+    bool equals(const LoggingDestination& other) const;
+
+    /// @brief Default constructor.
+    LoggingDestination()
+        : output_("stdout"), maxver_(1), maxsize_(204800) {
+    }
+};
+
+/// @brief structure that describes one logging entry
+///
+/// This is a structure that conveys one logger entry configuration.
+/// The structure in JSON form has the following syntax:
+///        {
+///            "name": "*",
+///            "output_options": [
+///                {
+///                    "output": "/path/to/the/logfile.log",
+///                    "maxver": 8,
+///                    "maxsize": 204800
+///                }
+///            ],
+///            "severity": "WARN",
+///            "debuglevel": 99
+///        },
+struct LoggingInfo {
+
+    /// @brief logging name
+    std::string name_;
+
+    /// @brief describes logging severity
+    isc::log::Severity severity_;
+
+    /// @brief debuglevel (used when severity_ == DEBUG)
+    ///
+    /// We use range 0(least verbose)..99(most verbose)
+    int debuglevel_;
+
+    /// @brief specific logging destinations
+    std::vector<LoggingDestination> destinations_;
+
+    /// @brief Default constructor.
+    LoggingInfo();
+
+    /// @brief Removes logging destinations.
+    void clearDestinations() {
+        destinations_.clear();
+    }
+
+    /// @brief Compares two objects for equality.
+    ///
+    /// @param other An object to be compared with this object.
+    ///
+    /// @return true if objects are equal, false otherwise.
+    bool equals(const LoggingInfo& other) const;
+
+    /// @brief Compares two objects for equality.
+    ///
+    /// @param other An object to be compared with this object.
+    ///
+    /// @return true if objects are equal, false otherwise.
+    bool operator==(const LoggingInfo& other) const {
+        return (equals(other));
+    }
+
+    /// @brief Compares two objects for inequality.
+    ///
+    /// @param other An object to be compared with this object.
+    ///
+    /// @return true if objects are not equal, false otherwise.
+    bool operator!=(const LoggingInfo& other) const {
+        return (!equals(other));
+    }
+
+    /// @brief Converts logger configuration to a spec.
+    isc::log::LoggerSpecification toSpec() const;
+};
+
+/// @brief storage for logging information in log4cplus format
+typedef std::vector<isc::dhcp::LoggingInfo> LoggingInfoStorage;
+
+}
+}
+
+#endif // DHCPSRV_LOGGING_INFO_H

+ 138 - 0
src/lib/dhcpsrv/srv_config.cc

@@ -0,0 +1,138 @@
+// Copyright (C) 2014 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 <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/srv_config.h>
+#include <log/logger_manager.h>
+#include <log/logger_specification.h>
+#include <list>
+#include <sstream>
+
+using namespace isc::log;
+
+namespace isc {
+namespace dhcp {
+
+SrvConfig::SrvConfig()
+    : sequence_(0) {
+}
+
+SrvConfig::SrvConfig(uint32_t sequence)
+    : sequence_(sequence) {
+}
+
+std::string
+SrvConfig::getConfigSummary(const uint32_t selection) const {
+    std::ostringstream s;
+    size_t subnets_num;
+    if ((selection & CFGSEL_SUBNET4) == CFGSEL_SUBNET4) {
+        subnets_num = CfgMgr::instance().getSubnets4()->size();
+        if (subnets_num > 0) {
+            s << "added IPv4 subnets: " << subnets_num;
+        } else {
+            s << "no IPv4 subnets!";
+        }
+        s << "; ";
+    }
+
+    if ((selection & CFGSEL_SUBNET6) == CFGSEL_SUBNET6) {
+        subnets_num = CfgMgr::instance().getSubnets6()->size();
+        if (subnets_num > 0) {
+            s << "added IPv6 subnets: " << subnets_num;
+        } else {
+            s << "no IPv6 subnets!";
+        }
+        s << "; ";
+    }
+
+    if ((selection & CFGSEL_DDNS) == CFGSEL_DDNS) {
+        bool ddns_enabled = CfgMgr::instance().ddnsEnabled();
+        s << "DDNS: " << (ddns_enabled ? "enabled" : "disabled") << "; ";
+    }
+
+    if (s.tellp() == static_cast<std::streampos>(0)) {
+        s << "no config details available";
+    }
+
+    std::string summary = s.str();
+    size_t last_separator_pos = summary.find_last_of(";");
+    if (last_separator_pos == summary.length() - 2) {
+        summary.erase(last_separator_pos);
+    }
+    return (summary);
+}
+
+bool
+SrvConfig::sequenceEquals(const SrvConfig& other) {
+    return (getSequence() == other.getSequence());
+}
+
+void
+SrvConfig::copy(SrvConfig& new_config) const {
+    // We will entirely replace loggers in the new configuration.
+    new_config.logging_info_.clear();
+    for (LoggingInfoStorage::const_iterator it = logging_info_.begin();
+         it != logging_info_.end(); ++it) {
+        new_config.addLoggingInfo(*it);
+    }
+    // Replace interface configuration.
+    new_config.setCfgIface(cfg_iface_);
+}
+
+void
+SrvConfig::applyLoggingCfg() const {
+    /// @todo Remove the hardcoded location.
+    setenv("KEA_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1);
+
+    std::list<LoggerSpecification> specs;
+    for (LoggingInfoStorage::const_iterator it = logging_info_.begin();
+         it != logging_info_.end(); ++it) {
+        specs.push_back(it->toSpec());
+    }
+    LoggerManager manager;
+    manager.process(specs.begin(), specs.end());
+}
+
+bool
+SrvConfig::equals(const SrvConfig& other) const {
+    // If number of loggers is different, then configurations aren't equal.
+    if (logging_info_.size() != other.logging_info_.size()) {
+        return (false);
+    }
+    // Pass through all loggers and try to find the match for each of them
+    // with the loggers from the other configuration. The order doesn't
+    // matter so we can't simply compare the vectors.
+    for (LoggingInfoStorage::const_iterator this_it =
+             logging_info_.begin(); this_it != logging_info_.end();
+         ++this_it) {
+        bool match = false;
+        for (LoggingInfoStorage::const_iterator other_it =
+                 other.logging_info_.begin();
+             other_it != other.logging_info_.end(); ++other_it) {
+            if (this_it->equals(*other_it)) {
+                match = true;
+                break;
+            }
+        }
+        // No match found for the particular logger so return false.
+        if (!match) {
+            return (false);
+        }
+    }
+    // Logging information is equal between objects, so check other values.
+    return (cfg_iface_ == other.cfg_iface_);
+}
+
+}
+}

+ 230 - 0
src/lib/dhcpsrv/srv_config.h

@@ -0,0 +1,230 @@
+// Copyright (C) 2014 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 DHCPSRV_CONFIG_H
+#define DHCPSRV_CONFIG_H
+
+#include <dhcpsrv/cfg_iface.h>
+#include <dhcpsrv/logging_info.h>
+#include <boost/shared_ptr.hpp>
+#include <vector>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+class CfgMgr;
+
+
+/// @brief Specifies current DHCP configuration
+///
+/// @todo Migrate all other configuration parameters from cfgmgr.h here
+class SrvConfig {
+public:
+    /// @name Constants for selection of parameters returned by @c getConfigSummary
+    ///
+    //@{
+    /// Nothing selected
+    static const uint32_t CFGSEL_NONE    = 0x00000000;
+    /// Number of IPv4 subnets
+    static const uint32_t CFGSEL_SUBNET4 = 0x00000001;
+    /// Number of IPv6 subnets
+    static const uint32_t CFGSEL_SUBNET6 = 0x00000002;
+    /// Number of enabled ifaces
+    static const uint32_t CFGSEL_IFACE4  = 0x00000004;
+    /// Number of v6 ifaces
+    static const uint32_t CFGSEL_IFACE6  = 0x00000008;
+    /// DDNS enabled/disabled
+    static const uint32_t CFGSEL_DDNS    = 0x00000010;
+    /// Number of all subnets
+    static const uint32_t CFGSEL_SUBNET  = 0x00000003;
+    /// IPv4 related config
+    static const uint32_t CFGSEL_ALL4    = 0x00000015;
+    /// IPv6 related config
+    static const uint32_t CFGSEL_ALL6    = 0x0000001A;
+    /// Whole config
+    static const uint32_t CFGSEL_ALL     = 0xFFFFFFFF;
+    //@}
+
+    /// @brief Default constructor.
+    ///
+    /// This constructor sets configuration sequence number to 0.
+    SrvConfig();
+
+    /// @brief Constructor.
+    ///
+    /// Sets arbitrary configuration sequence number.
+    SrvConfig(uint32_t sequence);
+
+    /// @brief Returns summary of the configuration in the textual format.
+    ///
+    /// This method returns the brief text describing the current configuration.
+    /// It may be used for logging purposes, e.g. when the new configuration is
+    /// committed to notify a user about the changes in configuration.
+    ///
+    /// @todo Currently this method uses @c CfgMgr accessors to get the
+    /// configuration parameters. Once these parameters are migrated from the
+    /// @c CfgMgr this method will have to be modified accordingly.
+    ///
+    /// @todo Implement reporting a summary of interfaces being used for
+    /// receiving and sending DHCP messages. This will be implemented with
+    /// ticket #3512.
+    ///
+    /// @param selection Is a bitfield which describes the parts of the
+    /// configuration to be returned.
+    ///
+    /// @return Summary of the configuration in the textual format.
+    std::string getConfigSummary(const uint32_t selection) const;
+
+    /// @brief Returns configuration sequence number.
+    uint32_t getSequence() const {
+        return (sequence_);
+    }
+
+    /// @brief Compares configuration sequence with other sequence.
+    ///
+    /// This method compares sequence numbers of two configurations for
+    /// equality. The sequence numbers are meant to be unique, so if
+    /// they are equal it means that they point to the same configuration.
+    ///
+    /// @param other Configuration which sequence number should be
+    /// compared with the sequence number of this configuration.
+    ///
+    /// @return true if sequence numbers are equal.
+    bool sequenceEquals(const SrvConfig& other);
+
+    /// @brief Returns logging specific configuration.
+    const LoggingInfoStorage& getLoggingInfo() const {
+        return (logging_info_);
+    }
+
+    /// @brief Sets logging specific configuration.
+    ///
+    /// @param logging_info New logging configuration.
+    void addLoggingInfo(const LoggingInfo& logging_info) {
+        logging_info_.push_back(logging_info);
+    }
+
+    /// @brief Returns object which represents selection of interfaces.
+    ///
+    /// This function returns a reference to the object which represents the
+    /// set of interfaces being used to receive DHCP traffic.
+    ///
+    /// @return Object representing selection of interfaces.
+    const CfgIface& getCfgIface() const {
+        return (cfg_iface_);
+    }
+
+    /// @brief Sets the object representing selection of interfaces.
+    ///
+    /// @param cfg_iface Object representing selection of interfaces.
+    void setCfgIface(const CfgIface& cfg_iface) {
+        cfg_iface_ = cfg_iface;
+    }
+
+    /// @brief Copies the currnet configuration to a new configuration.
+    ///
+    /// This method copies the parameters stored in the configuration to
+    /// an object passed as parameter. The configuration sequence is not
+    /// copied.
+    ///
+    /// @param [out] new_config An object to which the configuration will
+    /// be copied.
+    void copy(SrvConfig& new_config) const;
+
+    /// @brief Apply logging configuration to log4cplus.
+    void applyLoggingCfg() const;
+
+    /// @name Methods and operators used to compare configurations.
+    ///
+    //@{
+    ///
+    /// @brief Compares two objects for equality.
+    ///
+    /// It ignores the configuration sequence number when checking for
+    /// equality of objects.
+    ///
+    /// @param other An object to be compared with this object.
+    ///
+    /// @return true if two objects are equal, false otherwise.
+    bool equals(const SrvConfig& other) const;
+
+    /// @brief Compares two objects for inequality.
+    ///
+    /// It ignores the configuration sequence number when checking for
+    /// inequality of objects.
+    ///
+    /// @param other An object to be compared with this object.
+    ///
+    /// @return true if two objects are not equal, false otherwise.
+    bool nequals(const SrvConfig& other) const {
+        return (!equals(other));
+    }
+
+    /// @brief Equality operator.
+    ///
+    /// It ignores the configuration sequence number when checking for
+    /// equality of objects.
+    ///
+    /// @param other An object to be compared with this object.
+    ///
+    /// @return true if two objects are equal, false otherwise.
+    bool operator==(const SrvConfig& other) const {
+        return (equals(other));
+    }
+
+    /// @param other An object to be compared with this object.
+    ///
+    /// It ignores the configuration sequence number when checking for
+    /// inequality of objects.
+    ///
+    /// @param other An object to be compared with this object.
+    ///
+    /// @return true if two objects are not equal, false otherwise.
+    bool operator!=(const SrvConfig& other) const {
+        return (nequals(other));
+    }
+
+    //@}
+
+private:
+
+    /// @brief Sequence number identifying the configuration.
+    uint32_t sequence_;
+
+    /// @brief Logging specific information.
+    LoggingInfoStorage logging_info_;
+
+    /// @brief Interface configuration.
+    ///
+    /// Used to select interfaces on which the DHCP server will listen to
+    /// queries.
+    CfgIface cfg_iface_;
+
+};
+
+/// @name Pointers to the @c SrvConfig object.
+///
+//@{
+/// @brief Non-const pointer to the @c SrvConfig.
+typedef boost::shared_ptr<SrvConfig> SrvConfigPtr;
+
+/// @brief Const pointer to the @c SrvConfig.
+typedef boost::shared_ptr<const SrvConfig> ConstSrvConfigPtr;
+//@}
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // DHCPSRV_CONFIG_H

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

@@ -55,7 +55,6 @@ libdhcpsrv_unittests_SOURCES  = run_unittests.cc
 libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
-libdhcpsrv_unittests_SOURCES += configuration_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file4_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc
@@ -69,6 +68,7 @@ libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += logging_unittest.cc
+libdhcpsrv_unittests_SOURCES += logging_info_unittest.cc
 libdhcpsrv_unittests_SOURCES += generic_lease_mgr_unittest.cc generic_lease_mgr_unittest.h
 libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc
@@ -81,6 +81,7 @@ endif
 libdhcpsrv_unittests_SOURCES += pool_unittest.cc
 libdhcpsrv_unittests_SOURCES += schema_mysql_copy.h
 libdhcpsrv_unittests_SOURCES += schema_pgsql_copy.h
+libdhcpsrv_unittests_SOURCES += srv_config_unittest.cc
 libdhcpsrv_unittests_SOURCES += subnet_unittest.cc
 libdhcpsrv_unittests_SOURCES += test_get_callout_handle.cc test_get_callout_handle.h
 libdhcpsrv_unittests_SOURCES += triplet_unittest.cc

+ 94 - 43
src/lib/dhcpsrv/tests/cfg_iface_unittest.cc

@@ -65,13 +65,13 @@ CfgIfaceTest::unicastOpen(const std::string& iface_name) const {
 // This test checks that the interface names can be explicitly selected
 // by their names and IPv4 sockets are opened on these interfaces.
 TEST_F(CfgIfaceTest, explicitNamesV4) {
-    CfgIface cfg(CfgIface::V4);
+    CfgIface cfg;
     // Specify valid interface names. There should be no error.
-    ASSERT_NO_THROW(cfg.use("eth0"));
-    ASSERT_NO_THROW(cfg.use("eth1"));
+    ASSERT_NO_THROW(cfg.use(AF_INET, "eth0"));
+    ASSERT_NO_THROW(cfg.use(AF_INET, "eth1"));
 
     // Open sockets on specified interfaces.
-    cfg.openSockets(DHCP4_SERVER_PORT);
+    cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
 
     // Sockets should be now open on eth0 and eth1, but not on loopback.
     EXPECT_TRUE(socketOpen("eth0", AF_INET));
@@ -91,9 +91,9 @@ TEST_F(CfgIfaceTest, explicitNamesV4) {
 
     // Reset configuration and select only one interface this time.
     cfg.reset();
-    ASSERT_NO_THROW(cfg.use("eth1"));
+    ASSERT_NO_THROW(cfg.use(AF_INET, "eth1"));
 
-    cfg.openSockets(DHCP4_SERVER_PORT);
+    cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
 
     // Socket should be open on eth1 only.
     EXPECT_FALSE(socketOpen("eth0", AF_INET));
@@ -105,13 +105,13 @@ TEST_F(CfgIfaceTest, explicitNamesV4) {
 // This test checks that the interface names can be explicitly selected
 // by their names and IPv6 sockets are opened on these interfaces.
 TEST_F(CfgIfaceTest, explicitNamesV6) {
-    CfgIface cfg(CfgIface::V6);
+    CfgIface cfg;
     // Specify valid interface names. There should be no error.
-    ASSERT_NO_THROW(cfg.use("eth0"));
-    ASSERT_NO_THROW(cfg.use("eth1"));
+    ASSERT_NO_THROW(cfg.use(AF_INET6, "eth0"));
+    ASSERT_NO_THROW(cfg.use(AF_INET6, "eth1"));
 
     // Open sockets on specified interfaces.
-    cfg.openSockets(DHCP6_SERVER_PORT);
+    cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
 
     // Sockets should be now open on eth0 and eth1, but not on loopback.
     EXPECT_TRUE(socketOpen("eth0", AF_INET6));
@@ -131,9 +131,9 @@ TEST_F(CfgIfaceTest, explicitNamesV6) {
 
     // Reset configuration and select only one interface this time.
     cfg.reset();
-    ASSERT_NO_THROW(cfg.use("eth1"));
+    ASSERT_NO_THROW(cfg.use(AF_INET6, "eth1"));
 
-    cfg.openSockets(DHCP6_SERVER_PORT);
+    cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
 
     // Socket should be open on eth1 only.
     EXPECT_FALSE(socketOpen("eth0", AF_INET6));
@@ -145,10 +145,10 @@ TEST_F(CfgIfaceTest, explicitNamesV6) {
 // This test checks that the wildcard interface name can be specified to
 // select all interfaces to open IPv4 sockets.
 TEST_F(CfgIfaceTest, wildcardV4) {
-    CfgIface cfg(CfgIface::V4);
-    ASSERT_NO_THROW(cfg.use("*"));
+    CfgIface cfg;
+    ASSERT_NO_THROW(cfg.use(AF_INET, "*"));
 
-    cfg.openSockets(DHCP4_SERVER_PORT);
+    cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
 
     // Sockets should be now open on eth0 and eth1, but not on loopback.
     EXPECT_TRUE(socketOpen("eth0", AF_INET));
@@ -164,10 +164,10 @@ TEST_F(CfgIfaceTest, wildcardV4) {
 // This test checks that the wildcard interface name can be specified to
 // select all interfaces to open IPv6 sockets.
 TEST_F(CfgIfaceTest, wildcardV6) {
-    CfgIface cfg(CfgIface::V6);
-    ASSERT_NO_THROW(cfg.use("*"));
+    CfgIface cfg;
+    ASSERT_NO_THROW(cfg.use(AF_INET6, "*"));
 
-    cfg.openSockets(DHCP4_SERVER_PORT);
+    cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
 
     // Sockets should be now open on eth0 and eth1, but not on loopback.
     EXPECT_TRUE(socketOpen("eth0", AF_INET6));
@@ -184,14 +184,14 @@ TEST_F(CfgIfaceTest, wildcardV6) {
 // the interface on which the socket bound to link local address is also
 // opened.
 TEST_F(CfgIfaceTest, validUnicast) {
-    CfgIface cfg(CfgIface::V6);
+    CfgIface cfg;
 
     // One socket will be opened on link-local address, one on unicast but
     // on the same interface.
-    ASSERT_NO_THROW(cfg.use("eth0"));
-    ASSERT_NO_THROW(cfg.use("eth0/2001:db8:1::1"));
+    ASSERT_NO_THROW(cfg.use(AF_INET6, "eth0"));
+    ASSERT_NO_THROW(cfg.use(AF_INET6, "eth0/2001:db8:1::1"));
 
-    cfg.openSockets(DHCP6_SERVER_PORT);
+    cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
 
     EXPECT_TRUE(socketOpen("eth0", AF_INET6));
     EXPECT_TRUE(unicastOpen("eth0"));
@@ -199,28 +199,79 @@ TEST_F(CfgIfaceTest, validUnicast) {
 
 // Test that when invalid interface names are specified an exception is thrown.
 TEST_F(CfgIfaceTest, invalidValues) {
-    CfgIface cfg(CfgIface::V4);
-    ASSERT_THROW(cfg.use(""), InvalidIfaceName);
-    ASSERT_THROW(cfg.use(" "), InvalidIfaceName);
-    ASSERT_THROW(cfg.use("bogus"), NoSuchIface);
-
-    ASSERT_NO_THROW(cfg.use("eth0"));
-    ASSERT_THROW(cfg.use("eth0"), DuplicateIfaceName);
-
-    ASSERT_THROW(cfg.use("eth0/2001:db8:1::1"), InvalidIfaceName);
-
-    cfg.setFamily(CfgIface::V6);
-
-    ASSERT_THROW(cfg.use("eth0/"), InvalidIfaceName);
-    ASSERT_THROW(cfg.use("/2001:db8:1::1"), InvalidIfaceName);
-    ASSERT_THROW(cfg.use("*/2001:db8:1::1"), InvalidIfaceName);
-    ASSERT_THROW(cfg.use("bogus/2001:db8:1::1"), NoSuchIface);
-    ASSERT_THROW(cfg.use("eth0/fe80::3a60:77ff:fed5:cdef"), InvalidIfaceName);
-    ASSERT_THROW(cfg.use("eth0/fe80::3a60:77ff:fed5:cdef"), InvalidIfaceName);
-    ASSERT_THROW(cfg.use("eth0/2001:db8:1::2"), NoSuchAddress);
+    CfgIface cfg;
+    ASSERT_THROW(cfg.use(AF_INET, ""), InvalidIfaceName);
+    ASSERT_THROW(cfg.use(AF_INET, " "), InvalidIfaceName);
+    ASSERT_THROW(cfg.use(AF_INET, "bogus"), NoSuchIface);
+
+    ASSERT_NO_THROW(cfg.use(AF_INET, "eth0"));
+    ASSERT_THROW(cfg.use(AF_INET, "eth0"), DuplicateIfaceName);
+
+    ASSERT_THROW(cfg.use(AF_INET, "eth0/2001:db8:1::1"), InvalidIfaceName);
+
+    ASSERT_THROW(cfg.use(AF_INET6, "eth0/"), InvalidIfaceName);
+    ASSERT_THROW(cfg.use(AF_INET6, "/2001:db8:1::1"), InvalidIfaceName);
+    ASSERT_THROW(cfg.use(AF_INET6, "*/2001:db8:1::1"), InvalidIfaceName);
+    ASSERT_THROW(cfg.use(AF_INET6, "bogus/2001:db8:1::1"), NoSuchIface);
+    ASSERT_THROW(cfg.use(AF_INET6, "eth0/fe80::3a60:77ff:fed5:cdef"),
+                 InvalidIfaceName);
+    ASSERT_THROW(cfg.use(AF_INET6, "eth0/fe80::3a60:77ff:fed5:cdef"),
+                 InvalidIfaceName);
+    ASSERT_THROW(cfg.use(AF_INET6, "eth0/2001:db8:1::2"), NoSuchAddress);
+    ASSERT_NO_THROW(cfg.use(AF_INET6, "*"));
+    ASSERT_THROW(cfg.use(AF_INET6, "*"), DuplicateIfaceName);
+}
 
-    ASSERT_NO_THROW(cfg.use("*"));
-    ASSERT_THROW(cfg.use("*"), DuplicateIfaceName);
+// Test that the equality and inequality operators work fine for CfgIface.
+TEST_F(CfgIfaceTest, equality) {
+    CfgIface cfg1;
+    CfgIface cfg2;
+
+    // Initially objects must be equal.
+    EXPECT_TRUE(cfg1 == cfg2);
+    EXPECT_FALSE(cfg1 != cfg2);
+
+    // Differ by one interface.
+    cfg1.use(AF_INET, "eth0");
+    EXPECT_FALSE(cfg1 == cfg2);
+    EXPECT_TRUE(cfg1 != cfg2);
+
+    // Now interfaces should be equal.
+    cfg2.use(AF_INET, "eth0");
+    EXPECT_TRUE(cfg1 == cfg2);
+    EXPECT_FALSE(cfg1 != cfg2);
+
+    // Differ by unicast address.
+    cfg1.use(AF_INET6, "eth0/2001:db8:1::1");
+    EXPECT_FALSE(cfg1 == cfg2);
+    EXPECT_TRUE(cfg1 != cfg2);
+
+    // Differ by unicast address and one interface.
+    cfg2.use(AF_INET6, "eth1");
+    EXPECT_FALSE(cfg1 == cfg2);
+    EXPECT_TRUE(cfg1 != cfg2);
+
+    // Now, the unicast addresses are equal but still differ by one interface.
+    cfg2.use(AF_INET6, "eth0/2001:db8:1::1");
+    EXPECT_FALSE(cfg1 == cfg2);
+    EXPECT_TRUE(cfg1 != cfg2);
+
+    // They should be now back to equal.
+    cfg1.use(AF_INET6, "eth1");
+    EXPECT_TRUE(cfg1 == cfg2);
+    EXPECT_FALSE(cfg1 != cfg2);
+
+    // Even though the wildcard doesn't change anything because all interfaces
+    // are already in use, the fact that the wildcard is specified should
+    // cause them to be not equal.
+    cfg1.use(AF_INET6, "*");
+    EXPECT_FALSE(cfg1 == cfg2);
+    EXPECT_TRUE(cfg1 != cfg2);
+
+    // Finally, both are equal as they use wildacard.
+    cfg2.use(AF_INET, "*");
+    EXPECT_TRUE(cfg1 == cfg2);
+    EXPECT_FALSE(cfg1 != cfg2);
 }
 
 } // end of anonymous namespace

+ 157 - 5
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -262,9 +262,7 @@ class CfgMgrTest : public ::testing::Test {
 public:
     CfgMgrTest() {
         // make sure we start with a clean configuration
-        CfgMgr::instance().deleteSubnets4();
-        CfgMgr::instance().deleteSubnets6();
-        CfgMgr::instance().deleteOptionDefs();
+        clear();
     }
 
     /// @brief generates interface-id option based on provided text
@@ -279,9 +277,15 @@ public:
 
     ~CfgMgrTest() {
         // clean up after the test
+        clear();
+    }
+
+    void clear() {
+        CfgMgr::instance().setVerbose(false);
         CfgMgr::instance().deleteSubnets4();
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteOptionDefs();
+        CfgMgr::instance().clear();
     }
 
     /// used in client classification (or just empty container for other tests)
@@ -292,10 +296,13 @@ public:
 // it is empty by default.
 TEST_F(CfgMgrTest, configuration) {
 
-    ConfigurationPtr configuration = CfgMgr::instance().getConfiguration();
+    ConstSrvConfigPtr configuration = CfgMgr::instance().getCurrentCfg();
     ASSERT_TRUE(configuration);
+    EXPECT_TRUE(configuration->getLoggingInfo().empty());
 
-    EXPECT_TRUE(configuration->logging_info_.empty());
+    configuration = CfgMgr::instance().getStagingCfg();
+    ASSERT_TRUE(configuration);
+    EXPECT_TRUE(configuration->getLoggingInfo().empty());
 }
 
 // This test verifies that multiple option definitions can be added
@@ -1119,6 +1126,151 @@ TEST_F(CfgMgrTest, subnet6Duplication) {
 }
 
 
+// This test verifies that the configuration staging, commit and rollback works
+// as expected.
+TEST_F(CfgMgrTest, staging) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+    // Initially, the current configuration is a default one. We are going
+    // to get the current configuration a couple of times and make sure
+    // that always the same instance is returned.
+    ConstSrvConfigPtr const_config;
+    for (int i = 0; i < 5; ++i) {
+        const_config = cfg_mgr.getCurrentCfg();
+        ASSERT_TRUE(const_config) << "Returned NULL current configuration"
+            " for iteration " << i;
+        EXPECT_EQ(0, const_config->getSequence())
+            << "Returned invalid sequence number "
+            << const_config->getSequence() << " for iteration " << i;
+    }
+
+    // Try to get the new staging configuration. When getStagingCfg() is called
+    // for the first time the new instance of the staging configuration is
+    // returned. This instance is returned for every call to getStagingCfg()
+    // until commit is called.
+    SrvConfigPtr config;
+    for (int i = 0; i < 5; ++i) {
+        config = cfg_mgr.getStagingCfg();
+        ASSERT_TRUE(config) << "Returned NULL staging configuration for"
+            " iteration " << i;
+        // The sequence id is 1 for staging because it is ahead of current
+        // configuration having sequence number 0.
+        EXPECT_EQ(1, config->getSequence()) << "Returned invalid sequence"
+            " number " << config->getSequence() << " for iteration " << i;
+    }
+
+    // This should change the staging configuration so as it becomes a current
+    // one.
+    cfg_mgr.commit();
+    const_config = cfg_mgr.getCurrentCfg();
+    ASSERT_TRUE(const_config);
+    // Sequence id equal to 1 indicates that the current configuration points
+    // to the configuration that used to be a staging configuration previously.
+    EXPECT_EQ(1, const_config->getSequence());
+
+    // Create a new staging configuration. It should be assigned a new
+    // sequence id.
+    config = cfg_mgr.getStagingCfg();
+    ASSERT_TRUE(config);
+    EXPECT_EQ(2, config->getSequence());
+
+    // Let's execute commit a couple of times. The first invocation to commit
+    // changes the configuration having sequence 2 to current configuration.
+    // Other commits are no-op.
+    for (int i = 0; i < 5; ++i) {
+        cfg_mgr.commit();
+    }
+
+    // The current configuration now have sequence number 2.
+    const_config = cfg_mgr.getCurrentCfg();
+    ASSERT_TRUE(const_config);
+    EXPECT_EQ(2, const_config->getSequence());
+
+    // Clear configuration along with a history.
+    cfg_mgr.clear();
+
+    // After clearing configuration we should successfully get the
+    // new staging configuration.
+    config = cfg_mgr.getStagingCfg();
+    ASSERT_TRUE(config);
+    EXPECT_EQ(1, config->getSequence());
+
+    // Modify the staging configuration.
+    config->addLoggingInfo(LoggingInfo());
+    ASSERT_TRUE(config);
+    // The modified staging configuration should have one logger configured.
+    ASSERT_EQ(1, config->getLoggingInfo().size());
+
+    // Rollback should remove a staging configuration, including the logger.
+    ASSERT_NO_THROW(cfg_mgr.rollback());
+
+    // Make sure that the logger is not set. This is an indication that the
+    // rollback worked.
+    config = cfg_mgr.getStagingCfg();
+    ASSERT_TRUE(config);
+    EXPECT_EQ(0, config->getLoggingInfo().size());
+}
+
+// This test verifies that it is possible to revert to an old configuration.
+TEST_F(CfgMgrTest, revert) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+    // Let's create 5 unique configurations: differing by a debug level in the
+    // range of 10 to 14.
+    for (int i = 0; i < 5; ++i) {
+        SrvConfigPtr config = cfg_mgr.getStagingCfg();
+        LoggingInfo logging_info;
+        logging_info.debuglevel_ = i + 10;
+        config->addLoggingInfo(logging_info);
+        cfg_mgr.commit();
+    }
+
+    // Now we have 6 configurations with:
+    // - debuglevel = 99 (a default one)
+    // - debuglevel = 10
+    // - debuglevel = 11
+    // - debuglevel = 12
+    // - debuglevel = 13
+    // - debuglevel = 14 (current)
+
+    // Hence, the maximum index of the configuration to revert is 5 (which
+    // points to the configuration with debuglevel = 99). For the index greater
+    // than 5 we should get an exception.
+    ASSERT_THROW(cfg_mgr.revert(6), isc::OutOfRange);
+    // Value of 0 also doesn't make sense.
+    ASSERT_THROW(cfg_mgr.revert(0), isc::OutOfRange);
+
+    // We should be able to revert to configuration with debuglevel = 10.
+    ASSERT_NO_THROW(cfg_mgr.revert(4));
+    // And this configuration should be now the current one and the debuglevel
+    // of this configuration is 10.
+    EXPECT_EQ(10, cfg_mgr.getCurrentCfg()->getLoggingInfo()[0].debuglevel_);
+    EXPECT_NE(cfg_mgr.getCurrentCfg()->getSequence(), 1);
+
+    // The new set of configuration is now as follows:
+    // - debuglevel = 99
+    // - debuglevel = 10
+    // - debuglevel = 11
+    // - debuglevel = 12
+    // - debuglevel = 13
+    // - debuglevel = 14
+    // - debuglevel = 10 (current)
+    // So, reverting to configuration having index 3 means that the debug level
+    // of the current configuration will become 12.
+    ASSERT_NO_THROW(cfg_mgr.revert(3));
+    EXPECT_EQ(12, cfg_mgr.getCurrentCfg()->getLoggingInfo()[0].debuglevel_);
+}
+
+// This test verifies that the verbosity can be set and obtained from the
+// configuration manager.
+TEST_F(CfgMgrTest, verbosity) {
+    ASSERT_FALSE(CfgMgr::instance().isVerbose());
+
+    CfgMgr::instance().setVerbose(true);
+    ASSERT_TRUE(CfgMgr::instance().isVerbose());
+
+    CfgMgr::instance().setVerbose(false);
+    EXPECT_FALSE(CfgMgr::instance().isVerbose());
+}
+
 /// @todo Add unit-tests for testing:
 /// - addActiveIface() with invalid interface name
 /// - addActiveIface() with the same interface twice

+ 10 - 8
src/lib/dhcpsrv/tests/daemon_unittest.cc

@@ -16,6 +16,7 @@
 
 #include <exceptions/exceptions.h>
 #include <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/daemon.h>
 #include <dhcpsrv/logging.h>
 #include <log/logger_unittest_support.h>
@@ -68,9 +69,10 @@ TEST_F(DaemonTest, constructor) {
 // More dedicated tests are availablef for LogConfigParser class.
 // See logger_unittest.cc
 TEST_F(DaemonTest, parsingConsoleOutput) {
+    CfgMgr::instance().setVerbose(false);
 
     // Storage - parsed configuration will be stored here
-    ConfigurationPtr storage(new Configuration());
+    SrvConfigPtr storage(new SrvConfig());
 
     const char* config_txt =
     "{ \"loggers\": ["
@@ -89,18 +91,18 @@ TEST_F(DaemonTest, parsingConsoleOutput) {
 
     // Spawn a daemon and tell it to configure logger
     Daemon x;
-    EXPECT_NO_THROW(x.configureLogger(config, storage, false));
+    EXPECT_NO_THROW(x.configureLogger(config, storage));
 
     // The parsed configuration should be processed by the daemon and
     // stored in configuration storage.
-    ASSERT_EQ(1, storage->logging_info_.size());
+    ASSERT_EQ(1, storage->getLoggingInfo().size());
 
-    EXPECT_EQ("kea", storage->logging_info_[0].name_);
-    EXPECT_EQ(99, storage->logging_info_[0].debuglevel_);
-    EXPECT_EQ(isc::log::DEBUG, storage->logging_info_[0].severity_);
+    EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+    EXPECT_EQ(99, storage->getLoggingInfo()[0].debuglevel_);
+    EXPECT_EQ(isc::log::DEBUG, storage->getLoggingInfo()[0].severity_);
 
-    ASSERT_EQ(1, storage->logging_info_[0].destinations_.size());
-    EXPECT_EQ("stdout" , storage->logging_info_[0].destinations_[0].output_);
+    ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+    EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[0].output_);
 }
 
 

+ 14 - 10
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -59,8 +59,7 @@ public:
 
     /// @brief Resets selection of the interfaces from previous tests.
     void resetIfaceCfg() {
-        CfgMgr::instance().getConfiguration()->cfg_iface_.closeSockets();
-        CfgMgr::instance().getConfiguration()->cfg_iface_.reset();
+        CfgMgr::instance().clear();
     }
 };
 
@@ -225,11 +224,14 @@ TEST_F(DhcpParserTest, interfaceListParserTest) {
 
     const std::string name = "interfaces";
 
+    ParserContextPtr parser_context(new ParserContext(Option::V4));
+
     // Verify that parser constructor fails if parameter name isn't "interface"
-    EXPECT_THROW(InterfaceListConfigParser("bogus_name"), isc::BadValue);
+    EXPECT_THROW(InterfaceListConfigParser("bogus_name", parser_context),
+                 isc::BadValue);
 
     boost::scoped_ptr<InterfaceListConfigParser>
-        parser(new InterfaceListConfigParser(name));
+        parser(new InterfaceListConfigParser(name, parser_context));
     ElementPtr list_element = Element::createList();
     list_element->add(Element::create("eth0"));
 
@@ -240,9 +242,9 @@ TEST_F(DhcpParserTest, interfaceListParserTest) {
 
     // Use CfgMgr instance to check if eth0 and eth1 was added, and that
     // eth2 was not added.
-    ConfigurationPtr cfg = CfgMgr::instance().getConfiguration();
+    SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
     ASSERT_TRUE(cfg);
-    ASSERT_NO_THROW(cfg->cfg_iface_.openSockets(10000));
+    ASSERT_NO_THROW(cfg->getCfgIface().openSockets(AF_INET, 10000));
 
     EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET));
     EXPECT_FALSE(test_config.socketOpen("eth1", AF_INET));
@@ -253,13 +255,15 @@ TEST_F(DhcpParserTest, interfaceListParserTest) {
     list_element->add(Element::create("*"));
 
     // Reset parser and configuration.
-    parser.reset(new InterfaceListConfigParser(name));
-    cfg->cfg_iface_.closeSockets();
-    cfg->cfg_iface_.reset();
+    parser.reset(new InterfaceListConfigParser(name, parser_context));
+    cfg->getCfgIface().closeSockets();
+    CfgMgr::instance().clear();
 
     parser->build(list_element);
     parser->commit();
-    ASSERT_NO_THROW(cfg->cfg_iface_.openSockets(10000));
+
+    cfg = CfgMgr::instance().getStagingCfg();
+    ASSERT_NO_THROW(cfg->getCfgIface().openSockets(AF_INET, 10000));
 
     EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET));
     EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET));

+ 160 - 0
src/lib/dhcpsrv/tests/logging_info_unittest.cc

@@ -0,0 +1,160 @@
+// Copyright (C) 2014 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/cfgmgr.h>
+#include <dhcpsrv/logging_info.h>
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp;
+
+namespace {
+
+// Checks if two destinations can be compared for equality.
+TEST(LoggingDestintaion, equals) {
+    LoggingDestination dest1;
+    LoggingDestination dest2;
+
+    EXPECT_TRUE(dest1.equals(dest2));
+
+    dest1.output_ = "stderr";
+    EXPECT_FALSE(dest1.equals(dest2));
+
+    dest2.output_ = "stdout";
+    EXPECT_FALSE(dest1.equals(dest2));
+
+    dest2.output_ = "stderr";
+    EXPECT_TRUE(dest1.equals(dest2));
+
+    dest1.maxver_ = 10;
+    dest2.maxver_ = 5;
+    EXPECT_FALSE(dest1.equals(dest2));
+
+    dest2.maxver_ = 10;
+    EXPECT_TRUE(dest1.equals(dest2));
+
+    dest1.maxsize_ = 64;
+    dest2.maxsize_ = 32;
+    EXPECT_FALSE(dest1.equals(dest2));
+
+    dest1.maxsize_ = 32;
+    EXPECT_TRUE(dest1.equals(dest2));
+}
+
+/// @brief Test fixture class for testing @c LoggingInfo.
+class LoggingInfoTest : public ::testing::Test {
+public:
+
+    /// @brief Setup the test.
+    virtual void SetUp() {
+        CfgMgr::instance().setVerbose(false);
+    }
+
+    /// @brief Clear after the test.
+    virtual void TearDown() {
+        CfgMgr::instance().setVerbose(false);
+    }
+};
+
+// Checks if default logging configuration is correct.
+TEST_F(LoggingInfoTest, defaults) {
+    LoggingInfo info_non_verbose;
+    EXPECT_EQ("kea", info_non_verbose.name_);
+    EXPECT_EQ(isc::log::INFO, info_non_verbose.severity_);
+    EXPECT_EQ(0, info_non_verbose.debuglevel_);
+
+    ASSERT_EQ(1, info_non_verbose.destinations_.size());
+    EXPECT_EQ("stdout", info_non_verbose.destinations_[0].output_);
+
+    CfgMgr::instance().setVerbose(true);
+    LoggingInfo info_verbose;
+    EXPECT_EQ("kea", info_verbose.name_);
+    EXPECT_EQ(isc::log::DEBUG, info_verbose.severity_);
+    EXPECT_EQ(99, info_verbose.debuglevel_);
+
+    ASSERT_EQ(1, info_verbose.destinations_.size());
+    EXPECT_EQ("stdout", info_verbose.destinations_[0].output_);
+}
+
+// Checks if (in)equality operators work for LoggingInfo.
+TEST_F(LoggingInfoTest, equalityOperators) {
+    LoggingInfo info1;
+    LoggingInfo info2;
+
+    // Initially, both objects are the same.
+    EXPECT_TRUE(info1 == info2);
+
+    // Differ by name.
+    info1.name_ = "foo";
+    info2.name_ = "bar";
+    EXPECT_FALSE(info1 == info2);
+    EXPECT_TRUE(info1 != info2);
+
+    // Names equal.
+    info2.name_ = "foo";
+    EXPECT_TRUE(info1 == info2);
+    EXPECT_FALSE(info1 != info2);
+
+    // Differ by severity.
+    info1.severity_ = isc::log::DEBUG;
+    info2.severity_ = isc::log::INFO;
+    EXPECT_FALSE(info1 == info2);
+    EXPECT_TRUE(info1 != info2);
+
+    // Severities equal.
+    info2.severity_ = isc::log::DEBUG;
+    EXPECT_TRUE(info1 == info2);
+    EXPECT_FALSE(info1 != info2);
+
+    // Differ by debug level.
+    info1.debuglevel_ = 99;
+    info2.debuglevel_ = 1;
+    EXPECT_FALSE(info1 == info2);
+    EXPECT_TRUE(info1 != info2);
+
+    // Debug level equal.
+    info2.debuglevel_ = 99;
+    EXPECT_TRUE(info1 == info2);
+    EXPECT_FALSE(info1 != info2);
+
+    // Create two different desinations.
+    LoggingDestination dest1;
+    LoggingDestination dest2;
+    dest1.output_ = "foo";
+    dest2.output_ = "bar";
+
+    // Push destinations in some order to info1.
+    info1.destinations_.push_back(dest1);
+    info1.destinations_.push_back(dest2);
+
+    // Push in reverse order to info2. Order shouldn't matter.
+    info2.destinations_.push_back(dest2);
+    info2.destinations_.push_back(dest1);
+
+    EXPECT_TRUE(info1 == info2);
+    EXPECT_FALSE(info1 != info2);
+
+    // Change one of the destinations.
+    LoggingDestination dest3;
+    dest3.output_ = "foobar";
+
+    info2.destinations_[2] = dest3;
+
+    // The should now be unequal.
+    EXPECT_FALSE(info1 == info2);
+    EXPECT_TRUE(info1 != info2);
+
+}
+
+} // end of anonymous namespace

+ 36 - 36
src/lib/dhcpsrv/tests/logging_unittest.cc

@@ -49,10 +49,10 @@ class LoggingTest : public ::testing::Test {
 // Checks that contructor is able to process specified storage properly
 TEST_F(LoggingTest, constructor) {
 
-    ConfigurationPtr null_ptr;
+    SrvConfigPtr null_ptr;
     EXPECT_THROW(LogConfigParser parser(null_ptr), BadValue);
 
-    ConfigurationPtr nonnull(new Configuration());
+    SrvConfigPtr nonnull(new SrvConfig());
 
     EXPECT_NO_THROW(LogConfigParser parser(nonnull));
 }
@@ -76,7 +76,7 @@ TEST_F(LoggingTest, parsingConsoleOutput) {
     "    }"
     "]}";
 
-    ConfigurationPtr storage(new Configuration());
+    SrvConfigPtr storage(new SrvConfig());
 
     LogConfigParser parser(storage);
 
@@ -88,14 +88,14 @@ TEST_F(LoggingTest, parsingConsoleOutput) {
 
     EXPECT_NO_THROW(parser.parseConfiguration(config));
 
-    ASSERT_EQ(1, storage->logging_info_.size());
+    ASSERT_EQ(1, storage->getLoggingInfo().size());
 
-    EXPECT_EQ("kea", storage->logging_info_[0].name_);
-    EXPECT_EQ(99, storage->logging_info_[0].debuglevel_);
-    EXPECT_EQ(isc::log::DEBUG, storage->logging_info_[0].severity_);
+    EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+    EXPECT_EQ(99, storage->getLoggingInfo()[0].debuglevel_);
+    EXPECT_EQ(isc::log::DEBUG, storage->getLoggingInfo()[0].severity_);
 
-    ASSERT_EQ(1, storage->logging_info_[0].destinations_.size());
-    EXPECT_EQ("stdout" , storage->logging_info_[0].destinations_[0].output_);
+    ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+    EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[0].output_);
 }
 
 // Checks if the LogConfigParser class is able to transform JSON structures
@@ -116,7 +116,7 @@ TEST_F(LoggingTest, parsingFile) {
     "    }"
     "]}";
 
-    ConfigurationPtr storage(new Configuration());
+    SrvConfigPtr storage(new SrvConfig());
 
     LogConfigParser parser(storage);
 
@@ -128,14 +128,14 @@ TEST_F(LoggingTest, parsingFile) {
 
     EXPECT_NO_THROW(parser.parseConfiguration(config));
 
-    ASSERT_EQ(1, storage->logging_info_.size());
+    ASSERT_EQ(1, storage->getLoggingInfo().size());
 
-    EXPECT_EQ("kea", storage->logging_info_[0].name_);
-    EXPECT_EQ(0, storage->logging_info_[0].debuglevel_);
-    EXPECT_EQ(isc::log::INFO, storage->logging_info_[0].severity_);
+    EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+    EXPECT_EQ(0, storage->getLoggingInfo()[0].debuglevel_);
+    EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
 
-    ASSERT_EQ(1, storage->logging_info_[0].destinations_.size());
-    EXPECT_EQ("logfile.txt" , storage->logging_info_[0].destinations_[0].output_);
+    ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+    EXPECT_EQ("logfile.txt" , storage->getLoggingInfo()[0].destinations_[0].output_);
 }
 
 // Checks if the LogConfigParser class is able to transform data structures
@@ -166,7 +166,7 @@ TEST_F(LoggingTest, multipleLoggers) {
     "    }"
     "]}";
 
-    ConfigurationPtr storage(new Configuration());
+    SrvConfigPtr storage(new SrvConfig());
 
     LogConfigParser parser(storage);
 
@@ -178,19 +178,19 @@ TEST_F(LoggingTest, multipleLoggers) {
 
     EXPECT_NO_THROW(parser.parseConfiguration(config));
 
-    ASSERT_EQ(2, storage->logging_info_.size());
+    ASSERT_EQ(2, storage->getLoggingInfo().size());
 
-    EXPECT_EQ("kea", storage->logging_info_[0].name_);
-    EXPECT_EQ(0, storage->logging_info_[0].debuglevel_);
-    EXPECT_EQ(isc::log::INFO, storage->logging_info_[0].severity_);
-    ASSERT_EQ(1, storage->logging_info_[0].destinations_.size());
-    EXPECT_EQ("logfile.txt" , storage->logging_info_[0].destinations_[0].output_);
+    EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+    EXPECT_EQ(0, storage->getLoggingInfo()[0].debuglevel_);
+    EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
+    ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+    EXPECT_EQ("logfile.txt" , storage->getLoggingInfo()[0].destinations_[0].output_);
 
-    EXPECT_EQ("wombat", storage->logging_info_[1].name_);
-    EXPECT_EQ(99, storage->logging_info_[1].debuglevel_);
-    EXPECT_EQ(isc::log::DEBUG, storage->logging_info_[1].severity_);
-    ASSERT_EQ(1, storage->logging_info_[1].destinations_.size());
-    EXPECT_EQ("logfile2.txt" , storage->logging_info_[1].destinations_[0].output_);
+    EXPECT_EQ("wombat", storage->getLoggingInfo()[1].name_);
+    EXPECT_EQ(99, storage->getLoggingInfo()[1].debuglevel_);
+    EXPECT_EQ(isc::log::DEBUG, storage->getLoggingInfo()[1].severity_);
+    ASSERT_EQ(1, storage->getLoggingInfo()[1].destinations_.size());
+    EXPECT_EQ("logfile2.txt" , storage->getLoggingInfo()[1].destinations_[0].output_);
 }
 
 // Checks if the LogConfigParser class is able to transform data structures
@@ -214,7 +214,7 @@ TEST_F(LoggingTest, multipleLoggingDestinations) {
     "    }"
     "]}";
 
-    ConfigurationPtr storage(new Configuration());
+    SrvConfigPtr storage(new SrvConfig());
 
     LogConfigParser parser(storage);
 
@@ -226,14 +226,14 @@ TEST_F(LoggingTest, multipleLoggingDestinations) {
 
     EXPECT_NO_THROW(parser.parseConfiguration(config));
 
-    ASSERT_EQ(1, storage->logging_info_.size());
+    ASSERT_EQ(1, storage->getLoggingInfo().size());
 
-    EXPECT_EQ("kea", storage->logging_info_[0].name_);
-    EXPECT_EQ(0, storage->logging_info_[0].debuglevel_);
-    EXPECT_EQ(isc::log::INFO, storage->logging_info_[0].severity_);
-    ASSERT_EQ(2, storage->logging_info_[0].destinations_.size());
-    EXPECT_EQ("logfile.txt" , storage->logging_info_[0].destinations_[0].output_);
-    EXPECT_EQ("stdout" , storage->logging_info_[0].destinations_[1].output_);
+    EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+    EXPECT_EQ(0, storage->getLoggingInfo()[0].debuglevel_);
+    EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
+    ASSERT_EQ(2, storage->getLoggingInfo()[0].destinations_.size());
+    EXPECT_EQ("logfile.txt" , storage->getLoggingInfo()[0].destinations_[0].output_);
+    EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[1].output_);
 }
 
 /// @todo There is no easy way to test applyConfiguration() and defaultLogging().

+ 128 - 44
src/lib/dhcpsrv/tests/configuration_unittest.cc

@@ -14,17 +14,18 @@
 
 #include <config.h>
 
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
-#include <dhcpsrv/configuration.h>
+#include <dhcpsrv/srv_config.h>
 #include <dhcpsrv/subnet.h>
 #include <gtest/gtest.h>
 
 using namespace isc::asiolink;
 using namespace isc::dhcp;
 
-// Those are the tests for Configuration storage. Right now they are minimal,
+// Those are the tests for SrvConfig storage. Right now they are minimal,
 // but the number is expected to grow significantly once we migrate more
-// parameters from CfgMgr storage to Configuration storage.
+// parameters from CfgMgr storage to SrvConfig storage.
 
 namespace {
 
@@ -32,13 +33,14 @@ namespace {
 const int TEST_SUBNETS_NUM = 3;
 
 /// @brief Test fixture class for testing configuration data storage.
-class ConfigurationTest : public ::testing::Test {
+class SrvConfigTest : public ::testing::Test {
 public:
     /// @brief Constructor.
     ///
     /// Creates IPv4 and IPv6 subnets for unit test. The number of subnets
     /// is @c TEST_SUBNETS_NUM for IPv4 and IPv6 each.
-    ConfigurationTest() {
+    SrvConfigTest()
+        : iface_mgr_test_config_(true) {
         // Remove any subnets dangling from previous unit tests.
         clearSubnets();
 
@@ -74,7 +76,7 @@ public:
     /// @brief Destructor.
     ///
     /// Removes any dangling configuration.
-    virtual ~ConfigurationTest() {
+    virtual ~SrvConfigTest() {
         clearSubnets();
     }
 
@@ -86,9 +88,9 @@ public:
     /// @c TEST_SUBNETS_NUM.
     ///
     /// @todo Until the subnets configuration is migrated from the @c CfgMgr to
-    /// the @c Configuration object, this function adds the subnet to the
+    /// the @c SrvConfig object, this function adds the subnet to the
     /// @c CfgMgr. Once, the subnet configuration is held in the
-    /// @c Configuration this function must be modified to store the subnets in
+    /// @c SrvConfig this function must be modified to store the subnets in
     /// the @c conf_ object.
     void addSubnet4(const unsigned int index);
 
@@ -100,16 +102,16 @@ public:
     /// @c TEST_SUBNETS_NUM.
     ///
     /// @todo Until the subnets configuration is migrated from the @c CfgMgr to
-    /// the @c Configuration object, this function adds the subnet to the
+    /// the @c SrvConfig object, this function adds the subnet to the
     /// @c CfgMgr. Once, the subnet configuration is held in the
-    /// @c Configuration this function must be modified to store the subnets in
+    /// @c SrvConfig this function must be modified to store the subnets in
     /// @c conf_ object.
     void addSubnet6(const unsigned int index);
 
     /// @brief Removes all subnets from the configuration.
     ///
     /// @todo Modify this function once the subnet configuration is migrated
-    /// from @c CfgMgr to @c Configuration.
+    /// from @c CfgMgr to @c SrvConfig.
     void clearSubnets();
 
     /// @brief Enable/disable DDNS.
@@ -119,16 +121,18 @@ public:
     void enableDDNS(const bool enable);
 
     /// @brief Stores configuration.
-    Configuration conf_;
+    SrvConfig conf_;
     /// @brief A collection of IPv4 subnets used by unit tests.
     Subnet4Collection test_subnets4_;
     /// @brief A collection of IPv6 subnets used by unit tests.
     Subnet6Collection test_subnets6_;
+    /// @brief Fakes interface configuration.
+    isc::dhcp::test::IfaceMgrTestConfig iface_mgr_test_config_;
 
 };
 
 void
-ConfigurationTest::addSubnet4(const unsigned int index) {
+SrvConfigTest::addSubnet4(const unsigned int index) {
     if (index >= TEST_SUBNETS_NUM) {
         FAIL() << "Subnet index " << index << "out of range (0.."
                << TEST_SUBNETS_NUM << "): " << "unable to add IPv4 subnet";
@@ -137,7 +141,7 @@ ConfigurationTest::addSubnet4(const unsigned int index) {
 }
 
 void
-ConfigurationTest::addSubnet6(const unsigned int index) {
+SrvConfigTest::addSubnet6(const unsigned int index) {
     if (index >= TEST_SUBNETS_NUM) {
         FAIL() << "Subnet index " << index << "out of range (0.."
                << TEST_SUBNETS_NUM << "): " << "unable to add IPv6 subnet";
@@ -146,25 +150,26 @@ ConfigurationTest::addSubnet6(const unsigned int index) {
 }
 
 void
-ConfigurationTest::clearSubnets() {
+SrvConfigTest::clearSubnets() {
     CfgMgr::instance().deleteSubnets4();
     CfgMgr::instance().deleteSubnets6();
 }
 
 void
-ConfigurationTest::enableDDNS(const bool enable) {
+SrvConfigTest::enableDDNS(const bool enable) {
     // D2 configuration should always be non-NULL.
     CfgMgr::instance().getD2ClientConfig()->enableUpdates(enable);
 }
 
 // Check that by default there are no logging entries
-TEST_F(ConfigurationTest, basic) {
-    EXPECT_TRUE(conf_.logging_info_.empty());
+TEST_F(SrvConfigTest, basic) {
+    EXPECT_TRUE(conf_.getLoggingInfo().empty());
 }
 
-// Check that Configuration can store logging information.
-TEST_F(ConfigurationTest, loggingInfo) {
+// Check that SrvConfig can store logging information.
+TEST_F(SrvConfigTest, loggingInfo) {
     LoggingInfo log1;
+    log1.clearDestinations();
     log1.name_ = "foo";
     log1.severity_ = isc::log::WARN;
     log1.debuglevel_ = 77;
@@ -176,84 +181,163 @@ TEST_F(ConfigurationTest, loggingInfo) {
 
     log1.destinations_.push_back(dest);
 
-    conf_.logging_info_.push_back(log1);
+    conf_.addLoggingInfo(log1);
 
-    EXPECT_EQ("foo", conf_.logging_info_[0].name_);
-    EXPECT_EQ(isc::log::WARN, conf_.logging_info_[0].severity_);
-    EXPECT_EQ(77, conf_.logging_info_[0].debuglevel_);
+    EXPECT_EQ("foo", conf_.getLoggingInfo()[0].name_);
+    EXPECT_EQ(isc::log::WARN, conf_.getLoggingInfo()[0].severity_);
+    EXPECT_EQ(77, conf_.getLoggingInfo()[0].debuglevel_);
 
-    EXPECT_EQ("some-logfile.txt", conf_.logging_info_[0].destinations_[0].output_);
-    EXPECT_EQ(5, conf_.logging_info_[0].destinations_[0].maxver_);
-    EXPECT_EQ(2097152, conf_.logging_info_[0].destinations_[0].maxsize_);
+    EXPECT_EQ("some-logfile.txt", conf_.getLoggingInfo()[0].destinations_[0].output_);
+    EXPECT_EQ(5, conf_.getLoggingInfo()[0].destinations_[0].maxver_);
+    EXPECT_EQ(2097152, conf_.getLoggingInfo()[0].destinations_[0].maxsize_);
 }
 
 // Check that the configuration summary including information about the status
 // of DDNS is returned.
-TEST_F(ConfigurationTest, summaryDDNS) {
+TEST_F(SrvConfigTest, summaryDDNS) {
     EXPECT_EQ("DDNS: disabled",
-              conf_.getConfigSummary(Configuration::CFGSEL_DDNS));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_DDNS));
 
     enableDDNS(true);
     EXPECT_EQ("DDNS: enabled",
-              conf_.getConfigSummary(Configuration::CFGSEL_DDNS));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_DDNS));
 
     enableDDNS(false);
     EXPECT_EQ("no IPv4 subnets!; no IPv6 subnets!; DDNS: disabled",
-              conf_.getConfigSummary(Configuration::CFGSEL_ALL));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_ALL));
 }
 
 // Check that the configuration summary including information about added
 // subnets is returned.
-TEST_F(ConfigurationTest, summarySubnets) {
+TEST_F(SrvConfigTest, summarySubnets) {
     EXPECT_EQ("no config details available",
-              conf_.getConfigSummary(Configuration::CFGSEL_NONE));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_NONE));
 
     // Initially, there are no subnets added but it should be explicitly
     // reported when we query for information about the subnets.
     EXPECT_EQ("no IPv4 subnets!; no IPv6 subnets!",
-              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET));
 
     // If we just want information about IPv4 subnets, there should be no
     // mention of IPv6 subnets, even though there are none added.
     EXPECT_EQ("no IPv4 subnets!",
-              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET4));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET4));
 
     // If we just want information about IPv6 subnets, there should be no
     // mention of IPv4 subnets, even though there are none added.
     EXPECT_EQ("no IPv6 subnets!",
-              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET6));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET6));
 
     // Add IPv4 subnet and make sure it is reported.
     addSubnet4(0);
     EXPECT_EQ("added IPv4 subnets: 1",
-              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET4));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET4));
     EXPECT_EQ("added IPv4 subnets: 1; no IPv6 subnets!",
-              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET));
 
     // Add IPv6 subnet and make sure it is reported.
     addSubnet6(0);
     EXPECT_EQ("added IPv6 subnets: 1",
-              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET6));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET6));
     EXPECT_EQ("added IPv4 subnets: 1; added IPv6 subnets: 1",
-              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET));
 
     // Add one more subnet and make sure the bumped value is only
     // for IPv4, but not for IPv6.
     addSubnet4(1);
     EXPECT_EQ("added IPv4 subnets: 2; added IPv6 subnets: 1",
-              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET));
     EXPECT_EQ("added IPv4 subnets: 2",
-              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET4));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET4));
 
     addSubnet6(1);
     EXPECT_EQ("added IPv4 subnets: 2; added IPv6 subnets: 2",
-              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET));
 
     // Remove all subnets and make sure that there are no reported subnets
     // back again.
     clearSubnets();
     EXPECT_EQ("no IPv4 subnets!; no IPv6 subnets!",
-              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET));
+              conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET));
+}
+
+// This test checks if entire configuration can be copied and that the sequence
+// number is not affected.
+TEST_F(SrvConfigTest, copy) {
+    // Create two configurations with different sequence numbers.
+    SrvConfig conf1(32);
+    SrvConfig conf2(64);
+
+    // Set logging information for conf1.
+    LoggingInfo info;
+    info.name_ = "foo";
+    info.severity_ = isc::log::DEBUG;
+    info.debuglevel_ = 64;
+    info.destinations_.push_back(LoggingDestination());
+
+    // Set interface configuration for conf1.
+    CfgIface cfg_iface;
+    cfg_iface.use(AF_INET, "eth0");
+
+    conf1.addLoggingInfo(info);
+    conf1.setCfgIface(cfg_iface);
+
+    // Make sure both configurations are different.
+    ASSERT_TRUE(conf1 != conf2);
+
+    // Copy conf1 to conf2.
+    ASSERT_NO_THROW(conf1.copy(conf2));
+
+    // Now they should be equal.
+    EXPECT_TRUE(conf1 == conf2);
+
+    // But, their sequence numbers should be unequal.
+    EXPECT_FALSE(conf1.sequenceEquals(conf2));
+}
+
+// This test checks that two configurations can be compared for (in)equality.
+TEST_F(SrvConfigTest, equality) {
+    SrvConfig conf1(32);
+    SrvConfig conf2(64);
+
+    // Initially, both objects should be equal, even though the configuration
+    // sequences are not matching.
+    EXPECT_TRUE(conf1 == conf2);
+    EXPECT_FALSE(conf1 != conf2);
+
+    // Differ by logging information.
+    LoggingInfo info1;
+    LoggingInfo info2;
+    info1.name_ = "foo";
+    info2.name_ = "bar";
+
+    conf1.addLoggingInfo(info1);
+    conf2.addLoggingInfo(info2);
+
+    EXPECT_FALSE(conf1 == conf2);
+    EXPECT_TRUE(conf1 != conf2);
+
+    conf1.addLoggingInfo(info2);
+    conf2.addLoggingInfo(info1);
+
+    EXPECT_TRUE(conf1 == conf2);
+    EXPECT_FALSE(conf1 != conf2);
+
+    // Differ by interface configuration.
+    CfgIface cfg_iface1;
+    CfgIface cfg_iface2;
+
+    cfg_iface1.use(AF_INET, "eth0");
+    conf1.setCfgIface(cfg_iface1);
+
+    EXPECT_FALSE(conf1 == conf2);
+    EXPECT_TRUE(conf1 != conf2);
+
+    cfg_iface2.use(AF_INET, "eth0");
+    conf2.setCfgIface(cfg_iface2);
+
+    EXPECT_TRUE(conf1 == conf2);
+    EXPECT_FALSE(conf1 != conf2);
 }
 
 } // end of anonymous namespace

+ 1 - 0
tools/.gitignore

@@ -0,0 +1 @@
+/path_replacer.sh