Parcourir la source

[master] Merge branch 'trac3399' [Kea4 reads config from JSON file]

Conflicts:
	ChangeLog
	src/bin/dhcp6/kea_controller.cc
	src/lib/dhcpsrv/daemon.h
Tomek Mrugalski il y a 11 ans
Parent
commit
83816e00c5
36 fichiers modifiés avec 1167 ajouts et 445 suppressions
  1. 8 1
      ChangeLog
  2. 1 0
      doc/devel/mainpage.dox
  3. 38 0
      doc/examples/kea4/several-subnets.json
  4. 34 0
      doc/examples/kea4/single-subnet.json
  5. 9 1
      src/bin/dhcp4/Makefile.am
  6. 219 0
      src/bin/dhcp4/bundy_controller.cc
  7. 112 213
      src/bin/dhcp4/ctrl_dhcp4_srv.cc
  8. 94 65
      src/bin/dhcp4/ctrl_dhcp4_srv.h
  9. 40 0
      src/bin/dhcp4/dhcp4.dox
  10. 5 7
      src/bin/dhcp4/dhcp4_messages.mes
  11. 3 2
      src/bin/dhcp4/dhcp4_srv.h
  12. 1 1
      src/bin/dhcp4/config_parser.cc
  13. 0 0
      src/bin/dhcp4/json_config_parser.h
  14. 124 0
      src/bin/dhcp4/kea_controller.cc
  15. 40 33
      src/bin/dhcp4/main.cc
  16. 17 1
      src/bin/dhcp4/tests/Makefile.am
  17. 30 0
      src/bin/dhcp4/tests/bundy_controller_unittest.cc
  18. 1 1
      src/bin/dhcp4/tests/config_parser_unittest.cc
  19. 5 0
      src/bin/dhcp4/tests/configs-list.txt
  20. 12 4
      src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
  21. 1 1
      src/bin/dhcp4/tests/d2_unittest.cc
  22. 1 1
      src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
  23. 1 18
      src/bin/dhcp4/tests/dhcp4_test.py
  24. 1 1
      src/bin/dhcp4/tests/dhcp4_test_utils.cc
  25. 1 1
      src/bin/dhcp4/tests/direct_client_unittest.cc
  26. 296 0
      src/bin/dhcp4/tests/kea_controller_unittest.cc
  27. 2 2
      src/bin/dhcp6/bundy_controller.cc
  28. 6 3
      src/bin/dhcp6/ctrl_dhcp6_srv.cc
  29. 14 10
      src/bin/dhcp6/ctrl_dhcp6_srv.h
  30. 10 9
      src/bin/dhcp6/dhcp6.dox
  31. 5 1
      src/bin/dhcp6/dhcp6_messages.mes
  32. 6 22
      src/bin/dhcp6/kea_controller.cc
  33. 20 28
      src/bin/dhcp6/main.cc
  34. 4 0
      src/bin/dhcp6/tests/bundy_controller_unittest.cc
  35. 0 17
      src/bin/dhcp6/tests/dhcp6_test.py
  36. 6 2
      src/lib/dhcpsrv/daemon.h

+ 8 - 1
ChangeLog

@@ -1,3 +1,10 @@
+788.	[func]		tomek
+	DHCPv4 server: New parameter added to configure.ac: --with-kea-config.
+	It allows selecting configuration backend and accepts one of two
+	values: BUNDY, which uses Bundy (former BIND10) framework as Kea
+	0.8 did, or JSON, which reads configuration from a JSON file.
+	(Trac #3399, git 6e4dd3ae58c091ba0fd64c87fa8d7c268210f99b)
+
 787.	[func]		marcin
 787.	[func]		marcin
 	DHCPv6 server: Implemented dynamic reconfiguration of the server,
 	DHCPv6 server: Implemented dynamic reconfiguration of the server,
 	triggered when the SIGHUP signal is received by the server's
 	triggered when the SIGHUP signal is received by the server's
@@ -31,7 +38,7 @@
 	(Trac #3268,    git bd60252e679f19b062f61926647f661ab169f21c)
 	(Trac #3268,    git bd60252e679f19b062f61926647f661ab169f21c)
 
 
 783.	[func]*		tomek
 783.	[func]*		tomek
-	b10-dhcp6: New parameter added to configure: --with-kea-config.
+	DHCPv6 server: New parameter added to configure: --with-kea-config.
 	It allows selecting configuration backend and accepts one of two
 	It allows selecting configuration backend and accepts one of two
 	values: BUNDY, which uses Bundy (former BIND10 framework as Kea
 	values: BUNDY, which uses Bundy (former BIND10 framework as Kea
 	0.8 did, or JSON, which reads configuration from a JSON file.
 	0.8 did, or JSON, which reads configuration from a JSON file.

+ 1 - 0
doc/devel/mainpage.dox

@@ -55,6 +55,7 @@
  *   - @subpage dhcpv4OptionsParse
  *   - @subpage dhcpv4OptionsParse
  *   - @subpage dhcpv4DDNSIntegration
  *   - @subpage dhcpv4DDNSIntegration
  *   - @subpage dhcpv4Classifier
  *   - @subpage dhcpv4Classifier
+ *   - @subpage dhcpv4ConfigBackend
  *   - @subpage dhcpv4Other
  *   - @subpage dhcpv4Other
  * - @subpage dhcp6
  * - @subpage dhcp6
  *   - @subpage dhcpv6Session
  *   - @subpage dhcpv6Session

+ 38 - 0
doc/examples/kea4/several-subnets.json

@@ -0,0 +1,38 @@
+# This is an example configuration file for DHCPv4 server in Kea.
+# It's a basic scenario with three IPv4 subnets configured. In each
+# subnet, there's a smaller pool of dynamic addresses.
+
+{ "Dhcp4":
+
+{ 
+# Kea is told to listen on eth0 interface only.
+  "interfaces": [ "eth0" ],
+
+# We need to specify lease type. As of May 2014, three backends are supported:
+# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require
+# any prior set up.
+  "lease-database": {
+    "type": "memfile"
+  },
+
+# Addresses will be assigned with the valid lifetimes being 4000.
+# Client is told to start renewing after 1000 seconds. If the server
+# does not repond after 2000 seconds since the lease was granted, client
+# is supposed to start REBIND procedure (emergency renewal that allows
+# switching to a different server).
+  "valid-lifetime": 4000,
+  "renew-timer": 1000,
+  "rebind-timer": 2000,
+
+# The following list defines subnets. Each subnet consists of at
+# least subnet and pool entries.
+  "subnet4": [ 
+  {    "pool": [ "192.0.2.1 - 192.0.2.200" ],
+       "subnet": "192.0.2.0/24"  },
+  {    "pool": [ "192.0.3.100 - 192.0.3.200" ],
+       "subnet": "192.0.3.0/24"  },
+  {    "pool": [ "192.0.4.1 - 192.0.4.254" ],
+       "subnet": "192.0.4.0/24"  } ]
+}
+
+}

+ 34 - 0
doc/examples/kea4/single-subnet.json

@@ -0,0 +1,34 @@
+# This is an example configuration file for the DHCPv4 server in Kea.
+# It is a basic scenario with one IPv4 subnet configured. The subnet
+# contains a single pool of dynamically allocated addresses.
+
+{ "Dhcp4":
+
+{ 
+# Kea is told to listen on eth0 interface only.
+  "interfaces": [ "eth0" ],
+
+# We need to specify lease type. As of May 2014, three backends are supported:
+# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require
+# any prior set up.
+  "lease-database": {
+    "type": "memfile"
+  },
+
+# Addresses will be assigned with valid lifetimes being 4000. Client
+# is told to start renewing after 1000 seconds. If the server does not respond
+# after 2000 seconds since the lease was granted, client is supposed
+# to start REBIND procedure (emergency renewal that allows switching
+# to a different server).
+  "valid-lifetime": 4000,
+  "renew-timer": 1000,
+  "rebind-timer": 2000,
+
+# The following list defines subnets. We have only one subnet
+# here.
+  "subnet4": [ 
+  {    "pool": [ "192.0.2.1 - 192.0.2.200" ],
+       "subnet": "192.0.2.0/24"  } ]
+}
+
+}

+ 9 - 1
src/bin/dhcp4/Makefile.am

@@ -51,10 +51,18 @@ pkglibexec_PROGRAMS = b10-dhcp4
 
 
 b10_dhcp4_SOURCES  = main.cc
 b10_dhcp4_SOURCES  = main.cc
 b10_dhcp4_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
 b10_dhcp4_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
-b10_dhcp4_SOURCES += config_parser.cc config_parser.h
+b10_dhcp4_SOURCES += json_config_parser.cc json_config_parser.h
 b10_dhcp4_SOURCES += dhcp4_log.cc dhcp4_log.h
 b10_dhcp4_SOURCES += dhcp4_log.cc dhcp4_log.h
 b10_dhcp4_SOURCES += dhcp4_srv.cc dhcp4_srv.h
 b10_dhcp4_SOURCES += dhcp4_srv.cc dhcp4_srv.h
 
 
+if CONFIG_BACKEND_BUNDY
+b10_dhcp4_SOURCES += bundy_controller.cc
+endif
+
+if CONFIG_BACKEND_JSON
+b10_dhcp4_SOURCES += kea_controller.cc
+endif
+
 nodist_b10_dhcp4_SOURCES = dhcp4_messages.h dhcp4_messages.cc
 nodist_b10_dhcp4_SOURCES = dhcp4_messages.h dhcp4_messages.cc
 EXTRA_DIST += dhcp4_messages.mes
 EXTRA_DIST += dhcp4_messages.mes
 
 

+ 219 - 0
src/bin/dhcp4/bundy_controller.cc

@@ -0,0 +1,219 @@
+// 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
+// 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 <asiolink/asiolink.h>
+#include <cc/data.h>
+#include <cc/session.h>
+#include <config/ccsession.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/dhcp4_log.h>
+#include <dhcp4/spec_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
+#include <exceptions/exceptions.h>
+#include <hooks/hooks_manager.h>
+#include <util/buffer.h>
+
+#include <cassert>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+using namespace isc::asiolink;
+using namespace isc::cc;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::log;
+using namespace isc::util;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Helper session object that represents raw connection to msgq.
+isc::cc::Session* cc_session_ = NULL;
+
+/// @brief Session that receives configuration and commands
+isc::config::ModuleCCSession* config_session_ = NULL;
+
+/// @brief A dummy configuration handler that always returns success.
+///
+/// This configuration handler does not perform configuration
+/// parsing and always returns success. A dummy handler should
+/// be installed using \ref isc::config::ModuleCCSession ctor
+/// to get the initial configuration. This initial configuration
+/// comprises values for only those elements that were modified
+/// the previous session. The \ref dhcp4ConfigHandler can't be
+/// used to parse the initial configuration because it needs the
+/// full configuration to satisfy dependencies between the
+/// various configuration values. Installing the dummy handler
+/// that guarantees to return success causes initial configuration
+/// to be stored for the session being created and that it can
+/// be later accessed with
+/// \ref isc::config::ConfigData::getFullConfig().
+///
+/// @param new_config new configuration.
+///
+/// @return success configuration status.
+ConstElementPtr
+dhcp4StubConfigHandler(ConstElementPtr) {
+    // This configuration handler is intended to be used only
+    // when the initial configuration comes in. To receive this
+    // configuration a pointer to this handler must be passed
+    // using ModuleCCSession constructor. This constructor will
+    // invoke the handler and will store the configuration for
+    // the configuration session when the handler returns success.
+    // Since this configuration is partial we just pretend to
+    // parse it and always return success. The function that
+    // initiates the session must get the configuration on its
+    // own using getFullConfig.
+    return (isc::config::createAnswer(0, "Configuration accepted."));
+}
+
+ConstElementPtr
+bundyConfigHandler(ConstElementPtr new_config) {
+    if (!ControlledDhcpv4Srv::getInstance() || !config_session_) {
+        // That should never happen as we install config_handler
+        // after we instantiate the server.
+        ConstElementPtr answer =
+            isc::config::createAnswer(1, "Configuration rejected,"
+                                      " server is during startup/shutdown phase.");
+        return (answer);
+    }
+
+    // The configuration passed to this handler function is partial.
+    // In other words, it just includes the values being modified.
+    // In the same time, there are dependencies between various
+    // DHCP configuration parsers. For example: the option value can
+    // be set if the definition of this option is set. If someone removes
+    // an existing option definition then the partial configuration that
+    // removes that definition is triggered while a relevant option value
+    // may remain configured. This eventually results in the DHCP server
+    // configuration being in the inconsistent state.
+    // In order to work around this problem we need to merge the new
+    // configuration with the existing (full) configuration.
+
+    // Let's create a new object that will hold the merged configuration.
+    boost::shared_ptr<MapElement> merged_config(new MapElement());
+    // Let's get the existing configuration.
+    ConstElementPtr full_config = config_session_->getFullConfig();
+    // The full_config and merged_config should be always non-NULL
+    // but to provide some level of exception safety we check that they
+    // really are (in case we go out of memory).
+    if (full_config && merged_config) {
+        merged_config->setValue(full_config->mapValue());
+
+        // Merge an existing and new configuration.
+        isc::data::merge(merged_config, new_config);
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE)
+            .arg(full_config->str());
+    }
+
+    // Configure the server.
+    return (ControlledDhcpv4Srv::processConfig(merged_config));
+}
+
+
+
+
+void ControlledDhcpv4Srv::init(const std::string& /*config_file*/) {
+
+    string specfile;
+    if (getenv("B10_FROM_BUILD")) {
+        specfile = string(getenv("B10_FROM_BUILD")) +
+            "/src/bin/dhcp4/dhcp4.spec";
+    } else {
+        specfile = string(DHCP4_SPECFILE_LOCATION);
+    }
+
+    /// @todo: Check if session is not established already. Throw, if it is.
+
+    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTING)
+              .arg(specfile);
+    cc_session_ = new Session(io_service_.get_io_service());
+    // Create a session with the dummy configuration handler.
+    // Dumy configuration handler is internally invoked by the
+    // constructor and on success the constructor updates
+    // the current session with the configuration that had been
+    // committed in the previous session. If we did not install
+    // the dummy handler, the previous configuration would have
+    // been lost.
+    config_session_ = new ModuleCCSession(specfile, *cc_session_,
+                                          dhcp4StubConfigHandler,
+                                          processCommand, false);
+    config_session_->start();
+
+    // We initially create ModuleCCSession() without configHandler, as
+    // the session module is too eager to send partial configuration.
+    // We want to get the full configuration, so we explicitly call
+    // getFullConfig() and then pass it to our configHandler.
+    config_session_->setConfigHandler(bundyConfigHandler);
+
+    try {
+        configureDhcp4Server(*this, config_session_->getFullConfig());
+
+        // Server will start DDNS communications if its enabled.
+        server_->startD2();
+
+        // Configuration may disable or enable interfaces so we have to
+        // reopen sockets according to new configuration.
+        openActiveSockets(getPort(), useBroadcast());
+
+    } catch (const std::exception& ex) {
+        LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
+
+    }
+
+    /// Integrate the asynchronous I/O model of BIND 10 configuration
+    /// control with the "select" model of the DHCP server.  This is
+    /// fully explained in \ref dhcpv4Session.
+    int ctrl_socket = cc_session_->getSocketDesc();
+    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTED)
+              .arg(ctrl_socket);
+    IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
+}
+
+
+void ControlledDhcpv4Srv::cleanup() {
+    if (config_session_) {
+        delete config_session_;
+        config_session_ = NULL;
+    }
+    if (cc_session_) {
+
+        int ctrl_socket = cc_session_->getSocketDesc();
+        cc_session_->disconnect();
+
+        IfaceMgr::instance().deleteExternalSocket(ctrl_socket);
+        delete cc_session_;
+        cc_session_ = NULL;
+    }
+}
+
+void
+Daemon::loggerInit(const char* log_name, bool verbose) {
+    isc::log::initLogger(log_name,
+                         (verbose ? isc::log::DEBUG : isc::log::INFO),
+                         isc::log::MAX_DEBUG_LEVEL, NULL, true);
+}
+
+};
+};

+ 112 - 213
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -13,36 +13,14 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <config.h>
 #include <config.h>
-
-#include <asiolink/asiolink.h>
 #include <cc/data.h>
 #include <cc/data.h>
-#include <cc/session.h>
-#include <config/ccsession.h>
-#include <dhcp/iface_mgr.h>
-#include <dhcp4/config_parser.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/dhcp4_log.h>
-#include <dhcp4/spec_config.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <dhcpsrv/dhcp_config_parser.h>
-#include <exceptions/exceptions.h>
 #include <hooks/hooks_manager.h>
 #include <hooks/hooks_manager.h>
-#include <util/buffer.h>
-
-#include <cassert>
-#include <iostream>
-#include <sstream>
-#include <string>
-#include <vector>
+#include <dhcp4/json_config_parser.h>
 
 
-using namespace isc::asiolink;
-using namespace isc::cc;
-using namespace isc::config;
 using namespace isc::data;
 using namespace isc::data;
-using namespace isc::dhcp;
 using namespace isc::hooks;
 using namespace isc::hooks;
-using namespace isc::log;
-using namespace isc::util;
 using namespace std;
 using namespace std;
 
 
 namespace isc {
 namespace isc {
@@ -51,217 +29,141 @@ namespace dhcp {
 ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
 ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
 
 
 ConstElementPtr
 ConstElementPtr
-ControlledDhcpv4Srv::dhcp4StubConfigHandler(ConstElementPtr) {
-    // This configuration handler is intended to be used only
-    // when the initial configuration comes in. To receive this
-    // configuration a pointer to this handler must be passed
-    // using ModuleCCSession constructor. This constructor will
-    // invoke the handler and will store the configuration for
-    // the configuration session when the handler returns success.
-    // Since this configuration is partial we just pretend to
-    // parse it and always return success. The function that
-    // initiates the session must get the configuration on its
-    // own using getFullConfig.
-    return (isc::config::createAnswer(0, "Configuration accepted."));
+ControlledDhcpv4Srv::commandShutdownHandler(const string&, ConstElementPtr) {
+    if (ControlledDhcpv4Srv::getInstance()) {
+        ControlledDhcpv4Srv::getInstance()->shutdown();
+    } else {
+        LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING);
+        ConstElementPtr answer = isc::config::createAnswer(1,
+                                              "Shutdown failure.");
+        return (answer);
+    }
+    ConstElementPtr answer = isc::config::createAnswer(0, "Shutting down.");
+    return (answer);
 }
 }
 
 
 ConstElementPtr
 ConstElementPtr
-ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
-    if (!server_ || !server_->config_session_) {
-        // That should never happen as we install config_handler
-        // after we instantiate the server.
-        ConstElementPtr answer =
-            isc::config::createAnswer(1, "Configuration rejected,"
-                                      " server is during startup/shutdown phase.");
+ControlledDhcpv4Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
+
+    /// @todo delete any stored CalloutHandles referring to the old libraries
+    /// Get list of currently loaded libraries and reload them.
+    vector<string> loaded = HooksManager::getLibraryNames();
+    bool status = HooksManager::loadLibraries(loaded);
+    if (!status) {
+        LOG_ERROR(dhcp4_logger, DHCP4_HOOKS_LIBS_RELOAD_FAIL);
+        ConstElementPtr answer = isc::config::createAnswer(1,
+                                 "Failed to reload hooks libraries.");
         return (answer);
         return (answer);
     }
     }
+    ConstElementPtr answer = isc::config::createAnswer(0,
+                             "Hooks libraries successfully reloaded.");
+    return (answer);
+}
 
 
-    // The configuration passed to this handler function is partial.
-    // In other words, it just includes the values being modified.
-    // In the same time, there are dependencies between various
-    // DHCP configuration parsers. For example: the option value can
-    // be set if the definition of this option is set. If someone removes
-    // an existing option definition then the partial configuration that
-    // removes that definition is triggered while a relevant option value
-    // may remain configured. This eventually results in the DHCP server
-    // configuration being in the inconsistent state.
-    // In order to work around this problem we need to merge the new
-    // configuration with the existing (full) configuration.
-
-    // Let's create a new object that will hold the merged configuration.
-    boost::shared_ptr<MapElement> merged_config(new MapElement());
-    // Let's get the existing configuration.
-    ConstElementPtr full_config = server_->config_session_->getFullConfig();
-    // The full_config and merged_config should be always non-NULL
-    // but to provide some level of exception safety we check that they
-    // really are (in case we go out of memory).
-    if (full_config && merged_config) {
-        merged_config->setValue(full_config->mapValue());
-
-        // Merge an existing and new configuration.
-        isc::data::merge(merged_config, new_config);
-        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE)
-            .arg(full_config->str());
-    }
+ConstElementPtr
+ControlledDhcpv4Srv::commandConfigReloadHandler(const string&,
+                                                ConstElementPtr args) {
+    return (processConfig(args));
+}
 
 
-    // Configure the server.
-    ConstElementPtr answer = configureDhcp4Server(*server_, merged_config);
+ConstElementPtr
+ControlledDhcpv4Srv::processCommand(const string& command,
+                                    ConstElementPtr args) {
+    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
+              .arg(command).arg(args->str());
 
 
-    // Check that configuration was successful. If not, do not reopen sockets.
-    int rcode = 0;
-    parseAnswer(rcode, answer);
-    if (rcode != 0) {
-        return (answer);
-    }
+    ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
 
 
-    // Server will start DDNS communications if its enabled.
-    try {
-        server_->startD2();
-    } catch (const std::exception& ex) {
-        std::ostringstream err;
-        err << "error starting DHCP_DDNS client "
-                " after server reconfiguration: " << ex.what();
-        return (isc::config::createAnswer(1, err.str()));
+    if (!srv) {
+        ConstElementPtr no_srv = isc::config::createAnswer(1,
+          "Server object not initialized, so can't process command '" +
+          command + "', arguments: '" + args->str() + "'.");
+        return (no_srv);
     }
     }
 
 
-    // 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 {
     try {
-        server_->openActiveSockets(server_->getPort(), server_->useBroadcast());
-    } catch (std::exception& ex) {
-        std::ostringstream err;
-        err << "failed to open sockets after server reconfiguration: " << ex.what();
-        answer = isc::config::createAnswer(1, err.str());
-    }
-    return (answer);
-}
+        if (command == "shutdown") {
+            return (srv->commandShutdownHandler(command, args));
 
 
-ConstElementPtr
-ControlledDhcpv4Srv::dhcp4CommandHandler(const string& command, ConstElementPtr args) {
-    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
-              .arg(command).arg(args->str());
+        } else if (command == "libreload") {
+            return (srv->commandLibReloadHandler(command, args));
 
 
-    if (command == "shutdown") {
-        if (ControlledDhcpv4Srv::server_) {
-            ControlledDhcpv4Srv::server_->shutdown();
-        } else {
-            LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING);
-            ConstElementPtr answer = isc::config::createAnswer(1,
-                                     "Shutdown failure.");
-            return (answer);
-        }
-        ConstElementPtr answer = isc::config::createAnswer(0,
-                                 "Shutting down.");
-        return (answer);
+        } else if (command == "config-reload") {
+            return (srv->commandConfigReloadHandler(command, args));
 
 
-    } else if (command == "libreload") {
-        // TODO delete any stored CalloutHandles referring to the old libraries
-        // Get list of currently loaded libraries and reload them.
-        vector<string> loaded = HooksManager::getLibraryNames();
-        bool status = HooksManager::loadLibraries(loaded);
-        if (!status) {
-            LOG_ERROR(dhcp4_logger, DHCP4_HOOKS_LIBS_RELOAD_FAIL);
-            ConstElementPtr answer = isc::config::createAnswer(1,
-                                     "Failed to reload hooks libraries.");
-            return (answer);
         }
         }
-        ConstElementPtr answer = isc::config::createAnswer(0,
-                                 "Hooks libraries successfully reloaded.");
+        ConstElementPtr answer = isc::config::createAnswer(1,
+                                 "Unrecognized command:" + command);
         return (answer);
         return (answer);
+    } catch (const Exception& ex) {
+        return (isc::config::createAnswer(1, "Error while processing command '"
+                                          + command + "':" + ex.what() +
+                                          ", params: '" + args->str() + "'"));
     }
     }
-
-    ConstElementPtr answer = isc::config::createAnswer(1,
-                             "Unrecognized command.");
-
-    return (answer);
 }
 }
 
 
-void ControlledDhcpv4Srv::sessionReader(void) {
-    // Process one asio event. If there are more events, iface_mgr will call
-    // this callback more than once.
-    if (server_) {
-        server_->io_service_.run_one();
-    }
-}
+isc::data::ConstElementPtr
+ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
 
 
-void ControlledDhcpv4Srv::establishSession() {
+    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED)
+              .arg(config->str());
 
 
-    string specfile;
-    if (getenv("B10_FROM_BUILD")) {
-        specfile = string(getenv("B10_FROM_BUILD")) +
-            "/src/bin/dhcp4/dhcp4.spec";
-    } else {
-        specfile = string(DHCP4_SPECFILE_LOCATION);
-    }
+    ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
 
 
-    /// @todo: Check if session is not established already. Throw, if it is.
+    // Single stream instance used in all error clauses
+    std::ostringstream err;
 
 
-    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTING)
-              .arg(specfile);
-    cc_session_ = new Session(io_service_.get_io_service());
-    // Create a session with the dummy configuration handler.
-    // Dumy configuration handler is internally invoked by the
-    // constructor and on success the constructor updates
-    // the current session with the configuration that had been
-    // committed in the previous session. If we did not install
-    // the dummy handler, the previous configuration would have
-    // been lost.
-    config_session_ = new ModuleCCSession(specfile, *cc_session_,
-                                          dhcp4StubConfigHandler,
-                                          dhcp4CommandHandler, false);
-    config_session_->start();
+    if (!srv) {
+        err << "Server object not initialized, can't process config.";
+        return (isc::config::createAnswer(1, err.str()));
+    }
+    
+    ConstElementPtr answer = configureDhcp4Server(*srv, config);
 
 
-    // We initially create ModuleCCSession() without configHandler, as
-    // the session module is too eager to send partial configuration.
-    // We want to get the full configuration, so we explicitly call
-    // getFullConfig() and then pass it to our configHandler.
-    config_session_->setConfigHandler(dhcp4ConfigHandler);
 
 
+    // Check that configuration was successful. If not, do not reopen sockets
+    // and don't bother with DDNS stuff.
     try {
     try {
-        configureDhcp4Server(*this, config_session_->getFullConfig());
-
-        // Server will start DDNS communications if its enabled.
-        server_->startD2();
-
-        // Configuration may disable or enable interfaces so we have to
-        // reopen sockets according to new configuration.
-        openActiveSockets(getPort(), useBroadcast());
-
+        int rcode = 0;
+        isc::config::parseAnswer(rcode, answer);
+        if (rcode != 0) {
+            return (answer);
+        }
     } catch (const std::exception& ex) {
     } catch (const std::exception& ex) {
-        LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
-
+        err << "Failed to process configuration:" << ex.what();
+        return (isc::config::createAnswer(1, err.str()));
     }
     }
 
 
-    /// Integrate the asynchronous I/O model of BIND 10 configuration
-    /// control with the "select" model of the DHCP server.  This is
-    /// fully explained in \ref dhcpv4Session.
-    int ctrl_socket = cc_session_->getSocketDesc();
-    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTED)
-              .arg(ctrl_socket);
-    IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
-}
-
-void ControlledDhcpv4Srv::disconnectSession() {
-    if (config_session_) {
-        delete config_session_;
-        config_session_ = NULL;
+    // Server will start DDNS communications if its enabled.
+    try {
+        srv->startD2();
+    } catch (const std::exception& ex) {
+        err << "Error starting DHCP_DDNS client after server reconfiguration: "
+            << ex.what();
+        return (isc::config::createAnswer(1, err.str()));
     }
     }
-    if (cc_session_) {
 
 
-        int ctrl_socket = cc_session_->getSocketDesc();
-        cc_session_->disconnect();
-
-        IfaceMgr::instance().deleteExternalSocket(ctrl_socket);
-        delete cc_session_;
-        cc_session_ = NULL;
+    // 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 {
+        srv->openActiveSockets(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);
 }
 }
 
 
 ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)
 ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)
-    :Dhcpv4Srv(port), cc_session_(NULL), config_session_(NULL) {
-    server_ = this; // remember this instance for use in callback
+    :Dhcpv4Srv(port) {
+    if (getInstance()) {
+        isc_throw(InvalidOperation,
+                  "There is another Dhcpv4Srv instance already.");
+    }
+    server_ = this; // remember this instance for later use in handlers
 }
 }
 
 
 void ControlledDhcpv4Srv::shutdown() {
 void ControlledDhcpv4Srv::shutdown() {
@@ -270,22 +172,19 @@ void ControlledDhcpv4Srv::shutdown() {
 }
 }
 
 
 ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
 ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
-    disconnectSession();
-
-    server_ = NULL; // forget this instance. There should be no callback anymore
-                    // at this stage anyway.
+    cleanup();
+    
+    server_ = NULL; // forget this instance. Noone should call any handlers at
+                    // this stage.
 }
 }
 
 
-isc::data::ConstElementPtr
-ControlledDhcpv4Srv::execDhcpv4ServerCommand(const std::string& command_id,
-                                             isc::data::ConstElementPtr args) {
-    try {
-        return (dhcp4CommandHandler(command_id, args));
-    } catch (const Exception& ex) {
-        ConstElementPtr answer = isc::config::createAnswer(1, ex.what());
-        return (answer);
+void ControlledDhcpv4Srv::sessionReader(void) {
+    // Process one asio event. If there are more events, iface_mgr will call
+    // this callback more than once.
+    if (getInstance()) {
+        getInstance()->io_service_.run_one();
     }
     }
 }
 }
 
 
-};
-};
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 94 - 65
src/bin/dhcp4/ctrl_dhcp4_srv.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013  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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -26,12 +26,10 @@ namespace dhcp {
 
 
 /// @brief Controlled version of the DHCPv4 server
 /// @brief Controlled version of the DHCPv4 server
 ///
 ///
-/// This is a class that is responsible for establishing connection
-/// with msqg (receving commands and configuration). This is an extended
-/// version of Dhcpv4Srv class that is purely a DHCPv4 server, without
-/// external control. ControlledDhcpv4Srv should be used in typical BIND10
-/// (i.e. featuring msgq) environment, while Dhcpv4Srv should be used in
-/// embedded environments.
+/// This is a class that is responsible for DHCPv4 server being controllable.
+/// It does various things, depending on the configuration backend.
+/// For Bundy backend it establishes a connection with msqg and later receives
+/// commands over it. For Kea backend, it reads configuration file from disk.
 ///
 ///
 /// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
 /// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
 /// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
 /// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
@@ -46,44 +44,57 @@ public:
     /// @brief Destructor.
     /// @brief Destructor.
     ~ControlledDhcpv4Srv();
     ~ControlledDhcpv4Srv();
 
 
-    /// @brief Establishes msgq session.
+    /// @brief Initializes the server.
     ///
     ///
-    /// Creates session that will be used to receive commands and updated
-    /// configuration from cfgmgr (or indirectly from user via bindctl).
-    void establishSession();
+    /// Depending on the configuration backend, it establishes msgq session,
+    /// reads the JSON file from disk or may perform any other setup
+    /// operation. For specific details, see actual implementation in
+    /// *_backend.cc
+    ///
+    /// This method may throw if initialization fails. Exception types may be
+    /// specific to used configuration backend.
+    void init(const std::string& config_file);
 
 
-    /// @brief Terminates existing msgq session.
+    /// @brief Performs cleanup, immediately before termination
     ///
     ///
-    /// This method terminates existing session with msgq. After calling
+    /// This method performs final clean up, just before the Dhcpv4Srv object
+    /// is destroyed. The actual behavior is backend dependent. For Bundy
+    /// backend, it terminates existing session with msgq. After calling
     /// it, no further messages over msgq (commands or configuration updates)
     /// it, no further messages over msgq (commands or configuration updates)
-    /// may be received.
+    /// may be received. For JSON backend, it is no-op.
     ///
     ///
-    /// It is ok to call this method when session is disconnected already.
-    void disconnectSession();
+    /// For specific details, see actual implementation in *_backend.cc
+    void cleanup();
 
 
     /// @brief Initiates shutdown procedure for the whole DHCPv4 server.
     /// @brief Initiates shutdown procedure for the whole DHCPv4 server.
     void shutdown();
     void shutdown();
 
 
-    /// @brief Session callback, processes received commands.
+    /// @brief Command processor
+    ///
+    /// This method is uniform for all config backends. It processes received
+    /// command (as a string + JSON arguments). Internally, it's just a
+    /// wrapper that calls process*Command() methods and catches exceptions
+    /// in them.
+    ///
+    /// Currently supported commands are:
+    /// - shutdown
+    /// - libreload
+    /// - config-reload
+    ///
+    /// @note It never throws.
     ///
     ///
     /// @param command Text represenation of the command (e.g. "shutdown")
     /// @param command Text represenation of the command (e.g. "shutdown")
     /// @param args Optional parameters
     /// @param args Optional parameters
-    /// @param command text represenation of the command (e.g. "shutdown")
-    /// @param args optional parameters
     ///
     ///
     /// @return status of the command
     /// @return status of the command
     static isc::data::ConstElementPtr
     static isc::data::ConstElementPtr
-    execDhcpv4ServerCommand(const std::string& command,
-                            isc::data::ConstElementPtr args);
+    processCommand(const std::string& command, isc::data::ConstElementPtr args);
 
 
-protected:
-    /// @brief Static pointer to the sole instance of the DHCP server.
+    /// @brief Configuration processor
     ///
     ///
-    /// This is required for config and command handlers to gain access to
-    /// the server
-    static ControlledDhcpv4Srv* server_;
-
-    /// @brief A callback for handling incoming configuration updates.
+    /// This is a method for handling incoming configuration updates.
+    /// This method should be called by all configuration backends when the
+    /// server is starting up or when configuration has changed.
     ///
     ///
     /// As pointer to this method is used a callback in ASIO used in
     /// As pointer to this method is used a callback in ASIO used in
     /// ModuleCCSession, it has to be static.
     /// ModuleCCSession, it has to be static.
@@ -92,54 +103,72 @@ protected:
     ///
     ///
     /// @return status of the config update
     /// @return status of the config update
     static isc::data::ConstElementPtr
     static isc::data::ConstElementPtr
-    dhcp4ConfigHandler(isc::data::ConstElementPtr new_config);
-
-    /// @brief A dummy configuration handler that always returns success.
-    ///
-    /// This configuration handler does not perform configuration
-    /// parsing and always returns success. A dummy handler should
-    /// be installed using \ref isc::config::ModuleCCSession ctor
-    /// to get the initial configuration. This initial configuration
-    /// comprises values for only those elements that were modified
-    /// the previous session. The \ref dhcp4ConfigHandler can't be
-    /// used to parse the initial configuration because it needs the
-    /// full configuration to satisfy dependencies between the
-    /// various configuration values. Installing the dummy handler
-    /// that guarantees to return success causes initial configuration
-    /// to be stored for the session being created and that it can
-    /// be later accessed with
-    /// \ref isc::config::ConfigData::getFullConfig().
-    ///
-    /// @param new_config new configuration.
-    ///
-    /// @return success configuration status.
-    static isc::data::ConstElementPtr
-    dhcp4StubConfigHandler(isc::data::ConstElementPtr new_config);
+    processConfig(isc::data::ConstElementPtr new_config);
 
 
-    /// @brief A callback for handling incoming commands.
+    /// @brief Returns pointer to the sole instance of Dhcpv4Srv
     ///
     ///
-    /// @param command textual representation of the command
-    /// @param args parameters of the command
+    /// @return server instance (may return NULL, if called before server is spawned)
+    static ControlledDhcpv4Srv* getInstance() {
+        return (server_);
+    }
+
+
+protected:
+    /// @brief Static pointer to the sole instance of the DHCP server.
     ///
     ///
-    /// @return status of the processed command
-    static isc::data::ConstElementPtr
-    dhcp4CommandHandler(const std::string& command, isc::data::ConstElementPtr args);
+    /// This is required for config and command handlers to gain access to
+    /// the server
+    static ControlledDhcpv4Srv* server_;
 
 
-    /// @brief Callback that will be called from iface_mgr when command/config arrives.
+    /// @brief Callback that will be called from iface_mgr when data
+    /// is received over control socket.
     ///
     ///
-    /// This static callback method is called from IfaceMgr::receive4() method,
-    /// when there is a new command or configuration sent over msgq.
+    /// This static callback method is called from IfaceMgr::receive6() method,
+    /// when there is a new command or configuration sent over control socket
+    /// (that was sent from msgq if backend is Bundy, or some yet unspecified
+    /// sender if the backend is JSON file).
     static void sessionReader(void);
     static void sessionReader(void);
 
 
-
     /// @brief IOService object, used for all ASIO operations.
     /// @brief IOService object, used for all ASIO operations.
     isc::asiolink::IOService io_service_;
     isc::asiolink::IOService io_service_;
 
 
-    /// @brief Helper session object that represents raw connection to msgq.
-    isc::cc::Session* cc_session_;
+    /// @brief Handler for processing 'shutdown' command
+    ///
+    /// This handler processes shutdown command, which initializes shutdown
+    /// procedure.
+    /// @param command (parameter ignored)
+    /// @param args (parameter ignored)
+    ///
+    /// @return status of the command
+    isc::data::ConstElementPtr
+    commandShutdownHandler(const std::string& command,
+                           isc::data::ConstElementPtr args);
 
 
-    /// @brief Session that receives configuration and commands
-    isc::config::ModuleCCSession* config_session_;
+    /// @brief Handler for processing 'libreload' command
+    ///
+    /// This handler processes libreload command, which unloads all hook
+    /// libraries and reloads them.
+    ///
+    /// @param command (parameter ignored)
+    /// @param args (parameter ignored)
+    ///
+    /// @return status of the command
+    isc::data::ConstElementPtr
+    commandLibReloadHandler(const std::string& command,
+                            isc::data::ConstElementPtr args);
+
+    /// @brief Handler for processing 'config-reload' command
+    ///
+    /// This handler processes config-reload command, which processes
+    /// configuration specified in args parameter.
+    ///
+    /// @param command (parameter ignored)
+    /// @param args configuration to be processed
+    ///
+    /// @return status of the command
+    isc::data::ConstElementPtr
+    commandConfigReloadHandler(const std::string& command,
+                               isc::data::ConstElementPtr args);
 };
 };
 
 
 }; // namespace isc::dhcp
 }; // namespace isc::dhcp

+ 40 - 0
src/bin/dhcp4/dhcp4.dox

@@ -191,6 +191,46 @@ being passed in isc::dhcp::Dhcpv4Srv::selectSubnet() to isc::dhcp::CfgMgr::getSu
 Currently this capability is usable, but the number of scenarios it supports is
 Currently this capability is usable, but the number of scenarios it supports is
 limited.
 limited.
 
 
+ @section dhcpv4ConfigBackend Configuration backend for DHCPv4
+
+There are many theoretical ways in which server configuration can be stored. Kea 0.8 and
+earlier versions used BIND10 framework and its internal storage for DHCPv6 server configuration.
+The legacy ISC-DHCP implementation uses flat files. Configuration stored in JSON files is
+becoming more and more popular among various projects. There are unofficial patches for
+ISC-DHCP that keep parts of the configuration in LDAP. It was also suggested that in some
+cases it would be convenient to keep configuration in XML files.
+
+Kea 0.9 introduces configuration backends that are switchable during compilation phase.
+There is a new parameter for configure script: --with-kea-config. It currently supports
+two values: BUNDY and JSON.
+
+BUNDY (which is the default value as of May 2014) means that Kea4 is linked with the
+Bundy (former BIND10) configuration backend that connects to the BIND10 framework and in general works
+exactly the same as Kea 0.8 and earlier versions. The benefits of that backend are uniform
+integration with Bundy/BIND10 framework, easy on-line reconfiguration using bindctl, available
+RESTful API. On the other hand, it requires the whole heavy Bundy framework that requires
+Python3 to be present. That framework is going away with the release of Kea 0.9.
+
+JSON is a new configuration backend that causes Kea to read JSON configuration file from
+disk. It does not require any framework and thus is considered more lightweight. It will
+allow dynamic on-line reconfiguration, but will lack remote capabilities (i.e. no RESTful
+API). This configuration backend is expected to be the default for upcoming Kea 0.9. It
+requires <tt> -c config-file </tt> command-line option.
+
+Internally, configuration backends are implemented as different implementations of the
+isc::dhcp::ControlledDhcpv4Srv class, stored in {kea,bundy}_controller.cc files. Depending on
+the choice made by ./configure script, only one of those files is compiled and linked.
+There are backend specific tests in src/bin/dhcp4/tests/{kea,bundy}_controller_unittest.cc.
+Only tests specific to selected backend are linked and executed during make distcheck.
+
+While it is unlikely that ISC will support more than one backend at any given time, there
+are several aspects that make that approach appealing in the long term. First, having
+two backends is essential during transition time, where both old and new backend is used.
+Second, there are external organizations that develop and presumably maintain LDAP backend
+for ISC-DHCP. Is at least possible that similar will happen for Kea. Finally, if we ever
+extend the isc::dhcp::CfgMgr with configuration export, this approach could be used as
+a migration tool.
+
 @section dhcpv4Other Other DHCPv4 topics
 @section dhcpv4Other Other DHCPv4 topics
 
 
  For hooks API support in DHCPv4, see @ref dhcpv4Hooks.
  For hooks API support in DHCPv4, see @ref dhcpv4Hooks.

+ 5 - 7
src/bin/dhcp4/dhcp4_messages.mes

@@ -44,6 +44,10 @@ name was malformed or due to internal server error.
 A debug message listing the command (and possible arguments) received
 A debug message listing the command (and possible arguments) received
 from the BIND 10 control system by the DHCPv4 server.
 from the BIND 10 control system by the DHCPv4 server.
 
 
+% DHCP4_CONFIG_RECEIVED received configuration %1
+A debug message listing the configuration received by the DHCPv4 server.
+The source of that configuration depends on used configuration backend.
+
 % DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
 % DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
 This is an informational message announcing the successful processing of a
 This is an informational message announcing the successful processing of a
 new configuration. It is output during server startup, and when an updated
 new configuration. It is output during server startup, and when an updated
@@ -339,17 +343,11 @@ core component within the DHCPv4 server (the Dhcpv4 server object)
 has failed.  As a result, the server will exit.  The reason for the
 has failed.  As a result, the server will exit.  The reason for the
 failure is given within the message.
 failure is given within the message.
 
 
-% DHCP4_STANDALONE skipping message queue, running standalone
-This is a debug message indicating that the DHCPv4 server is running in
-standalone mode, not connected to the message queue.  Standalone mode
-is only useful during program development, and should not be used in a
-production environment.
-
 % DHCP4_STARTING server starting
 % DHCP4_STARTING server starting
 This informational message indicates that the DHCPv4 server has
 This informational message indicates that the DHCPv4 server has
 processed any command-line switches and is starting.
 processed any command-line switches and is starting.
 
 
-% DHCP4_START_INFO pid: %1, port: %2, verbose: %3, standalone: %4
+% DHCP4_START_INFO pid: %1, port: %2, verbose: %3
 This is a debug message issued during the DHCPv4 server startup.
 This is a debug message issued during the DHCPv4 server startup.
 It lists some information about the parameters with which the server
 It lists some information about the parameters with which the server
 is running.
 is running.

+ 3 - 2
src/bin/dhcp4/dhcp4_srv.h

@@ -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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -26,6 +26,7 @@
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <hooks/callout_handle.h>
 #include <hooks/callout_handle.h>
+#include <dhcpsrv/daemon.h>
 
 
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
 
 
@@ -57,7 +58,7 @@ public:
 ///
 ///
 /// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
 /// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
 /// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
 /// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
-class Dhcpv4Srv : public boost::noncopyable {
+class Dhcpv4Srv : public Daemon {
 
 
 public:
 public:
 
 

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

@@ -17,7 +17,7 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option_definition.h>
 #include <dhcp/option_definition.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
-#include <dhcp4/config_parser.h>
+#include <dhcp4/json_config_parser.h>
 #include <dhcpsrv/dbaccess_parser.h>
 #include <dhcpsrv/dbaccess_parser.h>
 #include <dhcpsrv/dhcp_parsers.h>
 #include <dhcpsrv/dhcp_parsers.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/option_space_container.h>

src/bin/dhcp4/config_parser.h → src/bin/dhcp4/json_config_parser.h


+ 124 - 0
src/bin/dhcp4/kea_controller.cc

@@ -0,0 +1,124 @@
+// 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 <dhcp4/json_config_parser.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/dhcp4_log.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+void
+ControlledDhcpv4Srv::init(const std::string& file_name) {
+    // This is a configuration backend implementation that reads the
+    // configuration from a JSON file.
+
+    isc::data::ConstElementPtr json;
+    isc::data::ConstElementPtr dhcp4;
+    isc::data::ConstElementPtr result;
+
+    // Basic sanity check: file name must not be empty.
+    try {
+        if (file_name.empty()) {
+            // Basic sanity check: file name must not be empty.
+            isc_throw(BadValue, "JSON configuration file not specified. Please "
+                      "use -c command line option.");
+        }
+
+        // Read contents of the file and parse it as JSON
+        json = isc::data::Element::fromJSONFile(file_name, true);
+
+        if (!json) {
+            LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
+                .arg("Config file " + file_name + " missing or empty.");
+            isc_throw(BadValue, "Unable to process JSON configuration file:"
+                      + file_name);
+        }
+
+        // Get Dhcp4 component from the config
+        dhcp4 = json->get("Dhcp4");
+
+        if (!dhcp4) {
+            LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
+                .arg("Config file " + file_name + " does not include 'Dhcp4' entry.");
+            isc_throw(BadValue, "Unable to process JSON configuration file:"
+                      + file_name);
+        }
+
+        // Use parsed JSON structures to configure the server
+        result = processCommand("config-reload", dhcp4);
+
+    }  catch (const std::exception& ex) {
+        LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
+        isc_throw(BadValue, "Unable to process JSON configuration file:"
+                  + file_name);
+    }
+
+    if (!result) {
+        // Undetermined status of the configuration. This should never happen,
+        // but as the configureDhcp4Server returns a pointer, it is theoretically
+        // possible that it will return NULL.
+        LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
+            .arg("Configuration failed: Undefined result of processCommand("
+                 "config-reload, " + file_name + ")");
+        isc_throw(BadValue, "Configuration failed: Undefined result of "
+                  "processCommand('config-reload', " + file_name + ")");
+    }
+
+    // Now check is the returned result is successful (rcode=0) or not
+    isc::data::ConstElementPtr comment; /// see @ref isc::config::parseAnswer
+    int rcode;
+    comment = isc::config::parseAnswer(rcode, result);
+    if (rcode != 0) {
+        string reason = "";
+        if (comment) {
+            reason = string(" (") + comment->stringValue() + string(")");
+        }
+        LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(reason);
+        isc_throw(BadValue, "Failed to apply configuration:" << reason);
+    }
+
+    // We don't need to call openActiveSockets() or startD2() as these
+    // methods are called in processConfig() which is called by
+    // processCommand("reload-config", ...)
+}
+
+void ControlledDhcpv4Srv::cleanup() {
+    // Nothing to do here. No need to disconnect from anything.
+}
+
+/// This is a logger initialization for JSON file backend.
+/// For now, it's just setting log messages to be printed on stdout.
+/// @todo: Implement this properly (see #3427)
+void Daemon::loggerInit(const char*, bool verbose) {
+
+    setenv("B10_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1);
+    setenv("B10_LOGGER_ROOT", "kea", 0);
+    setenv("B10_LOGGER_SEVERITY", (verbose ? "DEBUG":"INFO"), 0);
+    setenv("B10_LOGGER_DBGLEVEL", "99", 0);
+    setenv("B10_LOGGER_DESTINATION",  "stdout", 0);
+    isc::log::initLogger();
+}
+
+};
+};

+ 40 - 33
src/bin/dhcp4/main.cc

@@ -39,13 +39,15 @@ namespace {
 
 
 const char* const DHCP4_NAME = "b10-dhcp4";
 const char* const DHCP4_NAME = "b10-dhcp4";
 
 
+const char* const DHCP4_LOGGER_NAME = "kea";
+
 void
 void
 usage() {
 usage() {
-    cerr << "Usage: " << DHCP4_NAME << " [-v] [-s] [-p number]" << endl;
+    cerr << "Usage: " << DHCP4_NAME << " [-v] [-p number] [-c file]" << endl;
     cerr << "  -v: verbose output" << endl;
     cerr << "  -v: verbose output" << endl;
-    cerr << "  -s: stand-alone mode (don't connect to BIND10)" << endl;
     cerr << "  -p number: specify non-standard port number 1-65535 "
     cerr << "  -p number: specify non-standard port number 1-65535 "
          << "(useful for testing only)" << endl;
          << "(useful for testing only)" << endl;
+    cerr << "  -c file: specify configuration file" << endl;
     exit(EXIT_FAILURE);
     exit(EXIT_FAILURE);
 }
 }
 } // end of anonymous namespace
 } // end of anonymous namespace
@@ -55,19 +57,17 @@ main(int argc, char* argv[]) {
     int ch;
     int ch;
     int port_number = DHCP4_SERVER_PORT; // The default. any other values are
     int port_number = DHCP4_SERVER_PORT; // The default. any other values are
                                          // useful for testing only.
                                          // useful for testing only.
-    bool stand_alone = false;  // Should be connect to BIND10 msgq?
     bool verbose_mode = false; // Should server be verbose?
     bool verbose_mode = false; // Should server be verbose?
 
 
-    while ((ch = getopt(argc, argv, "vsp:")) != -1) {
+    // The standard config file
+    std::string config_file("");
+
+    while ((ch = getopt(argc, argv, "vp:c:")) != -1) {
         switch (ch) {
         switch (ch) {
         case 'v':
         case 'v':
             verbose_mode = true;
             verbose_mode = true;
             break;
             break;
 
 
-        case 's':
-            stand_alone = true;
-            break;
-
         case 'p':
         case 'p':
             try {
             try {
                 port_number = boost::lexical_cast<int>(optarg);
                 port_number = boost::lexical_cast<int>(optarg);
@@ -83,6 +83,10 @@ main(int argc, char* argv[]) {
             }
             }
             break;
             break;
 
 
+        case 'c': // config file
+            config_file = optarg;
+            break;
+
         default:
         default:
             usage();
             usage();
         }
         }
@@ -93,36 +97,39 @@ main(int argc, char* argv[]) {
         usage();
         usage();
     }
     }
 
 
-    // Initialize logging.  If verbose, we'll use maximum verbosity.
-    // If standalone is enabled, do not buffer initial log messages
-    isc::log::initLogger(DHCP4_NAME,
-                         (verbose_mode ? isc::log::DEBUG : isc::log::INFO),
-                         isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
-    LOG_INFO(dhcp4_logger, DHCP4_STARTING);
-    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
-              .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
-              .arg(stand_alone ? "yes" : "no" );
-
-
     int ret = EXIT_SUCCESS;
     int ret = EXIT_SUCCESS;
+
     try {
     try {
+        // Initialize logging.  If verbose, we'll use maximum verbosity.
+        // If standalone is enabled, do not buffer initial log messages
+        Daemon::loggerInit(DHCP4_LOGGER_NAME, verbose_mode);
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
+            .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no");
+
+        LOG_INFO(dhcp4_logger, DHCP4_STARTING);
+
+        // Create the server instance.
         ControlledDhcpv4Srv server(port_number);
         ControlledDhcpv4Srv server(port_number);
-        if (!stand_alone) {
-            try {
-                server.establishSession();
-            } catch (const std::exception& ex) {
-                LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what());
-                // Let's continue. It is useful to have the ability to run
-                // DHCP server in stand-alone mode, e.g. for testing
-                // We do need to make sure logging is no longer buffered
-                // since then it would not print until dhcp6 is stopped
-                isc::log::LoggerManager log_manager;
-                log_manager.process();
-            }
-        } else {
-            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_STANDALONE);
+
+        try {
+            // Initialize the server.
+            server.init(config_file);
+        } catch (const std::exception& ex) {
+            LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what());
+
+            // We should not continue if were told to configure (either read
+            // config file or establish Bundy control session).
+
+            isc::log::LoggerManager log_manager;
+            log_manager.process();
+
+            cerr << "Failed to initialize server: " << ex.what() << endl;
+            return (EXIT_FAILURE);
         }
         }
+
+        // And run the main loop of the server.
         server.run();
         server.run();
+
         LOG_INFO(dhcp4_logger, DHCP4_SHUTDOWN);
         LOG_INFO(dhcp4_logger, DHCP4_SHUTDOWN);
 
 
     } catch (const std::exception& ex) {
     } catch (const std::exception& ex) {

+ 17 - 1
src/bin/dhcp4/tests/Makefile.am

@@ -33,6 +33,7 @@ AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
 
 CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
 CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
 CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
 CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
+CLEANFILES += *.json
 
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 if USE_CLANGPP
 if USE_CLANGPP
@@ -77,7 +78,7 @@ TESTS += dhcp4_unittests
 
 
 dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc
 dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc
 dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc
 dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc
-dhcp4_unittests_SOURCES += ../config_parser.cc ../config_parser.h
+dhcp4_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h
 dhcp4_unittests_SOURCES += d2_unittest.h d2_unittest.cc
 dhcp4_unittests_SOURCES += d2_unittest.h d2_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4_test_utils.h
 dhcp4_unittests_SOURCES += dhcp4_test_utils.h
 dhcp4_unittests_SOURCES += dhcp4_unittests.cc
 dhcp4_unittests_SOURCES += dhcp4_unittests.cc
@@ -89,6 +90,19 @@ dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += config_parser_unittest.cc
 dhcp4_unittests_SOURCES += config_parser_unittest.cc
 dhcp4_unittests_SOURCES += fqdn_unittest.cc
 dhcp4_unittests_SOURCES += fqdn_unittest.cc
 dhcp4_unittests_SOURCES += marker_file.cc
 dhcp4_unittests_SOURCES += marker_file.cc
+
+if CONFIG_BACKEND_BUNDY
+# For Bundy backend, we only need to run the usual tests. There are no
+# Bundy-specific tests yet.
+dhcp4_unittests_SOURCES += ../bundy_controller.cc
+dhcp4_unittests_SOURCES += bundy_controller_unittest.cc
+endif
+
+if CONFIG_BACKEND_JSON
+dhcp4_unittests_SOURCES += ../kea_controller.cc
+dhcp4_unittests_SOURCES += kea_controller_unittest.cc
+endif
+
 nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
 nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
 nodist_dhcp4_unittests_SOURCES += marker_file.h test_libraries.h
 nodist_dhcp4_unittests_SOURCES += marker_file.h test_libraries.h
 
 
@@ -109,4 +123,6 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
 endif
 endif
 
 
+noinst_EXTRA_DIST = configs-list.txt
+
 noinst_PROGRAMS = $(TESTS)
 noinst_PROGRAMS = $(TESTS)

+ 30 - 0
src/bin/dhcp4/tests/bundy_controller_unittest.cc

@@ -0,0 +1,30 @@
+// 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 <gtest/gtest.h>
+
+namespace {
+
+/// As of May 2014, maintaining or extending Bundy support is very low
+/// prority for Kea team. We are looking for contributors, who would
+/// like to maintain this backend.
+
+// Bundy framework specific tests should be added here.
+TEST(BundyBackendTest, dummy) {
+
+}
+
+} // End of anonymous namespace

+ 1 - 1
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -19,7 +19,7 @@
 
 
 #include <config/ccsession.h>
 #include <config/ccsession.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/dhcp4_srv.h>
-#include <dhcp4/config_parser.h>
+#include <dhcp4/json_config_parser.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_int.h>

+ 5 - 0
src/bin/dhcp4/tests/configs-list.txt

@@ -0,0 +1,5 @@
+# This is a list of config files that the unit-tests (specifically
+# JSONFileBackendTest.loadAllConfigs) is going to load.
+
+../../../../doc/examples/kea4/single-subnet.json
+../../../../doc/examples/kea4/several-subnets.json

+ 12 - 4
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc

@@ -85,12 +85,12 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
     int rcode = -1;
     int rcode = -1;
 
 
     // Case 1: send bogus command
     // Case 1: send bogus command
-    ConstElementPtr result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("blah", params);
+    ConstElementPtr result = ControlledDhcpv4Srv::processCommand("blah", params);
     ConstElementPtr comment = parseAnswer(rcode, result);
     ConstElementPtr comment = parseAnswer(rcode, result);
     EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
     EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
 
 
     // Case 2: send shutdown command without any parameters
     // Case 2: send shutdown command without any parameters
-    result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
+    result = ControlledDhcpv4Srv::processCommand("shutdown", params);
     comment = parseAnswer(rcode, result);
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
     EXPECT_EQ(0, rcode); // expect success
 
 
@@ -99,7 +99,7 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
     params->set("pid", x);
     params->set("pid", x);
 
 
     // Case 3: send shutdown command with 1 parameter: pid
     // Case 3: send shutdown command with 1 parameter: pid
-    result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
+    result = ControlledDhcpv4Srv::processCommand("shutdown", params);
     comment = parseAnswer(rcode, result);
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
     EXPECT_EQ(0, rcode); // expect success
 }
 }
@@ -107,6 +107,14 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
 // Check that the "libreload" command will reload libraries
 // Check that the "libreload" command will reload libraries
 
 
 TEST_F(CtrlDhcpv4SrvTest, libreload) {
 TEST_F(CtrlDhcpv4SrvTest, libreload) {
+
+    // Sending commands for processing now requires a server that can process
+    // them.
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv4Srv(0))
+    );
+
     // Ensure no marker files to start with.
     // Ensure no marker files to start with.
     ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
     ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
     ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
     ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
@@ -137,7 +145,7 @@ TEST_F(CtrlDhcpv4SrvTest, libreload) {
     int rcode = -1;
     int rcode = -1;
 
 
     ConstElementPtr result =
     ConstElementPtr result =
-        ControlledDhcpv4Srv::execDhcpv4ServerCommand("libreload", params);
+        ControlledDhcpv4Srv::processCommand("libreload", params);
     ConstElementPtr comment = parseAnswer(rcode, result);
     ConstElementPtr comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // Expect success
     EXPECT_EQ(0, rcode); // Expect success
 
 

+ 1 - 1
src/bin/dhcp4/tests/d2_unittest.cc

@@ -13,7 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
-#include <dhcp4/config_parser.h>
+#include <dhcp4/json_config_parser.h>
 #include <dhcp4/tests/d2_unittest.h>
 #include <dhcp4/tests/d2_unittest.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 
 

+ 1 - 1
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -32,7 +32,7 @@
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/dhcp4_log.h>
-#include <dhcp4/config_parser.h>
+#include <dhcp4/json_config_parser.h>
 #include <hooks/server_hooks.h>
 #include <hooks/server_hooks.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr.h>

+ 1 - 18
src/bin/dhcp4/tests/dhcp4_test.py

@@ -13,7 +13,7 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 
-from init import ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
+from init import ProcessInfo
 
 
 import unittest
 import unittest
 import sys
 import sys
@@ -207,22 +207,5 @@ class TestDhcpv4Daemon(unittest.TestCase):
         # Check that there is an error message about invalid port number printed on stderr
         # Check that there is an error message about invalid port number printed on stderr
         self.assertEqual( str(error).count("Failed to parse port number"), 1)
         self.assertEqual( str(error).count("Failed to parse port number"), 1)
 
 
-    def test_portnumber_nonroot(self):
-        print("Check that specifying unprivileged port number will work.")
-
-        # Check that there is a message about running with an unprivileged port
-        (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-v', '-s', '-p', '10057'])
-        output_text = str(output) + str(error)
-        self.assertEqual(output_text.count("DHCP4_OPEN_SOCKET opening sockets on port 10057"), 1)
-
-    def test_skip_msgq(self):
-        print("Check that connection to BIND10 msgq can be disabled.")
-
-        # Check that the system outputs a message on one of its streams about running
-        # standalone.
-        (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-v', '-s', '-p', '10057'])
-        output_text = str(output) + str(error)
-        self.assertEqual(output_text.count("DHCP4_STANDALONE"), 1)
-
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()

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

@@ -17,7 +17,7 @@
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <cc/data.h>
 #include <config/ccsession.h>
 #include <config/ccsession.h>
-#include <dhcp4/config_parser.h>
+#include <dhcp4/json_config_parser.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_int_array.h>

+ 1 - 1
src/bin/dhcp4/tests/direct_client_unittest.cc

@@ -19,7 +19,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
-#include <dhcp4/config_parser.h>
+#include <dhcp4/json_config_parser.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 #include <string>
 #include <string>

+ 296 - 0
src/bin/dhcp4/tests/kea_controller_unittest.cc

@@ -0,0 +1,296 @@
+// 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 <config/ccsession.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcpsrv/cfgmgr.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <unistd.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+
+namespace {
+
+class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
+    // "Naked" DHCPv4 server, exposes internal fields
+public:
+    NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(0) { }
+};
+
+/// @brief test class for Kea configuration backend
+///
+/// This class is used for testing Kea configuration backend.
+/// It is very simple and currently focuses on reading
+/// config file from disk. It is expected to be expanded in the
+/// near future.
+class JSONFileBackendTest : public ::testing::Test {
+public:
+    JSONFileBackendTest() {
+    }
+
+    ~JSONFileBackendTest() {
+        static_cast<void>(unlink(TEST_FILE));
+    };
+
+    /// @brief writes specified content to a well known file
+    ///
+    /// Writes specified content to TEST_FILE. Tests will
+    /// attempt to read that file.
+    ///
+    /// @param content content to be written to file
+    void writeFile(const std::string& content) {
+        static_cast<void>(unlink(TEST_FILE));
+
+        ofstream out(TEST_FILE, ios::trunc);
+        EXPECT_TRUE(out.is_open());
+        out << content;
+        out.close();
+    }
+
+    /// Name of a config file used during tests
+    static const char* TEST_FILE;
+};
+
+const char* JSONFileBackendTest::TEST_FILE  = "test-config.json";
+
+// This test checks if configuration can be read from a JSON file.
+TEST_F(JSONFileBackendTest, jsonFile) {
+
+    // Prepare configuration file.
+    string config = "{ \"Dhcp4\": { \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
+        "    \"subnet\": \"192.0.3.0/24\", "
+        "    \"id\": 0 "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
+        "    \"subnet\": \"192.0.4.0/24\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }"
+        "}";
+
+    writeFile(config);
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv4Srv(0))
+    );
+
+    // And configure it using the config file.
+    EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+    // Now check if the configuration has been applied correctly.
+    const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(3, subnets->size()); // We expect 3 subnets.
+
+
+    // Check subnet 1.
+    EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText());
+    EXPECT_EQ(24, subnets->at(0)->get().second);
+
+    // Check pools in the first subnet.
+    const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_V4);
+    ASSERT_EQ(1, pools1.size());
+    EXPECT_EQ("192.0.2.1", pools1.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("192.0.2.100", pools1.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
+
+    // Check subnet 2.
+    EXPECT_EQ("192.0.3.0", subnets->at(1)->get().first.toText());
+    EXPECT_EQ(24, subnets->at(1)->get().second);
+
+    // Check pools in the second subnet.
+    const PoolCollection& pools2 = subnets->at(1)->getPools(Lease::TYPE_V4);
+    ASSERT_EQ(1, pools2.size());
+    EXPECT_EQ("192.0.3.101", pools2.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("192.0.3.150", pools2.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_V4, pools2.at(0)->getType());
+
+    // And finally check subnet 3.
+    EXPECT_EQ("192.0.4.0", subnets->at(2)->get().first.toText());
+    EXPECT_EQ(24, subnets->at(2)->get().second);
+
+    // ... and it's only pool.
+    const PoolCollection& pools3 = subnets->at(2)->getPools(Lease::TYPE_V4);
+    EXPECT_EQ("192.0.4.101", pools3.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("192.0.4.150", pools3.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_V4, pools3.at(0)->getType());
+}
+
+// This test checks if configuration can be read from a JSON file.
+TEST_F(JSONFileBackendTest, comments) {
+
+    string config_hash_comments = "# This is a comment. It should be \n"
+        "#ignored. Real config starts in line below\n"
+        "{ \"Dhcp4\": { \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, \n"
+        "# comments in the middle should be ignored, too\n"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.0/24\" ],"
+        "    \"subnet\": \"192.0.2.0/22\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }"
+        "}";
+
+    /// @todo: Implement C++-style (// ...) comments
+    /// @todo: Implement C-style (/* ... */) comments
+
+    writeFile(config_hash_comments);
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv4Srv(0))
+    );
+
+    // And configure it using config with comments.
+    EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+    // Now check if the configuration has been applied correctly.
+    const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(1, subnets->size());
+
+    // Check subnet 1.
+    EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText());
+    EXPECT_EQ(22, subnets->at(0)->get().second);
+
+    // Check pools in the first subnet.
+    const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_V4);
+    ASSERT_EQ(1, pools1.size());
+    EXPECT_EQ("192.0.2.0", pools1.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("192.0.2.255", pools1.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
+}
+
+// This test checks if configuration detects failure when trying:
+// - empty file
+// - empty filename
+// - no Dhcp4 element
+// - Config file that contains Dhcp4 but has a content error
+TEST_F(JSONFileBackendTest, configBroken) {
+
+    // Empty config is not allowed, because Dhcp4 element is missing
+    string config_empty = "";
+
+    // This config does not have mandatory Dhcp4 element
+    string config_v4 = "{ \"Dhcp6\": { \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"2001:db8::/80\" ],"
+        "    \"subnet\": \"2001:db8::/64\" "
+        " } ]}";
+
+    // This has Dhcp4 element, but it's utter nonsense
+    string config_nonsense = "{ \"Dhcp4\": { \"reviews\": \"are so much fun\" } }";
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv4Srv(0))
+    );
+
+    // Try to configure without filename. Should fail.
+    EXPECT_THROW(srv->init(""), BadValue);
+
+    // Try to configure it using empty file. Should fail.
+    writeFile(config_empty);
+    EXPECT_THROW(srv->init(TEST_FILE), BadValue);
+
+    // Now try to load a config that does not have Dhcp4 component.
+    writeFile(config_v4);
+    EXPECT_THROW(srv->init(TEST_FILE), BadValue);
+
+    // Now try to load a config with Dhcp4 full of nonsense.
+    writeFile(config_nonsense);
+    EXPECT_THROW(srv->init(TEST_FILE), BadValue);
+}
+
+/// This unit-test reads all files enumerated in configs-test.txt file, loads
+/// each of them and verify that they can be loaded.
+///
+/// @todo: Unfortunately, we have this test disabled, because all loaded
+/// configs use memfile, which attempts to create lease file in
+/// /usr/local/var/bind10/kea-leases4.csv. We have couple options here:
+/// a) disable persistence in example configs - a very bad thing to do
+///    as users will forget to reenable it and then will be surprised when their
+///    leases disappear
+/// b) change configs to store lease file in /tmp. It's almost as bad as the
+///    previous one. Users will then be displeased when all their leases are
+///    wiped. (most systems wipe /tmp during boot)
+/// c) read each config and rewrite it on the fly, so persistence is disabled.
+///    This is probably the way to go, but this is a work for a dedicated ticket.
+///
+/// Hence I'm leaving the test in, but it is disabled.
+TEST_F(JSONFileBackendTest, DISABLED_loadAllConfigs) {
+
+    // Create server first
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv4Srv(0))
+    );
+
+    const char* configs_list = "configs-list.txt";
+    fstream configs(configs_list, ios::in);
+    ASSERT_TRUE(configs.is_open());
+    std::string config_name;
+    while (std::getline(configs, config_name)) {
+
+        // Ignore empty and commented lines
+        if (config_name.empty() || config_name[0] == '#') {
+            continue;
+        }
+
+        // Unit-tests usually do not print out anything, but in this case I
+        // think printing out tests configs is warranted.
+        std::cout << "Loading config file " << config_name << std::endl;
+
+        try {
+            srv->init(config_name);
+        } catch (const std::exception& ex) {
+            ADD_FAILURE() << "Exception thrown" << ex.what() << endl;
+        }
+    }
+}
+
+} // End of anonymous namespace

+ 2 - 2
src/bin/dhcp6/bundy_controller.cc

@@ -220,10 +220,10 @@ void ControlledDhcpv6Srv::cleanup() {
 }
 }
 
 
 void
 void
-Daemon::loggerInit(const char* log_name, bool verbose, bool stand_alone) {
+Daemon::loggerInit(const char* log_name, bool verbose) {
     isc::log::initLogger(log_name,
     isc::log::initLogger(log_name,
                          (verbose ? isc::log::DEBUG : isc::log::INFO),
                          (verbose ? isc::log::DEBUG : isc::log::INFO),
-                         isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
+                         isc::log::MAX_DEBUG_LEVEL, NULL, true);
 }
 }
 
 
 }; // end of isc::dhcp namespace
 }; // end of isc::dhcp namespace

+ 6 - 3
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -43,8 +43,8 @@ ControlledDhcpv6Srv::commandShutdownHandler(const string&, ConstElementPtr) {
 
 
 ConstElementPtr
 ConstElementPtr
 ControlledDhcpv6Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
 ControlledDhcpv6Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
-    // TODO delete any stored CalloutHandles referring to the old libraries
-    // Get list of currently loaded libraries and reload them.
+    /// @todo delete any stored CalloutHandles referring to the old libraries
+    /// Get list of currently loaded libraries and reload them.
     vector<string> loaded = HooksManager::getLibraryNames();
     vector<string> loaded = HooksManager::getLibraryNames();
     bool status = HooksManager::loadLibraries(loaded);
     bool status = HooksManager::loadLibraries(loaded);
     if (!status) {
     if (!status) {
@@ -99,9 +99,12 @@ ControlledDhcpv6Srv::processCommand(const std::string& command,
     }
     }
 }
 }
 
 
-
 isc::data::ConstElementPtr
 isc::data::ConstElementPtr
 ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
 ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
+
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_RECEIVED)
+              .arg(config->str());
+
     ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
     ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
 
 
     if (!srv) {
     if (!srv) {

+ 14 - 10
src/bin/dhcp6/ctrl_dhcp6_srv.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013  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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -26,12 +26,10 @@ namespace dhcp {
 
 
 /// @brief Controlled version of the DHCPv6 server
 /// @brief Controlled version of the DHCPv6 server
 ///
 ///
-/// This is a class that is responsible for establishing connection
-/// with msqg (receving commands and configuration). This is an extended
-/// version of Dhcpv6Srv class that is purely a DHCPv6 server, without
-/// external control. ControlledDhcpv6Srv should be used in typical BIND10
-/// (i.e. featuring msgq) environment, while Dhcpv6Srv should be used in
-/// embedded environments.
+/// This is a class that is responsible for DHCPv6 server being controllable.
+/// It does various things, depending on the configuration backend.
+/// For Bundy backend it establishes a connection with msqg and later receives
+/// commands over it. For Kea backend, it reads configuration file from disk.
 ///
 ///
 /// For detailed explanation or relations between main(), ControlledDhcpv6Srv,
 /// For detailed explanation or relations between main(), ControlledDhcpv6Srv,
 /// Dhcpv6Srv and other classes, see \ref dhcpv6Session.
 /// Dhcpv6Srv and other classes, see \ref dhcpv6Session.
@@ -53,7 +51,8 @@ public:
     /// operation. For specific details, see actual implementation in
     /// operation. For specific details, see actual implementation in
     /// *_backend.cc
     /// *_backend.cc
     ///
     ///
-    /// @return true if initialization was successful, false if it failed
+    /// This method may throw if initialization fails. Exception types may be
+    /// specific to used configuration backend.
     void init(const std::string& config_file);
     void init(const std::string& config_file);
 
 
     /// @brief Performs cleanup, immediately before termination
     /// @brief Performs cleanup, immediately before termination
@@ -77,6 +76,11 @@ public:
     /// wrapper that calls process*Command() methods and catches exceptions
     /// wrapper that calls process*Command() methods and catches exceptions
     /// in them.
     /// in them.
     ///
     ///
+    /// Currently supported commands are:
+    /// - shutdown
+    /// - libreload
+    /// - config-reload
+    ///
     /// @note It never throws.
     /// @note It never throws.
     ///
     ///
     /// @param command Text represenation of the command (e.g. "shutdown")
     /// @param command Text represenation of the command (e.g. "shutdown")
@@ -88,7 +92,7 @@ public:
 
 
     /// @brief configuration processor
     /// @brief configuration processor
     ///
     ///
-    /// This is a callback for handling incoming configuration updates.
+    /// This is a method for handling incoming configuration updates.
     /// This method should be called by all configuration backends when the
     /// This method should be called by all configuration backends when the
     /// server is starting up or when configuration has changed.
     /// server is starting up or when configuration has changed.
     ///
     ///
@@ -103,7 +107,7 @@ public:
 
 
     /// @brief returns pointer to the sole instance of Dhcpv6Srv
     /// @brief returns pointer to the sole instance of Dhcpv6Srv
     ///
     ///
-    /// @note may return NULL, if called before server is spawned
+    /// @return server instance (may return NULL, if called before server is spawned)
     static ControlledDhcpv6Srv* getInstance() {
     static ControlledDhcpv6Srv* getInstance() {
         return (server_);
         return (server_);
     }
     }

+ 10 - 9
src/bin/dhcp6/dhcp6.dox

@@ -234,24 +234,25 @@ cases it would be convenient to keep configuration in XML files.
 
 
 Kea 0.9 introduces configuration backends that are switchable during compilation phase.
 Kea 0.9 introduces configuration backends that are switchable during compilation phase.
 There is a new parameter for configure script: --with-kea-config. It currently supports
 There is a new parameter for configure script: --with-kea-config. It currently supports
-two values: BIND10 and JSON.
+two values: BUNDY and JSON.
 
 
-BIND10 (which is the default value as of April 2014) means that Kea6 is linked with the
-BIND10 configuration backend that connects to the BIND10 framework and in general works
+BUNDY (which is the default value as of April 2014) means that Kea6 is linked with the
+Bundy (former BIND10) configuration backend that connects to the BIND10 framework and in general works
 exactly the same as Kea 0.8 and earlier versions. The benefits of that backend are uniform
 exactly the same as Kea 0.8 and earlier versions. The benefits of that backend are uniform
-integration with BIND10 framework, easy on-line reconfiguration using bindctl, available
-RESTful API. On the other hand, it requires the whole heavy BIND10 framework that requires
-Python3 to be present. That backend is likely to go away with the release of Kea 0.9.
+integration with Bundy/BIND10 framework, easy on-line reconfiguration using bindctl, available
+RESTful API. On the other hand, it requires the whole heavy Bundy framework that requires
+Python3 to be present. That framework is going away with the release of Kea 0.9.
 
 
 JSON is a new configuration backend that causes Kea to read JSON configuration file from
 JSON is a new configuration backend that causes Kea to read JSON configuration file from
 disk. It does not require any framework and thus is considered more lightweight. It will
 disk. It does not require any framework and thus is considered more lightweight. It will
 allow dynamic on-line reconfiguration, but will lack remote capabilities (i.e. no RESTful
 allow dynamic on-line reconfiguration, but will lack remote capabilities (i.e. no RESTful
-API). This configuration backend is expected to be the default for upcoming Kea 0.9.
+API). This configuration backend is expected to be the default for upcoming Kea 0.9. It
+requires <tt> -c config-file </tt> command-line option.
 
 
 Internally, configuration backends are implemented as different implementations of the
 Internally, configuration backends are implemented as different implementations of the
-isc::dhcp::ControlledDhcpv6Srv class, stored in ctrl_*_dhcpv6_srv.cc files. Depending on
+isc::dhcp::ControlledDhcpv6Srv class, stored in {kea,bundy}_controller.cc files. Depending on
 the choice made by ./configure script, only one of those files is compiled and linked.
 the choice made by ./configure script, only one of those files is compiled and linked.
-There are backend specific tests in src/bin/dhcp6/tests/ctrl_*_dhcpv6_srv_unittest.cc.
+There are backend specific tests in src/bin/dhcp6/tests/{kea,bundy}_controller_unittest.cc.
 Only tests specific to selected backend are linked and executed during make distcheck.
 Only tests specific to selected backend are linked and executed during make distcheck.
 
 
 While it is unlikely that ISC will support more than one backend at any given time, there
 While it is unlikely that ISC will support more than one backend at any given time, there

+ 5 - 1
src/bin/dhcp6/dhcp6_messages.mes

@@ -40,6 +40,10 @@ address assignment.  The most likely cause is a problem with the client.
 A debug message listing the command (and possible arguments) received
 A debug message listing the command (and possible arguments) received
 from the BIND 10 control system by the IPv6 DHCP server.
 from the BIND 10 control system by the IPv6 DHCP server.
 
 
+% DHCP6_CONFIG_RECEIVED received configuration: %1
+A debug message listing the configuration received by the DHCPv6 server.
+The source of that configuration depends on used configuration backend.
+
 % DHCP6_CONFIG_COMPLETE DHCPv6 server has completed configuration: %1
 % DHCP6_CONFIG_COMPLETE DHCPv6 server has completed configuration: %1
 This is an informational message announcing the successful processing of a
 This is an informational message announcing the successful processing of a
 new configuration. it is output during server startup, and when an updated
 new configuration. it is output during server startup, and when an updated
@@ -540,7 +544,7 @@ production environment.
 This informational message indicates that the IPv6 DHCP server has
 This informational message indicates that the IPv6 DHCP server has
 processed any command-line switches and is starting.
 processed any command-line switches and is starting.
 
 
-% DHCP6_START_INFO pid: %1, port: %2, verbose: %3, standalone: %4
+% DHCP6_START_INFO pid: %1, port: %2, verbose: %3
 This is a debug message issued during the IPv6 DHCP server startup.
 This is a debug message issued during the IPv6 DHCP server startup.
 It lists some information about the parameters with which the server
 It lists some information about the parameters with which the server
 is running.
 is running.

+ 6 - 22
src/bin/dhcp6/kea_controller.cc

@@ -15,35 +15,19 @@
 #include <config.h>
 #include <config.h>
 
 
 #include <asiolink/asiolink.h>
 #include <asiolink/asiolink.h>
-#include <dhcp/iface_mgr.h>
 #include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_log.h>
-#include <dhcp6/spec_config.h>
-#include <log/logger_level.h>
-#include <log/logger_name.h>
-#include <log/logger_manager.h>
-#include <log/logger_specification.h>
-#include <log/logger_support.h>
-#include <log/output_option.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
-#include <util/buffer.h>
 
 
-#include <cassert>
-#include <iostream>
 #include <signal.h>
 #include <signal.h>
+
 #include <string>
 #include <string>
-#include <vector>
 
 
 using namespace isc::asiolink;
 using namespace isc::asiolink;
-using namespace isc::cc;
-using namespace isc::config;
-using namespace isc::data;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
-using namespace isc::log;
-using namespace isc::util;
 using namespace std;
 using namespace std;
 
 
 namespace {
 namespace {
@@ -74,7 +58,7 @@ void configure(const std::string& file_name) {
         }
         }
 
 
         // Read contents of the file and parse it as JSON
         // Read contents of the file and parse it as JSON
-        json = Element::fromJSONFile(file_name, true);
+        json = isc::data::Element::fromJSONFile(file_name, true);
 
 
         if (!json) {
         if (!json) {
             LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
             LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
@@ -113,9 +97,9 @@ void configure(const std::string& file_name) {
     }
     }
 
 
     // Now check is the returned result is successful (rcode=0) or not
     // Now check is the returned result is successful (rcode=0) or not
-    ConstElementPtr comment; /// see @ref isc::config::parseAnswer
+    isc::data::ConstElementPtr comment; /// see @ref isc::config::parseAnswer
     int rcode;
     int rcode;
-    comment = parseAnswer(rcode, result);
+    comment = isc::config::parseAnswer(rcode, result);
     if (rcode != 0) {
     if (rcode != 0) {
         string reason = "";
         string reason = "";
         if (comment) {
         if (comment) {
@@ -151,7 +135,7 @@ void signalHandler(int signo) {
                 .arg(file);
                 .arg(file);
         }
         }
     } else if ((signo == SIGTERM) || (signo == SIGINT)) {
     } else if ((signo == SIGTERM) || (signo == SIGINT)) {
-        ElementPtr params(new isc::data::MapElement());
+        isc::data::ElementPtr params(new isc::data::MapElement());
         ControlledDhcpv6Srv::processCommand("shutdown", params);
         ControlledDhcpv6Srv::processCommand("shutdown", params);
     }
     }
 }
 }
@@ -188,7 +172,7 @@ void ControlledDhcpv6Srv::cleanup() {
 /// This is a logger initialization for JSON file backend.
 /// This is a logger initialization for JSON file backend.
 /// For now, it's just setting log messages to be printed on stdout.
 /// For now, it's just setting log messages to be printed on stdout.
 /// @todo: Implement this properly (see #3427)
 /// @todo: Implement this properly (see #3427)
-void Daemon::loggerInit(const char*, bool verbose, bool ) {
+void Daemon::loggerInit(const char*, bool verbose) {
 
 
     setenv("B10_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1);
     setenv("B10_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1);
     setenv("B10_LOGGER_ROOT", "kea", 0);
     setenv("B10_LOGGER_ROOT", "kea", 0);

+ 20 - 28
src/bin/dhcp6/main.cc

@@ -43,9 +43,8 @@ const char* const DHCP6_LOGGER_NAME = "kea";
 
 
 void
 void
 usage() {
 usage() {
-    cerr << "Usage: " << DHCP6_NAME << " [-v] [-s] [-p port_number] [-c cfgfile]" << endl;
+    cerr << "Usage: " << DHCP6_NAME << " [-v] [-p port_number] [-c cfgfile]" << endl;
     cerr << "  -v: verbose output" << endl;
     cerr << "  -v: verbose output" << endl;
-    cerr << "  -s: skip configuration (don't connect to BIND10 or don't read config file)" << endl;
     cerr << "  -p number: specify non-standard port number 1-65535 "
     cerr << "  -p number: specify non-standard port number 1-65535 "
          << "(useful for testing only)" << endl;
          << "(useful for testing only)" << endl;
     cerr << "  -c file: specify configuration file" << endl;
     cerr << "  -c file: specify configuration file" << endl;
@@ -58,22 +57,17 @@ main(int argc, char* argv[]) {
     int ch;
     int ch;
     int port_number = DHCP6_SERVER_PORT; // The default. Any other values are
     int port_number = DHCP6_SERVER_PORT; // The default. Any other values are
                                          // useful for testing only.
                                          // useful for testing only.
-    bool stand_alone = false;  // Should be connect to BIND10 msgq?
     bool verbose_mode = false; // Should server be verbose?
     bool verbose_mode = false; // Should server be verbose?
 
 
     // The standard config file
     // The standard config file
     std::string config_file("");
     std::string config_file("");
 
 
-    while ((ch = getopt(argc, argv, "vsp:c:")) != -1) {
+    while ((ch = getopt(argc, argv, "vp:c:")) != -1) {
         switch (ch) {
         switch (ch) {
         case 'v':
         case 'v':
             verbose_mode = true;
             verbose_mode = true;
             break;
             break;
 
 
-        case 's': // stand-alone
-            stand_alone = true;
-            break;
-
         case 'p': // port number
         case 'p': // port number
             try {
             try {
                 port_number = boost::lexical_cast<int>(optarg);
                 port_number = boost::lexical_cast<int>(optarg);
@@ -107,39 +101,37 @@ main(int argc, char* argv[]) {
     int ret = EXIT_SUCCESS;
     int ret = EXIT_SUCCESS;
     try {
     try {
         // Initialize logging.  If verbose, we'll use maximum verbosity.
         // Initialize logging.  If verbose, we'll use maximum verbosity.
-        // If standalone is enabled, do not buffer initial log messages
-        Daemon::loggerInit(DHCP6_LOGGER_NAME, verbose_mode, stand_alone);
+        Daemon::loggerInit(DHCP6_LOGGER_NAME, verbose_mode);
 
 
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_START_INFO)
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_START_INFO)
-            .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
-            .arg(stand_alone ? "yes" : "no" );
+            .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no");
 
 
         LOG_INFO(dhcp6_logger, DHCP6_STARTING);
         LOG_INFO(dhcp6_logger, DHCP6_STARTING);
 
 
+        // Create the server instance.
         ControlledDhcpv6Srv server(port_number);
         ControlledDhcpv6Srv server(port_number);
 
 
-        if (!stand_alone) {
-            try {
-                // Initialize the server, i.e. establish control session
-                // if BIND10 backend is used or read a configuration file
-                server.init(config_file);
+        try {
+            // Initialize the server, i.e. establish control session
+            // if BIND10 backend is used or read a configuration file
+            // if Kea backend is used.
+            server.init(config_file);
 
 
-            } catch (const std::exception& ex) {
-                LOG_ERROR(dhcp6_logger, DHCP6_INIT_FAIL).arg(ex.what());
+        } catch (const std::exception& ex) {
+            LOG_ERROR(dhcp6_logger, DHCP6_INIT_FAIL).arg(ex.what());
 
 
-                // We should not continue if were told to configure (either read
-                // config file or establish BIND10 control session).
-                isc::log::LoggerManager log_manager;
-                log_manager.process();
+            // We should not continue if were told to configure (either read
+            // config file or establish BIND10 control session).
+            isc::log::LoggerManager log_manager;
+            log_manager.process();
 
 
-                cerr << "Failed to initialize server: " << ex.what() << endl;
-                return (EXIT_FAILURE);
-            }
-        } else {
-            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_STANDALONE);
+            cerr << "Failed to initialize server: " << ex.what() << endl;
+            return (EXIT_FAILURE);
         }
         }
 
 
+        // And run the main loop of the server.
         server.run();
         server.run();
+
         LOG_INFO(dhcp6_logger, DHCP6_SHUTDOWN);
         LOG_INFO(dhcp6_logger, DHCP6_SHUTDOWN);
 
 
     } catch (const std::exception& ex) {
     } catch (const std::exception& ex) {

+ 4 - 0
src/bin/dhcp6/tests/bundy_controller_unittest.cc

@@ -18,6 +18,10 @@
 
 
 namespace {
 namespace {
 
 
+/// As of May 2014, maintaining or extending Bundy support is very low
+/// prority for Kea team. We are looking for contributors, who would
+/// like to maintain this backend.
+
 // Bundy framework specific tests should be added here.
 // Bundy framework specific tests should be added here.
 TEST(BundyBackendTest, dummy) {
 TEST(BundyBackendTest, dummy) {
 
 

+ 0 - 17
src/bin/dhcp6/tests/dhcp6_test.py

@@ -210,22 +210,5 @@ class TestDhcpv6Daemon(unittest.TestCase):
         # Check that there is an error message about invalid port number printed on stderr
         # Check that there is an error message about invalid port number printed on stderr
         self.assertEqual( str(error).count("Failed to parse port number"), 1)
         self.assertEqual( str(error).count("Failed to parse port number"), 1)
 
 
-    def test_portnumber_nonroot(self):
-        print("Check that specifying unprivileged port number will work.")
-
-        # Check that there is a message about running with an unprivileged port
-        (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-v', '-s', '-p', '10547'])
-        output_text = str(output) + str(error)
-        self.assertEqual(output_text.count("DHCP6_OPEN_SOCKET opening sockets on port 10547"), 1)
-
-    def test_skip_msgq(self):
-        print("Check that connection to BIND10 msgq can be disabled.")
-
-        # Check that the system outputs a message on one of its streams about running
-        # standalone.
-        (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-v', '-s', '-p', '10547'])
-        output_text = str(output) + str(error)
-        self.assertEqual(output_text.count("DHCP6_STANDALONE"), 1)
-
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()

+ 6 - 2
src/lib/dhcpsrv/daemon.h

@@ -100,8 +100,12 @@ public:
 
 
     /// Initializes logger
     /// Initializes logger
     ///
     ///
-    /// This method initializes logger.
-    static void loggerInit(const char* log_name, bool verbose, bool stand_alone);
+    /// This method initializes logger. Currently its implementation is specific
+    /// to each configuration backend.
+    ///
+    /// @param log_name name used in logger initialization
+    /// @param verbose verbose mode (true usually enables DEBUG messages)
+    static void loggerInit(const char* log_name, bool verbose);
 
 
 private:
 private: