Browse Source

Merge branch 'trac3400'

Tomek Mrugalski 11 years ago
parent
commit
7e9fdfa644
36 changed files with 1628 additions and 342 deletions
  1. 7 0
      ChangeLog
  2. 34 0
      configure.ac
  3. 1 0
      doc/devel/mainpage.dox
  4. 36 0
      doc/examples/kea6/several-subnets.json
  5. 43 1
      doc/guide/bind10-guide.xml
  6. 10 2
      src/bin/dhcp6/Makefile.am
  7. 227 0
      src/bin/dhcp6/bundy_controller.cc
  8. 99 210
      src/bin/dhcp6/ctrl_dhcp6_srv.cc
  9. 82 55
      src/bin/dhcp6/ctrl_dhcp6_srv.h
  10. 39 0
      src/bin/dhcp6/dhcp6.dox
  11. 5 4
      src/bin/dhcp6/dhcp6_messages.mes
  12. 2 7
      src/bin/dhcp6/dhcp6_srv.h
  13. 1 1
      src/bin/dhcp6/config_parser.cc
  14. 0 0
      src/bin/dhcp6/json_config_parser.h
  15. 143 0
      src/bin/dhcp6/kea_controller.cc
  16. 40 20
      src/bin/dhcp6/main.cc
  17. 17 4
      src/bin/dhcp6/tests/Makefile.am
  18. 26 0
      src/bin/dhcp6/tests/bundy_controller_unittest.cc
  19. 6 1
      src/bin/dhcp6/tests/config_parser_unittest.cc
  20. 71 20
      src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
  21. 1 1
      src/bin/dhcp6/tests/d2_unittest.cc
  22. 3 3
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
  23. 1 1
      src/bin/dhcp6/tests/dhcp6_test_utils.cc
  24. 1 1
      src/bin/dhcp6/tests/hooks_unittest.cc
  25. 238 0
      src/bin/dhcp6/tests/kea_controller_unittest.cc
  26. 1 1
      src/bin/dhcp6/tests/rebind_unittest.cc
  27. 57 6
      src/lib/cc/data.cc
  28. 46 3
      src/lib/cc/data.h
  29. 1 0
      src/lib/cc/tests/Makefile.am
  30. 107 0
      src/lib/cc/tests/data_file_unittests.cc
  31. 109 1
      src/lib/cc/tests/data_unittests.cc
  32. 3 0
      src/lib/dhcpsrv/Makefile.am
  33. 45 0
      src/lib/dhcpsrv/daemon.cc
  34. 93 0
      src/lib/dhcpsrv/daemon.h
  35. 1 0
      src/lib/dhcpsrv/tests/Makefile.am
  36. 32 0
      src/lib/dhcpsrv/tests/daemon_unittest.cc

+ 7 - 0
ChangeLog

@@ -1,3 +1,10 @@
+7XX.	[func]*		tomek
+	b10-dhcp6: New parameter added to configure: --with-kea-config.
+	It allows selecting configuration backend and accepts one of two
+	values: BIND10, which uses BIND10 framework as Kea 0.8 did, or
+	JSON, which reads configuration from a JSON file.
+	(Trac #3400, git TBD)
+
 782.	[func]		tmark
 	Added sender-ip, sender-port, and max-queue-size parameters to
 	the dhcp-ddns configuration section of both b10-dhcp4 and b10-dhcp6.

+ 34 - 0
configure.ac

@@ -565,6 +565,9 @@ AC_SUBST(PYCOVERAGE)
 AC_SUBST(PYCOVERAGE_RUN)
 AC_SUBST(USE_PYCOVERAGE)
 
+
+
+
 enable_gtest="no"
 GTEST_INCLUDES=
 
@@ -1282,6 +1285,34 @@ AC_SUBST(PERL)
 AC_PATH_PROGS(AWK, gawk awk)
 AC_SUBST(AWK)
 
+
+# Kea configuration backend section
+# Currently there are 2 backends available: BUNDY and JSON
+# It is possible that we may extend this to accept additional backends.
+AC_ARG_WITH(kea-config,
+    AC_HELP_STRING([--with-kea-config],
+    [Selects configuration backend; currently available options are: BUNDY (default,
+    Kea reads configuration and commands from Bundy framework) or JSON (Kea reads
+    configuration from a JSON file from disk)]),
+    [CONFIG_BACKEND="$withval"],
+    [CONFIG_BACKEND=BUNDY])
+
+AM_CONDITIONAL(CONFIG_BACKEND_BUNDY, test "x$CONFIG_BACKEND" = "xBUNDY")
+AM_CONDITIONAL(CONFIG_BACKEND_JSON,   test "x$CONFIG_BACKEND" = "xJSON")
+
+if test "x$CONFIG_BACKEND" = "xBUNDY"; then
+    AC_DEFINE(CONFIG_BACKEND_BUNDY, 1, [Define to 1 if Kea config was set to BUNDY])
+fi
+
+if test "x$CONFIG_BACKEND" = "xJSON"; then
+    AC_DEFINE(CONFIG_BACKEND_JSON, 1, [Define to 1 if Kea config was set to JSON])
+fi
+
+# Let's sanity check if the specified backend value is allowed
+if test "x$CONFIG_BACKEND" != "xBUNDY" && test "x$CONFIG_BACKEND" != "xJSON"; then
+   AC_MSG_ERROR("Invalid configuration backend specified: $CONFIG_BACKEND. The only supported are: BUNDY JSON")
+fi
+
 AC_ARG_ENABLE(generate_docs, [AC_HELP_STRING([--enable-generate-docs],
   [regenerate documentation using Docbook [default=no]])],
   enable_generate_docs=$enableval, enable_generate_docs=no)
@@ -1620,6 +1651,9 @@ SQLite:
   SQLITE_VERSION:  ${SQLITE_VERSION}
   SQLITE_CFLAGS:   ${SQLITE_CFLAGS}
   SQLITE_LIBS:     ${SQLITE_LIBS}
+
+Kea config backend:
+  CONFIG_BACKEND: ${CONFIG_BACKEND}
 END
 
 # Avoid confusion on DNS/DHCP and only mention MySQL if it

+ 1 - 0
doc/devel/mainpage.dox

@@ -63,6 +63,7 @@
  *   - @subpage dhcpv6DDNSIntegration
  *   - @subpage dhcpv6OptionsParse
  *   - @subpage dhcpv6Classifier
+ *   - @subpage dhcpv6ConfigBackend
  *   - @subpage dhcpv6Other
  * - @subpage d2
  *   - @subpage d2CPL

+ 36 - 0
doc/examples/kea6/several-subnets.json

@@ -0,0 +1,36 @@
+# This is an example configuration file for DHCPv6 server in Kea.
+# It's a basic scenario with four IPv6 subnets configured. In each
+# subnet, there's a smaller pool of dynamic addresses.
+
+{ "Dhcp6":
+
+{ 
+# Kea is told to listen on eth0 interface only.
+  "interfaces": [ "eth0" ],
+
+# Addresses will be assigned with preferred and valid lifetimes
+# being 3000 and 4000, respectively. 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).
+  "preferred-lifetime": 3000,
+  "valid-lifetime": 4000,
+  "renew-timer": 1000,
+  "rebind-timer": 2000,
+
+# The following list defines subnets. Each subnet consists of at
+# least subnet and pool entries.
+  "subnet6": [ 
+  {    "pool": [ "2001:db8:1::/80" ],
+       "subnet": "2001:db8:1::/64"  },
+  {    "pool": [ "2001:db8:2::/80" ],
+       "subnet": "2001:db8:2::/64"  }, 
+  {    "pool": [ "2001:db8:3::/80" ],
+       "subnet": "2001:db8:3::/64"  },
+  {    "pool": [ "2001:db8:4::/80" ],
+       "subnet": "2001:db8:4::/64"  } ]
+}
+
+}
+

+ 43 - 1
doc/guide/bind10-guide.xml

@@ -710,7 +710,9 @@ as a dependency earlier -->
           <note>
             <para>
               For additional instructions concerning the building and installation of
-              Kea, see <xref linkend="dhcp-install-configure"/>.
+              Kea for various databases, see <xref linkend="dhcp-install-configure"/>.
+              For additional instructions concerning configuration backends, see
+              <xref linkend="dhcp-config-backend" />.
             </para>
           </note>
         </para>
@@ -1864,6 +1866,46 @@ address, but the usual ones don't." mean? -->
     The DHCP-DDNS server details are covered in <xref linkend="dhcp-ddns-server"/>
     </para>
 
+
+    <section id="dhcp-config-backend">
+      <title>Selecting configuration backend</title>
+      <para>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: BIND10 and
+      JSON. This is currently only supported by DHCPv6 component.</para>
+
+      <variablelist>
+
+        <varlistentry>
+          <term>BIND10</term>
+          <listitem>
+            <simpara>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 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.</simpara>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>JSON</term>
+          <listitem>
+            <simpara>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.</simpara>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+
+    </section>
+
     <section id="dhcp-install-configure">
       <title>DHCP Database Installation and Configuration</title>
       <para>

+ 10 - 2
src/bin/dhcp6/Makefile.am

@@ -52,10 +52,18 @@ BUILT_SOURCES = spec_config.h dhcp6_messages.h dhcp6_messages.cc
 pkglibexec_PROGRAMS = b10-dhcp6
 
 b10_dhcp6_SOURCES  = main.cc
-b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
-b10_dhcp6_SOURCES += config_parser.cc config_parser.h
 b10_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h
 b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
+b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
+b10_dhcp6_SOURCES += json_config_parser.cc json_config_parser.h
+
+if CONFIG_BACKEND_BUNDY
+b10_dhcp6_SOURCES += bundy_controller.cc
+endif
+
+if CONFIG_BACKEND_JSON
+b10_dhcp6_SOURCES += kea_controller.cc
+endif
 
 nodist_b10_dhcp6_SOURCES = dhcp6_messages.h dhcp6_messages.cc
 EXTRA_DIST += dhcp6_messages.mes

+ 227 - 0
src/bin/dhcp6/bundy_controller.cc

@@ -0,0 +1,227 @@
+// 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 <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/dhcp6_log.h>
+#include <dhcp6/spec_config.h>
+#include <exceptions/exceptions.h>
+#include <hooks/hooks_manager.h>
+#include <util/buffer.h>
+
+#include <cassert>
+#include <iostream>
+#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 dhcp6ConfigHandler 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
+dhcp6StubConfigHandler(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 (!ControlledDhcpv6Srv::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(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE)
+            .arg(merged_config->str());
+    }
+
+    // Configure the server.
+
+    return (ControlledDhcpv6Srv::processConfig(merged_config));
+}
+
+void
+ControlledDhcpv6Srv::init(const std::string& /* config_file*/) {
+    // This is Bundy configuration backed. It established control session
+    // that is used to connect to Bundy framework.
+    //
+    // Creates session that will be used to receive commands and updated
+    // configuration from cfgmgr (or indirectly from user via bindctl).
+
+    string specfile;
+    if (getenv("B10_FROM_BUILD")) {
+        specfile = string(getenv("B10_FROM_BUILD")) +
+            "/src/bin/dhcp6/dhcp6.spec";
+    } else {
+        specfile = string(DHCP6_SPECFILE_LOCATION);
+    }
+
+    /// @todo: Check if session is not established already. Throw, if it is.
+
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_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_,
+                                          dhcp6StubConfigHandler,
+                                          processCommand, false);
+    config_session_->start();
+
+    // The constructor already pulled the configuration that had
+    // been created in the previous session thanks to the dummy
+    // handler. We can switch to the handler that will be
+    // parsing future changes to the configuration.
+    config_session_->setConfigHandler(bundyConfigHandler);
+
+    try {
+        // Pull the full configuration out from the session.
+        processConfig(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());
+
+    } catch (const std::exception& ex) {
+        LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
+
+    }
+
+    /// Integrate the asynchronous I/O model of Bundy configuration
+    /// control with the "select" model of the DHCP server.  This is
+    /// fully explained in \ref dhcpv6Session.
+    int ctrl_socket = cc_session_->getSocketDesc();
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTED)
+              .arg(ctrl_socket);
+    IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
+
+    return;
+}
+
+void ControlledDhcpv6Srv::cleanup() {
+    if (config_session_) {
+        delete config_session_;
+        config_session_ = NULL;
+    }
+    if (cc_session_) {
+
+        int ctrl_socket = cc_session_->getSocketDesc();
+        cc_session_->disconnect();
+
+        // deregister session socket
+        IfaceMgr::instance().deleteExternalSocket(ctrl_socket);
+
+        delete cc_session_;
+        cc_session_ = NULL;
+    }
+}
+
+void
+Daemon::loggerInit(const char* log_name, bool verbose, bool stand_alone) {
+    isc::log::initLogger(log_name,
+                         (verbose ? isc::log::DEBUG : isc::log::INFO),
+                         isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 99 - 210
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 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
 // purpose with or without fee is hereby granted, provided that the above
@@ -13,35 +13,14 @@
 // 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 <dhcpsrv/dhcp_config_parser.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <dhcp6/config_parser.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>
-#include <dhcp6/spec_config.h>
-#include <exceptions/exceptions.h>
 #include <hooks/hooks_manager.h>
-#include <util/buffer.h>
-
-#include <cassert>
-#include <iostream>
-#include <string>
-#include <vector>
+#include <dhcp6/json_config_parser.h>
 
-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 {
@@ -50,73 +29,105 @@ namespace dhcp {
 ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
 
 ConstElementPtr
-ControlledDhcpv6Srv::dhcp6StubConfigHandler(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."));
+ControlledDhcpv6Srv::commandShutdownHandler(const string&, ConstElementPtr) {
+    if (ControlledDhcpv6Srv::server_) {
+        ControlledDhcpv6Srv::server_->shutdown();
+    } else {
+        LOG_WARN(dhcp6_logger, DHCP6_NOT_RUNNING);
+        ConstElementPtr answer = isc::config::createAnswer(1, "Shutdown failure.");
+        return (answer);
+    }
+    ConstElementPtr answer = isc::config::createAnswer(0, "Shutting down.");
+    return (answer);
 }
 
 ConstElementPtr
-ControlledDhcpv6Srv::dhcp6ConfigHandler(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.");
+ControlledDhcpv6Srv::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(dhcp6_logger, DHCP6_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.");
+    return (answer);
+}
+
+ConstElementPtr
+ControlledDhcpv6Srv::commandConfigReloadHandler(const string&, ConstElementPtr args) {
+
+    return (processConfig(args));
+}
 
-    // 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.
+isc::data::ConstElementPtr
+ControlledDhcpv6Srv::processCommand(const std::string& command,
+                                    isc::data::ConstElementPtr args) {
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_COMMAND_RECEIVED)
+              .arg(command).arg(args->str());
 
-    // 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());
+    ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
 
-        // Merge an existing and new configuration.
-        isc::data::merge(merged_config, new_config);
-        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE)
-            .arg(merged_config->str());
+    if (!srv) {
+        ConstElementPtr no_srv = isc::config::createAnswer(1,
+          "Server object not initialized, can't process command '" +
+          command + "'.");
+        return (no_srv);
     }
 
-    // Configure the server.
-    ConstElementPtr answer = configureDhcp6Server(*server_, merged_config);
+    try {
+        if (command == "shutdown") {
+            return (srv->commandShutdownHandler(command, args));
 
-    // Check that configuration was successful. If not, do not reopen sockets.
-    int rcode = 0;
-    parseAnswer(rcode, answer);
-    if (rcode != 0) {
-        return (answer);
+        } else if (command == "libreload") {
+            return (srv->commandLibReloadHandler(command, args));
+
+        } else if (command == "config-reload") {
+            return (srv->commandConfigReloadHandler(command, args));
+        }
+
+        return (isc::config::createAnswer(1, "Unrecognized command:"
+                                          + command));
+
+    } catch (const Exception& ex) {
+        return (isc::config::createAnswer(1, "Error while processing command '"
+                                          + command + "':" + ex.what()));
+    }
+}
+
+
+isc::data::ConstElementPtr
+ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
+    ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
+
+    if (!srv) {
+        ConstElementPtr no_srv = isc::config::createAnswer(1,
+          "Server object not initialized, can't process config.");
+        return (no_srv);
+    }
+
+    ConstElementPtr answer = configureDhcp6Server(*srv, config);
+
+    // Check that configuration was successful. If not, do not reopen sockets
+    // and don't bother with DDNS stuff.
+    try {
+        int rcode = 0;
+        isc::config::parseAnswer(rcode, answer);
+        if (rcode != 0) {
+            return (answer);
+        }
+    } catch (const std::exception& ex) {
+        return (isc::config::createAnswer(1, "Failed to process configuration:"
+                                          + string(ex.what())));
     }
 
     // Server will start DDNS communications if its enabled.
     try {
-        server_->startD2();
+        srv->startD2();
     } catch (const std::exception& ex) {
         std::ostringstream err;
         err << "error starting DHCP_DDNS client "
@@ -129,141 +140,22 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
     // safe and we really don't want to emit exceptions to the callback caller.
     // Instead, catch an exception and create appropriate answer.
     try {
-        server_->openActiveSockets(server_->getPort());
+        srv->openActiveSockets(srv->getPort());
     } catch (const std::exception& ex) {
         std::ostringstream err;
         err << "failed to open sockets after server reconfiguration: " << ex.what();
         answer = isc::config::createAnswer(1, err.str());
     }
-    return (answer);
-}
-
-ConstElementPtr
-ControlledDhcpv6Srv::dhcp6CommandHandler(const string& command, ConstElementPtr args) {
-    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_COMMAND_RECEIVED)
-              .arg(command).arg(args->str());
-
-    if (command == "shutdown") {
-        if (ControlledDhcpv6Srv::server_) {
-            ControlledDhcpv6Srv::server_->shutdown();
-        } else {
-            LOG_WARN(dhcp6_logger, DHCP6_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 == "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(dhcp6_logger, DHCP6_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.");
-        return (answer);
-    }
-
-    ConstElementPtr answer = isc::config::createAnswer(1,
-                             "Unrecognized command.");
 
     return (answer);
 }
 
-void ControlledDhcpv6Srv::sessionReader(void) {
-    // Process one asio event. If there are more events, iface_mgr will call
-    // this callback more than once.
+ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
+    : Dhcpv6Srv(port) {
     if (server_) {
-        server_->io_service_.run_one();
-    }
-}
-
-void ControlledDhcpv6Srv::establishSession() {
-
-    string specfile;
-    if (getenv("B10_FROM_BUILD")) {
-        specfile = string(getenv("B10_FROM_BUILD")) +
-            "/src/bin/dhcp6/dhcp6.spec";
-    } else {
-        specfile = string(DHCP6_SPECFILE_LOCATION);
-    }
-
-    /// @todo: Check if session is not established already. Throw, if it is.
-
-    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_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_,
-                                          dhcp6StubConfigHandler,
-                                          dhcp6CommandHandler, false);
-    config_session_->start();
-
-    // The constructor already pulled the configuration that had
-    // been created in the previous session thanks to the dummy
-    // handler. We can switch to the handler that will be
-    // parsing future changes to the configuration.
-    config_session_->setConfigHandler(dhcp6ConfigHandler);
-
-    try {
-        // Pull the full configuration out from the session.
-        configureDhcp6Server(*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());
-
-    } catch (const std::exception& ex) {
-        LOG_ERROR(dhcp6_logger, DHCP6_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 dhcpv6Session.
-    int ctrl_socket = cc_session_->getSocketDesc();
-    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTED)
-              .arg(ctrl_socket);
-    IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
-}
-
-void ControlledDhcpv6Srv::disconnectSession() {
-    if (config_session_) {
-        delete config_session_;
-        config_session_ = NULL;
-    }
-    if (cc_session_) {
-
-        int ctrl_socket = cc_session_->getSocketDesc();
-        cc_session_->disconnect();
-
-        // deregister session socket
-        IfaceMgr::instance().deleteExternalSocket(ctrl_socket);
-
-        delete cc_session_;
-        cc_session_ = NULL;
+        isc_throw(InvalidOperation,
+                  "There is another Dhcpv6Srv instance already.");
     }
-}
-
-ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
-    : Dhcpv6Srv(port), cc_session_(NULL), config_session_(NULL) {
     server_ = this; // remember this instance for use in callback
 }
 
@@ -273,22 +165,19 @@ void ControlledDhcpv6Srv::shutdown() {
 }
 
 ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
-    disconnectSession();
+    cleanup();
 
     server_ = NULL; // forget this instance. There should be no callback anymore
                     // at this stage anyway.
 }
 
-isc::data::ConstElementPtr
-ControlledDhcpv6Srv::execDhcpv6ServerCommand(const std::string& command_id,
-                                             isc::data::ConstElementPtr args) {
-    try {
-        return (dhcp6CommandHandler(command_id, args));
-    } catch (const Exception& ex) {
-        ConstElementPtr answer = isc::config::createAnswer(1, ex.what());
-        return (answer);
+void ControlledDhcpv6Srv::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();
     }
 }
 
-};
-};
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 82 - 55
src/bin/dhcp6/ctrl_dhcp6_srv.h

@@ -44,44 +44,53 @@ public:
     ControlledDhcpv6Srv(uint16_t port = DHCP6_SERVER_PORT);
 
     /// @brief Destructor.
-    ~ControlledDhcpv6Srv();
+    virtual ~ControlledDhcpv6Srv();
 
-    /// @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
+    ///
+    /// @return true if initialization was successful, false if it failed
+    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 Dhcpv6Srv 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)
-    /// 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 DHCPv6 server.
     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.
+    ///
+    /// @note It never throws.
     ///
     /// @param command Text represenation of the command (e.g. "shutdown")
     /// @param args Optional parameters
     ///
     /// @return status of the command
     static isc::data::ConstElementPtr
-    execDhcpv6ServerCommand(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 ControlledDhcpv6Srv* server_;
-
-    /// @brief A callback for handling incoming configuration updates.
+    /// This is a callback 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
     /// ModuleCCSession, it has to be static.
@@ -90,53 +99,71 @@ protected:
     ///
     /// @return status of the config update
     static isc::data::ConstElementPtr
-    dhcp6ConfigHandler(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 dhcp6ConfigHandler 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
-    dhcp6StubConfigHandler(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 Dhcpv6Srv
     ///
-    /// @param command textual representation of the command
-    /// @param args parameters of the command
+    /// @note may return NULL, if called before server is spawned
+    static ControlledDhcpv6Srv* 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
-    dhcp6CommandHandler(const std::string& command, isc::data::ConstElementPtr args);
+    /// This is required for config and command handlers to gain access to
+    /// the server. Some of them need to be static methods.
+    static ControlledDhcpv6Srv* 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::receive6() method,
-    /// when there is a new command or configuration sent over msgq.
+    /// 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);
 
     /// @brief IOService object, used for all ASIO operations.
     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 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 Session that receives configuration and commands
-    isc::config::ModuleCCSession* config_session_;
+    /// @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

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

@@ -223,6 +223,45 @@ being passed in isc::dhcp::Dhcpv6Srv::selectSubnet() to isc::dhcp::CfgMgr::getSu
 Currently this capability is usable, but the number of scenarios it supports is
 limited.
 
+ @section dhcpv6ConfigBackend Configuration backend for DHCPv6
+
+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: BIND10 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
+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.
+
+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.
+
+Internally, configuration backends are implemented as different implementations of the
+isc::dhcp::ControlledDhcpv6Srv class, stored in ctrl_*_dhcpv6_srv.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/dhcp6/tests/ctrl_*_dhcpv6_srv_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 dhcpv6Other Other DHCPv6 topics
 
  For hooks API support in DHCPv6, see @ref dhcpv6Hooks.

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

@@ -499,10 +499,11 @@ to recover.
 The IPv6 DHCP server has encountered a fatal error and is terminating.
 The reason for the failure is included in the message.
 
-% DHCP6_SESSION_FAIL failed to establish BIND 10 session (%1), running stand-alone
-The server has failed to establish communication with the rest of BIND
-10 and is running in stand-alone mode.  (This behavior will change once
-the IPv6 DHCP server is properly integrated with the rest of BIND 10.)
+% DHCP6_INIT_FAIL failed to initialize Kea server: %1
+The server has failed to establish communication with the rest of BIND 10,
+failed to read JSON configuration file or excountered any other critical
+issue that prevents it from starting up properly. Attached error message
+provides more details about the issue.
 
 % DHCP6_SHUTDOWN server shutdown
 The IPv6 DHCP server has terminated normally.

+ 2 - 7
src/bin/dhcp6/dhcp6_srv.h

@@ -27,8 +27,7 @@
 #include <dhcpsrv/d2_client_mgr.h>
 #include <dhcpsrv/subnet.h>
 #include <hooks/callout_handle.h>
-
-#include <boost/noncopyable.hpp>
+#include <dhcpsrv/daemon.h>
 
 #include <iostream>
 #include <queue>
@@ -52,11 +51,7 @@ public:
 /// that is going to be used as server-identifier, receives incoming
 /// packets, processes them, manages leases assignment and generates
 /// appropriate responses.
-///
-/// @note Only one instance of this class is instantiated as it encompasses
-///       the whole operation of the server.  Nothing, however, enforces the
-///       singleton status of the object.
-class Dhcpv6Srv : public boost::noncopyable {
+class Dhcpv6Srv : public Daemon {
 
 public:
     /// @brief defines if certain option may, must or must not appear

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

@@ -16,7 +16,7 @@
 #include <cc/data.h>
 #include <config/ccsession.h>
 #include <dhcp/libdhcp++.h>
-#include <dhcp6/config_parser.h>
+#include <dhcp6/json_config_parser.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcpsrv/cfgmgr.h>

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


+ 143 - 0
src/bin/dhcp6/kea_controller.cc

@@ -0,0 +1,143 @@
+// 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 <asiolink/asiolink.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/ctrl_dhcp6_srv.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 <util/buffer.h>
+
+#include <cassert>
+#include <iostream>
+#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::log;
+using namespace isc::util;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+void
+ControlledDhcpv6Srv::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 dhcp6;
+    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 = Element::fromJSONFile(file_name, true);
+
+        if (!json) {
+            LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
+                .arg("Config file " + file_name + " missing or empty.");
+            isc_throw(BadValue, "Unable to process JSON configuration file:"
+                      + file_name);
+        }
+
+        // Get Dhcp6 component from the config
+        dhcp6 = json->get("Dhcp6");
+
+        if (!dhcp6) {
+            LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
+                .arg("Config file " + file_name + " does not include 'Dhcp6' entry.");
+            isc_throw(BadValue, "Unable to process JSON configuration file:"
+                      + file_name);
+        }
+
+        // Use parsed JSON structures to configure the server
+        result = processCommand("config-reload", dhcp6);
+
+    }  catch (const std::exception& ex) {
+        LOG_ERROR(dhcp6_logger, DHCP6_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 configureDhcp6Server returns a pointer, it is theoretically
+        // possible that it will return NULL.
+        LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
+            .arg("Configuration failed: Undefined result of configureDhcp6Server"
+                 "() function after attempting to read " + file_name);
+        return;
+    }
+
+    // Now check is the returned result is successful (rcode=0) or not
+    ConstElementPtr comment; /// see @ref isc::config::parseAnswer
+    int rcode;
+    comment = parseAnswer(rcode, result);
+    if (rcode != 0) {
+        string reason = "";
+        if (comment) {
+            reason = string(" (") + comment->stringValue() + string(")");
+        }
+        LOG_ERROR(dhcp6_logger, DHCP6_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 ControlledDhcpv6Srv::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, bool ) {
+
+    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 - 20
src/bin/dhcp6/main.cc

@@ -18,6 +18,7 @@
 #include <dhcp6/dhcp6_log.h>
 #include <log/logger_support.h>
 #include <log/logger_manager.h>
+#include <exceptions/exceptions.h>
 
 #include <boost/lexical_cast.hpp>
 
@@ -38,13 +39,16 @@ using namespace std;
 namespace {
 const char* const DHCP6_NAME = "b10-dhcp6";
 
+const char* const DHCP6_LOGGER_NAME = "kea";
+
 void
 usage() {
-    cerr << "Usage: " << DHCP6_NAME << " [-v] [-s] [-p number]" << endl;
+    cerr << "Usage: " << DHCP6_NAME << " [-v] [-s] [-p port_number] [-c cfgfile]" << endl;
     cerr << "  -v: verbose output" << endl;
-    cerr << "  -s: stand-alone mode (don't connect to BIND10)" << 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 "
          << "(useful for testing only)" << endl;
+    cerr << "  -c file: specify configuration file" << endl;
     exit(EXIT_FAILURE);
 }
 } // end of anonymous namespace
@@ -57,17 +61,20 @@ main(int argc, char* argv[]) {
     bool stand_alone = false;  // Should be connect to BIND10 msgq?
     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, "vsp:c:")) != -1) {
         switch (ch) {
         case 'v':
             verbose_mode = true;
             break;
 
-        case 's':
+        case 's': // stand-alone
             stand_alone = true;
             break;
 
-        case 'p':
+        case 'p': // port number
             try {
                 port_number = boost::lexical_cast<int>(optarg);
             } catch (const boost::bad_lexical_cast &) {
@@ -82,6 +89,10 @@ main(int argc, char* argv[]) {
             }
             break;
 
+        case 'c': // config file
+            config_file = optarg;
+            break;
+
         default:
             usage();
         }
@@ -92,39 +103,48 @@ main(int argc, char* argv[]) {
         usage();
     }
 
-    // Initialize logging.  If verbose, we'll use maximum verbosity.
-    // If standalone is enabled, do not buffer initial log messages
-    isc::log::initLogger(DHCP6_NAME,
-                         (verbose_mode ? isc::log::DEBUG : isc::log::INFO),
-                         isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
-    LOG_INFO(dhcp6_logger, DHCP6_STARTING);
-    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" );
 
     int ret = EXIT_SUCCESS;
     try {
+        // 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);
+
+        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" );
+
+        LOG_INFO(dhcp6_logger, DHCP6_STARTING);
+
         ControlledDhcpv6Srv server(port_number);
+
         if (!stand_alone) {
             try {
-                server.establishSession();
+                // Initialize the server, i.e. establish control session
+                // if BIND10 backend is used or read a configuration file
+                server.init(config_file);
+
             } catch (const std::exception& ex) {
-                LOG_ERROR(dhcp6_logger, DHCP6_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
+                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();
+
+                cerr << "Failed to initialize server: " << ex.what() << endl;
+                return (EXIT_FAILURE);
             }
         } else {
             LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_STANDALONE);
         }
+
         server.run();
         LOG_INFO(dhcp6_logger, DHCP6_SHUTDOWN);
 
     } catch (const std::exception& ex) {
         LOG_FATAL(dhcp6_logger, DHCP6_SERVER_FAILED).arg(ex.what());
+        cerr << "Fatal error during start up: " << ex.what() << endl;
         ret = EXIT_FAILURE;
     }
 

+ 17 - 4
src/bin/dhcp6/tests/Makefile.am

@@ -24,6 +24,7 @@ check-local:
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
 AM_CPPFLAGS += -I$(top_srcdir)/src/bin
+AM_CPPFLAGS += -DTOP_BUILDDIR="\"$(top_builddir)\""
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
@@ -75,17 +76,29 @@ dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += fqdn_unittest.cc
 dhcp6_unittests_SOURCES += hooks_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6_test_utils.cc dhcp6_test_utils.h
-dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
-dhcp6_unittests_SOURCES += config_parser_unittest.cc
 dhcp6_unittests_SOURCES += d2_unittest.cc d2_unittest.h
 dhcp6_unittests_SOURCES += marker_file.cc
 dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
-dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
-dhcp6_unittests_SOURCES += ../config_parser.cc ../config_parser.h
+dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.h ../ctrl_dhcp6_srv.cc
+dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += wireshark.cc
 dhcp6_unittests_SOURCES += dhcp6_client.cc dhcp6_client.h
 dhcp6_unittests_SOURCES += rebind_unittest.cc
+dhcp6_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h
+dhcp6_unittests_SOURCES += config_parser_unittest.cc
+
+if CONFIG_BACKEND_BUNDY
+# For Bundy backend, we only need to run the usual tests. There are no
+# Bundy-specific tests yet.
+dhcp6_unittests_SOURCES += ../bundy_controller.cc
+dhcp6_unittests_SOURCES += bundy_controller_unittest.cc
+endif
+
+if CONFIG_BACKEND_JSON
+dhcp6_unittests_SOURCES += ../kea_controller.cc
+dhcp6_unittests_SOURCES += kea_controller_unittest.cc
+endif
 
 nodist_dhcp6_unittests_SOURCES  = ../dhcp6_messages.h ../dhcp6_messages.cc
 nodist_dhcp6_unittests_SOURCES += marker_file.h test_libraries.h

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

@@ -0,0 +1,26 @@
+// 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 {
+
+// Bundy framework specific tests should be added here.
+TEST(BundyBackendTest, dummy) {
+
+}
+
+} // End of anonymous namespace

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

@@ -20,7 +20,7 @@
 #include <dhcp/iface_mgr.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
-#include <dhcp6/config_parser.h>
+#include <dhcp6/json_config_parser.h>
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcpsrv/addr_utilities.h>
 #include <dhcpsrv/cfgmgr.h>
@@ -591,6 +591,11 @@ TEST_F(Dhcp6ParserTest, multipleSubnets) {
 
     ElementPtr json = Element::fromJSON(config);
 
+    ofstream out("config.json");
+    out << config;
+    out.close();
+    
+
     do {
         EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
         checkResult(x, 0);

+ 71 - 20
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc

@@ -15,7 +15,7 @@
 #include <config.h>
 
 #include <config/ccsession.h>
-#include <dhcp/dhcp6.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <hooks/hooks_manager.h>
 
@@ -25,17 +25,7 @@
 #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::dhcp::test;
@@ -85,13 +75,13 @@ TEST_F(CtrlDhcpv6SrvTest, commands) {
     int rcode = -1;
 
     // Case 1: send bogus command
-    ConstElementPtr result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("blah", params);
-    ConstElementPtr comment = parseAnswer(rcode, result);
+    ConstElementPtr result = ControlledDhcpv6Srv::processCommand("blah", params);
+    ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
     EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
 
     // Case 2: send shutdown command without any parameters
-    result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params);
-    comment = parseAnswer(rcode, result);
+    result = ControlledDhcpv6Srv::processCommand("shutdown", params);
+    comment = isc::config::parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
 
     const pid_t pid(getpid());
@@ -99,14 +89,21 @@ TEST_F(CtrlDhcpv6SrvTest, commands) {
     params->set("pid", x);
 
     // Case 3: send shutdown command with 1 parameter: pid
-    result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params);
-    comment = parseAnswer(rcode, result);
+    result = ControlledDhcpv6Srv::processCommand("shutdown", params);
+    comment = isc::config::parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // Expect success
 }
 
 // Check that the "libreload" command will reload libraries
-
 TEST_F(CtrlDhcpv6SrvTest, libreload) {
+
+    // Sending commands for processing now requires a server that can process
+    // them.
+    boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv6Srv(0))
+    );
+
     // Ensure no marker files to start with.
     ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
     ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
@@ -137,8 +134,8 @@ TEST_F(CtrlDhcpv6SrvTest, libreload) {
     int rcode = -1;
 
     ConstElementPtr result =
-        ControlledDhcpv6Srv::execDhcpv6ServerCommand("libreload", params);
-    ConstElementPtr comment = parseAnswer(rcode, result);
+        ControlledDhcpv6Srv::processCommand("libreload", params);
+    ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // Expect success
 
     // Check that the libraries have unloaded and reloaded.  The libraries are
@@ -148,4 +145,58 @@ TEST_F(CtrlDhcpv6SrvTest, libreload) {
     EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "1212"));
 }
 
+// Check that the "configReload" command will reload libraries
+TEST_F(CtrlDhcpv6SrvTest, configReload) {
+
+    // Sending commands for processing now requires a server that can process
+    // them.
+    boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv6Srv(0))
+    );
+
+    // Now execute the "libreload" command.  This should cause the libraries
+    // to unload and to reload.
+
+    // Use empty parameters list
+    // Prepare configuration file.
+    string config_txt = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:2::/80\" ],"
+        "    \"subnet\": \"2001:db8:2::/64\", "
+        "    \"id\": 0"
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:3::/80\" ],"
+        "    \"subnet\": \"2001:db8:3::/64\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr config = Element::fromJSON(config_txt);
+
+    // Make sure there are no subnets configured.
+    CfgMgr::instance().deleteSubnets6();
+
+    // Now send the command
+    int rcode = -1;
+    ConstElementPtr result =
+        ControlledDhcpv6Srv::processCommand("config-reload", config);
+    ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
+    EXPECT_EQ(0, rcode); // Expect success
+
+    // Check that the config was indeed applied.
+    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    EXPECT_EQ(3, subnets->size());
+
+    // Clean up after the test.
+    CfgMgr::instance().deleteSubnets6();
+}
+
 } // End of anonymous namespace

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

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

+ 3 - 3
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -26,7 +26,7 @@
 #include <dhcp/option_string.h>
 #include <dhcp/option_vendor.h>
 #include <dhcp/iface_mgr.h>
-#include <dhcp6/config_parser.h>
+#include <dhcp6/json_config_parser.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
@@ -197,12 +197,12 @@ TEST_F(Dhcpv6SrvTest, basic) {
 
     ASSERT_NO_THROW( {
         // Skip opening any sockets
-        srv.reset(new Dhcpv6Srv(0));
+        srv.reset(new NakedDhcpv6Srv(0));
     });
     srv.reset();
     ASSERT_NO_THROW({
         // open an unpriviledged port
-        srv.reset(new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000));
+        srv.reset(new NakedDhcpv6Srv(DHCP6_SERVER_PORT + 10000));
     });
 }
 

+ 1 - 1
src/bin/dhcp6/tests/dhcp6_test_utils.cc

@@ -14,7 +14,7 @@
 
 #include <gtest/gtest.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
-#include <dhcp6/config_parser.h>
+#include <dhcp6/json_config_parser.h>
 
 using namespace isc::data;
 using namespace isc::dhcp;

+ 1 - 1
src/bin/dhcp6/tests/hooks_unittest.cc

@@ -17,7 +17,7 @@
 #include <asiolink/io_address.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/duid.h>
-#include <dhcp6/config_parser.h>
+#include <dhcp6/json_config_parser.h>
 #include <dhcp/dhcp6.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr.h>

+ 238 - 0
src/bin/dhcp6/tests/kea_controller_unittest.cc

@@ -0,0 +1,238 @@
+// 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 <config/ccsession.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp6/ctrl_dhcp6_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 NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
+    // "Naked" DHCPv6 server, exposes internal fields
+public:
+    NakedControlledDhcpv6Srv():ControlledDhcpv6Srv(0) { }
+};
+
+
+class JSONFileBackendTest : public ::testing::Test {
+public:
+    JSONFileBackendTest() {
+    }
+
+    ~JSONFileBackendTest() {
+        static_cast<void>(unlink(TEST_FILE));
+    };
+
+    void writeFile(const std::string& file_name, const std::string& content) {
+        static_cast<void>(unlink(file_name.c_str()));
+
+        ofstream out(file_name.c_str(), ios::trunc);
+        EXPECT_TRUE(out.is_open());
+        out << content;
+        out.close();
+    }
+
+    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 = "{ \"Dhcp6\": { \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:2::/80\" ],"
+        "    \"subnet\": \"2001:db8:2::/64\", "
+        "    \"id\": 0"
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:3::/80\" ],"
+        "    \"subnet\": \"2001:db8:3::/64\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }"
+        "}";
+    writeFile(TEST_FILE, config);
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv6Srv(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 Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(3, subnets->size()); // We expect 3 subnets.
+
+
+    // Check subnet 1.
+    EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText());
+    EXPECT_EQ(64, subnets->at(0)->get().second);
+
+    // Check pools in the first subnet.
+    const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
+    ASSERT_EQ(1, pools1.size());
+    EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
+
+    // Check subnet 2.
+    EXPECT_EQ("2001:db8:2::", subnets->at(1)->get().first.toText());
+    EXPECT_EQ(64, subnets->at(1)->get().second);
+
+    // Check pools in the second subnet.
+    const PoolCollection& pools2 = subnets->at(1)->getPools(Lease::TYPE_NA);
+    ASSERT_EQ(1, pools2.size());
+    EXPECT_EQ("2001:db8:2::", pools2.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("2001:db8:2::ffff:ffff:ffff", pools2.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_NA, pools2.at(0)->getType());
+
+    // And finally check subnet 3.
+    EXPECT_EQ("2001:db8:3::", subnets->at(2)->get().first.toText());
+    EXPECT_EQ(64, subnets->at(2)->get().second);
+
+    // ... and it's only pool.
+    const PoolCollection& pools3 = subnets->at(2)->getPools(Lease::TYPE_NA);
+    EXPECT_EQ("2001:db8:3::", pools3.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("2001:db8:3::ffff:ffff:ffff", pools3.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_NA, 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"
+        "{ \"Dhcp6\": { \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, \n"
+        "# comments in the middle should be ignored, too\n"
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }"
+        "}";
+
+    /// @todo: Implement C++-style (// ...) comments
+    /// @todo: Implement C-style (/* ... */) comments
+
+    writeFile(TEST_FILE, config_hash_comments);
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv6Srv(0))
+    );
+
+    // And configure it using config without
+    EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+    // Now check if the configuration has been applied correctly.
+    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(1, subnets->size());
+
+    // Check subnet 1.
+    EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText());
+    EXPECT_EQ(64, subnets->at(0)->get().second);
+
+    // Check pools in the first subnet.
+    const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
+    ASSERT_EQ(1, pools1.size());
+    EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
+}
+
+// This test checks if configuration detects failure when trying:
+// - empty file
+// - empty filename
+// - no Dhcp6 element
+// - Config file that contains Dhcp6 but has a content error
+TEST_F(JSONFileBackendTest, configBroken) {
+
+    // Empty config is not allowed, because Dhcp6 element is missing
+    string config_empty = "";
+
+    // This config does not have mandatory Dhcp6 element
+    string config_v4 = "{ \"Dhcp4\": { \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.0/24\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" "
+        " } ]}";
+
+    // This has Dhcp6 element, but it's utter nonsense
+    string config_nonsense = "{ \"Dhcp6\": { \"reviews\": \"are so much fun\" } }";
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv6Srv(0))
+    );
+
+    // Try to configure without filename. Should fail.
+    EXPECT_THROW(srv->init(""), BadValue);
+
+    // Try to configure it using empty file. Should fail.
+    writeFile(TEST_FILE, config_empty);
+    EXPECT_THROW(srv->init(TEST_FILE), BadValue);
+
+    // Now try to load a config that does not have Dhcp6 component.
+    writeFile(TEST_FILE, config_v4);
+    EXPECT_THROW(srv->init(TEST_FILE), BadValue);
+
+    // Now try to load a config with Dhcp6 full of nonsense.
+    writeFile(TEST_FILE, config_nonsense);
+    EXPECT_THROW(srv->init(TEST_FILE), BadValue);
+}
+
+} // End of anonymous namespace

+ 1 - 1
src/bin/dhcp6/tests/rebind_unittest.cc

@@ -16,7 +16,7 @@
 #include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
-#include <dhcp6/config_parser.h>
+#include <dhcp6/json_config_parser.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcp6/tests/dhcp6_client.h>
 

+ 57 - 6
src/lib/cc/data.cc

@@ -24,6 +24,7 @@
 #include <iostream>
 #include <string>
 #include <sstream>
+#include <fstream>
 #include <cerrno>
 #include <climits>
 
@@ -610,17 +611,29 @@ Element::nameToType(const std::string& type_name) {
 }
 
 ElementPtr
-Element::fromJSON(std::istream& in) throw(JSONError) {
+Element::fromJSON(std::istream& in, bool preproc) throw(JSONError) {
+
     int line = 1, pos = 1;
-    return (fromJSON(in, "<istream>", line, pos));
+    stringstream filtered;
+    if (preproc) {
+        preprocess(in, filtered);
+    }
+
+    ElementPtr value = fromJSON(preproc ? filtered : in, "<istream>", line, pos);
+
+    return (value);
 }
 
 ElementPtr
-Element::fromJSON(std::istream& in, const std::string& file_name)
+Element::fromJSON(std::istream& in, const std::string& file_name, bool preproc)
     throw(JSONError)
 {
     int line = 1, pos = 1;
-    return (fromJSON(in, file_name, line, pos));
+    stringstream filtered;
+    if (preproc) {
+        preprocess(in, filtered);
+    }
+    return (fromJSON(preproc ? filtered : in, file_name, line, pos));
 }
 
 ElementPtr
@@ -698,11 +711,16 @@ Element::fromJSON(std::istream& in, const std::string& file, int& line,
 }
 
 ElementPtr
-Element::fromJSON(const std::string& in) {
+Element::fromJSON(const std::string& in, bool preproc) {
     std::stringstream ss;
     ss << in;
+
     int line = 1, pos = 1;
-    ElementPtr result(fromJSON(ss, "<string>", line, pos));
+    stringstream filtered;
+    if (preproc) {
+        preprocess(ss, filtered);
+    }
+    ElementPtr result(fromJSON(preproc ? filtered : ss, "<string>", line, pos));
     skipChars(ss, WHITESPACE, line, pos);
     // ss must now be at end
     if (ss.peek() != EOF) {
@@ -711,6 +729,20 @@ Element::fromJSON(const std::string& in) {
     return result;
 }
 
+ElementPtr
+Element::fromJSONFile(const std::string& file_name,
+                      bool preproc) {
+    std::ifstream infile(file_name.c_str(), std::ios::in | std::ios::binary);
+    if (!infile.is_open())
+    {
+        const char* error = strerror(errno);
+        isc_throw(InvalidOperation, "Failed to read file " << file_name
+                  << ",error:" << error);
+    }
+
+    return (fromJSON(infile, file_name, preproc));
+}
+
 // to JSON format
 
 void
@@ -1029,5 +1061,24 @@ merge(ElementPtr element, ConstElementPtr other) {
     }
 }
 
+void Element::preprocess(std::istream& in, std::stringstream& out) {
+
+    std::string line;
+
+    while (std::getline(in, line)) {
+        // If this is a comments line, replace it with empty line
+        // (so the line numbers will still match
+        if (!line.empty() && line[0] == '#') {
+            line = "";
+        }
+
+        // getline() removes end line charaters. Unfortunately, we need
+        // it for getting the line numbers right (in case we report an
+        // error.
+        out << line;
+        out << "\n";
+    }
+}
+
 }
 }

+ 46 - 3
src/lib/cc/data.h

@@ -151,6 +151,7 @@ protected:
         : type_(t), position_(pos) {
     }
 
+
 public:
 
     // any is a special type used in list specifications, specifying
@@ -401,18 +402,34 @@ public:
     //@{
     /// Creates an Element from the given JSON string
     /// \param in The string to parse the element from
+    /// \param preproc specified whether preprocessing (e.g. comment removal)
+    ///                should be performed
     /// \return An ElementPtr that contains the element(s) specified
     /// in the given string.
-    static ElementPtr fromJSON(const std::string& in);
+    static ElementPtr fromJSON(const std::string& in, bool preproc = false);
 
     /// Creates an Element from the given input stream containing JSON
     /// formatted data.
     ///
     /// \param in The string to parse the element from
+    /// \param preproc specified whether preprocessing (e.g. comment removal)
+    ///                should be performed
     /// \return An ElementPtr that contains the element(s) specified
     /// in the given input stream.
-    static ElementPtr fromJSON(std::istream& in) throw(JSONError);
-    static ElementPtr fromJSON(std::istream& in, const std::string& file_name)
+    static ElementPtr fromJSON(std::istream& in, bool preproc = false)
+        throw(JSONError);
+
+    /// Creates an Element from the given input stream containing JSON
+    /// formatted data.
+    ///
+    /// \param in The string to parse the element from
+    /// \param file_name specified input file name (used in error reporting)
+    /// \param preproc specified whether preprocessing (e.g. comment removal)
+    ///                should be performed
+    /// \return An ElementPtr that contains the element(s) specified
+    /// in the given input stream.
+    static ElementPtr fromJSON(std::istream& in, const std::string& file_name,
+                               bool preproc = false)
         throw(JSONError);
 
     /// Creates an Element from the given input stream, where we keep
@@ -430,6 +447,16 @@ public:
     static ElementPtr fromJSON(std::istream& in, const std::string& file,
                                int& line, int &pos)
         throw(JSONError);
+
+    /// Reads contents of specified file and interprets it as JSON.
+    ///
+    /// @param file_name name of the file to read
+    /// @param preproc specified whether preprocessing (e.g. comment removal)
+    ///                should be performed
+    /// @return An ElementPtr that contains the element(s) specified
+    /// if the given file.
+    static ElementPtr fromJSONFile(const std::string& file_name,
+                                   bool preproc = false);
     //@}
 
     /// \name Type name conversion functions
@@ -448,6 +475,22 @@ public:
     /// \return the corresponding type value
     static Element::types nameToType(const std::string& type_name);
 
+    /// \brief input text preprocessor
+    ///
+    /// This method performs preprocessing of the input stream (which is
+    /// expected to contain a text version of to be parsed JSON). For now the
+    /// sole supported operation is bash-style (line starting with #) comment
+    /// removal, but it will be extended later to cover more cases (C, C++ style
+    /// comments, file inclusions, maybe macro replacements?).
+    ///
+    /// This method processes the whole input stream. It reads all contents of
+    /// the input stream, filters the content and returns the result in a
+    /// different stream.
+    ///
+    /// @param in input stream to be preprocessed
+    /// @param out output stream (filtered content will be written here)
+    static void preprocess(std::istream& in, std::stringstream& out);
+
     /// \name Wire format factory functions
 
     /// These function pparse the wireformat at the given stringstream

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

@@ -21,6 +21,7 @@ if HAVE_GTEST
 TESTS += run_unittests
 # (TODO: these need to be completed and moved to tests/)
 run_unittests_SOURCES = data_unittests.cc session_unittests.cc run_unittests.cc
+run_unittests_SOURCES += data_file_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 

+ 107 - 0
src/lib/cc/tests/data_file_unittests.cc

@@ -0,0 +1,107 @@
+// 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 <exceptions/exceptions.h>
+#include <dhcpsrv/daemon.h>
+#include <gtest/gtest.h>
+#include <cc/data.h>
+#include <fstream>
+
+using namespace isc;
+using namespace isc::data;
+
+namespace {
+
+/// @brief Test class for testing Deamon class
+class DataFileTest : public ::testing::Test {
+public:
+
+    /// @brief writes specified text to a file
+    ///
+    /// That is an auxilliary funtion used in fileRead() tests.
+    ///
+    /// @param content text to be written to disk
+    void writeFile(const std::string& content) {
+        // Write sample content to disk
+        unlink(TEMP_FILE);
+        std::ofstream write_me(TEMP_FILE);
+        EXPECT_TRUE(write_me.is_open());
+        write_me << content;
+        write_me.close();
+    }
+
+    /// destructor
+    ~DataFileTest() {
+        static_cast<void>(unlink(TEMP_FILE));
+    }
+
+    /// Name of the temporary file
+    static const char* TEMP_FILE;
+};
+
+/// Temporary file name used in some tests
+const char* DataFileTest::TEMP_FILE="temp-file.json";
+
+// Test checks whether a text file can be read from disk.
+TEST_F(DataFileTest, readFileMultiline) {
+
+    const char* no_endline = "{ \"abc\": 123 }";
+    const char* with_endline = "{\n \"abc\":\n 123\n }\n";
+
+    // That's what we expect
+    ElementPtr exp = Element::fromJSON(no_endline);
+
+    // Write sample content to disk
+    writeFile(no_endline);
+
+    // Check that the read content is correct
+    EXPECT_TRUE(exp->equals(*Element::fromJSONFile(TEMP_FILE)));
+
+    // Write sample content to disk
+    writeFile(with_endline);
+
+    // Check that the read content is correct
+    EXPECT_TRUE(exp->equals(*Element::fromJSONFile(TEMP_FILE)));
+}
+
+// Test checks whether comments in file are ignored as expected.
+TEST_F(DataFileTest, readFileComments) {
+    const char* commented_content = "# This is a comment\n"
+        "{ \"abc\":\n"
+        "# a comment comment\n"
+        "1 }\n";
+
+    // That's what we expect
+    ElementPtr exp = Element::fromJSON("{ \"abc\": 1 }");
+
+    // Write sample content to disk
+    writeFile(commented_content);
+
+    // Check that the read will fail (without comment elimination)
+    EXPECT_THROW(Element::fromJSONFile(TEMP_FILE), JSONError);
+
+    // Check that the read content is correct (with comment elimination)
+    EXPECT_NO_THROW(Element::fromJSONFile(TEMP_FILE, true));
+    EXPECT_TRUE(exp->equals(*Element::fromJSONFile(TEMP_FILE, true)));
+}
+
+// This test checks that missing file will generate an exception.
+TEST_F(DataFileTest, readFileError) {
+
+    // Check that the read content is correct
+    EXPECT_THROW(Element::fromJSONFile("no-such-file.txt"), isc::InvalidOperation);
+}
+
+};

+ 109 - 1
src/lib/cc/tests/data_unittests.cc

@@ -939,6 +939,60 @@ TEST(Element, merge) {
 
 }
 
+// This test checks whether it is possible to ignore comments. It also checks
+// that the comments are ignored only when told to.
+TEST(Element, preprocessor) {
+
+    string no_comment = "{ \"a\": 1,\n"
+                        " \"b\": 2}";
+
+    string head_comment = "# this is a comment, ignore me\n"
+                          "{ \"a\": 1,\n"
+                          " \"b\": 2}";
+
+    string mid_comment = "{ \"a\": 1,\n"
+                         "# this is a comment, ignore me\n"
+                         " \"b\": 2}";
+
+    string tail_comment = "{ \"a\": 1,\n"
+                          " \"b\": 2}"
+                          "# this is a comment, ignore me\n";
+
+    string dbl_head_comment = "# this is a comment, ignore me\n"
+                              "# second line, still ignored\n"
+                              "{ \"a\": 1,\n"
+                              " \"b\": 2}";
+
+    string dbl_mid_comment = "{ \"a\": 1,\n"
+                             "# this is a comment, ignore me\n"
+                             "# second line, still ignored\n"
+                             " \"b\": 2}";
+
+    string dbl_tail_comment = "{ \"a\": 1,\n"
+                              " \"b\": 2}"
+                              "# this is a comment, ignore me\n"
+                              "# second line, still ignored\n";
+
+    // This is what we expect in all cases.
+    ElementPtr exp = Element::fromJSON(no_comment);
+
+    // Let's convert them all and see that the result it the same every time
+    EXPECT_TRUE(exp->equals(*Element::fromJSON(head_comment, true)));
+    EXPECT_TRUE(exp->equals(*Element::fromJSON(mid_comment, true)));
+    EXPECT_TRUE(exp->equals(*Element::fromJSON(tail_comment, true)));
+    EXPECT_TRUE(exp->equals(*Element::fromJSON(dbl_head_comment, true)));
+    EXPECT_TRUE(exp->equals(*Element::fromJSON(dbl_mid_comment, true)));
+    EXPECT_TRUE(exp->equals(*Element::fromJSON(dbl_tail_comment, true)));
+
+    // With preprocessing disabled, it should fail all around
+    EXPECT_THROW(Element::fromJSON(head_comment), JSONError);
+    EXPECT_THROW(Element::fromJSON(mid_comment), JSONError);
+    EXPECT_THROW(Element::fromJSON(tail_comment), JSONError);
+    EXPECT_THROW(Element::fromJSON(dbl_head_comment), JSONError);
+    EXPECT_THROW(Element::fromJSON(dbl_mid_comment), JSONError);
+    EXPECT_THROW(Element::fromJSON(dbl_tail_comment), JSONError);
+}
+
 TEST(Element, getPosition) {
     std::istringstream ss("{\n"
                           "    \"a\":  2,\n"
@@ -957,7 +1011,7 @@ TEST(Element, getPosition) {
     // Create a JSON string holding different type of values. Some of the
     // values in the config string are not aligned, so as we can check that
     // the position is set correctly for the elements.
-    ElementPtr top = Element::fromJSON(ss, "kea.conf");
+    ElementPtr top = Element::fromJSON(ss, string("kea.conf"));
     ASSERT_TRUE(top);
 
     // Element "a"
@@ -1033,4 +1087,58 @@ TEST(Element, getPosition) {
 
 }
 
+// Tests whether position is returned properly for a commented input JSON text.
+TEST(Element, getPositionCommented) {
+    std::istringstream ss("{\n"
+                          "    \"a\":  2,\n"
+                          "# comment\n"
+                          "    \"cy\": \"a string\",\n"
+                          "    \"dyz\": {\n"
+                          "# another comment\n"
+                          "      \"e\": 3,\n"
+                          "        \"f\": null\n"
+                          "\n"
+                          "    } }\n");
+
+    // Create a JSON string holding different type of values. Some of the
+    // values in the config string are not aligned, so as we can check that
+    // the position is set correctly for the elements.
+    ElementPtr top = Element::fromJSON(ss, string("kea.conf"), true);
+    ASSERT_TRUE(top);
+
+    // Element "a"
+    ConstElementPtr level1_el = top->get("a");
+    ASSERT_TRUE(level1_el);
+    EXPECT_EQ(2, level1_el->getPosition().line_);
+    EXPECT_EQ(11, level1_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
+
+    // Element "cy"
+    level1_el = top->get("cy");
+    ASSERT_TRUE(level1_el);
+    EXPECT_EQ(4, level1_el->getPosition().line_);
+    EXPECT_EQ(11, level1_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
+
+    // Element "dyz"
+    level1_el = top->get("dyz");
+    ASSERT_TRUE(level1_el);
+    EXPECT_EQ(5, level1_el->getPosition().line_);
+    EXPECT_EQ(13, level1_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
+
+    // Element "e" is a sub element of "dyz".
+    ConstElementPtr level2_el = level1_el->get("e");
+    ASSERT_TRUE(level2_el);
+    EXPECT_EQ(7, level2_el->getPosition().line_);
+    EXPECT_EQ(12, level2_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level2_el->getPosition().file_);
+
+    // Element "f" is also a sub element of "dyz"
+    level2_el = level1_el->get("f");
+    ASSERT_TRUE(level2_el);
+    EXPECT_EQ(8, level2_el->getPosition().line_);
+    EXPECT_EQ(14, level2_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level2_el->getPosition().file_);
+}
 }

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

@@ -3,6 +3,7 @@ SUBDIRS = . testutils tests
 dhcp_data_dir = @localstatedir@/@PACKAGE@
 
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -DDHCP_DATA_DIR="\"$(dhcp_data_dir)\""
+AM_CPPFLAGS += -DTOP_BUILDDIR="\"$(top_builddir)\""
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 if HAVE_MYSQL
 AM_CPPFLAGS += $(MYSQL_CPPFLAGS)
@@ -48,6 +49,7 @@ libkea_dhcpsrv_la_SOURCES += csv_lease_file4.cc csv_lease_file4.h
 libkea_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h
 libkea_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h
 libkea_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h
+libkea_dhcpsrv_la_SOURCES += daemon.cc daemon.h
 libkea_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libkea_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
@@ -58,6 +60,7 @@ libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
 libkea_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
+
 if HAVE_MYSQL
 libkea_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
 endif

+ 45 - 0
src/lib/dhcpsrv/daemon.cc

@@ -0,0 +1,45 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcpsrv/daemon.h>
+#include <exceptions/exceptions.h>
+#include <errno.h>
+
+/// @brief provides default implementation for basic daemon operations
+/// 
+/// This file provides stub implementations that are expected to be redefined
+/// in derived classes (e.g. ControlledDhcpv6Srv)
+namespace isc {
+namespace dhcp {
+
+Daemon::Daemon() {
+}
+
+void Daemon::init(const std::string&) {
+}
+
+void Daemon::cleanup() {
+
+}
+
+void Daemon::shutdown() {
+
+}
+
+Daemon::~Daemon() {
+}
+
+};
+};

+ 93 - 0
src/lib/dhcpsrv/daemon.h

@@ -0,0 +1,93 @@
+// 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 <string>
+#include <boost/noncopyable.hpp>
+
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Base class for all services
+///
+/// This is the base class that all daemons (DHCPv4, DHCPv6, D2 and possibly
+/// others) are derived from. It provides a standard interface for starting up,
+/// reconfiguring, shutting down and several other operations. It also covers
+/// some common operations.
+///
+/// This class is not expected to be instantiated directly, but rather daemon
+/// implementations should derive from it.
+///
+/// Methods are not pure virtual, as we need to instantiate basic daemons (e.g.
+/// Dhcpv6Srv) in tests, without going through the hassles of implemeting stub
+/// methods.
+///
+/// @note Only one instance of this class is instantiated as it encompasses
+///       the whole operation of the server.  Nothing, however, enforces the
+///       singleton status of the object.
+class Daemon : public boost::noncopyable {
+
+public:
+    /// @brief Default constructor
+    ///
+    /// Currently it does nothing.
+    Daemon();
+    
+    /// @brief Initializes the server.
+    ///
+    /// Depending on the configuration backend, it establishes msgq session,
+    /// or reads the 
+    /// Creates session that will be used to receive commands and updated
+    /// configuration from cfgmgr (or indirectly from user via bindctl).
+    ///
+    /// Note: This function may throw to report enountered problems. It may
+    /// also return false if the initialization was skipped. That may seem
+    /// redundant, but the idea here is that in some cases the configuration
+    /// was read, understood and the decision was made to not start. One
+    /// case where such capability could be needed is when we have a single
+    /// config file for Kea4 and D2, but the DNS Update is disabled. It is
+    /// likely that the D2 will be started, it will analyze its config file,
+    /// decide that it is not needed and will shut down.
+    ///
+    /// @note this method may throw
+    virtual void init(const std::string& config_file);
+
+    /// @brief Performs final deconfiguration.
+    ///
+    /// Performs configuration backend specific final clean-up. This is called
+    /// shortly before the daemon terminates. Depending on backend, it may
+    /// terminat existing msgq session, close LDAP connection or similar.
+    ///
+    /// The daemon is not expected to receive any further commands or
+    /// configuration updates as it is in final stages of shutdown.
+    virtual void cleanup();
+
+    /// @brief Initiates shutdown procedure for the whole DHCPv6 server.
+    virtual void shutdown();
+
+    /// @brief Desctructor
+    ///
+    /// Having virtual destructor ensures that all derived classes will have
+    /// virtual destructor as well.
+    virtual ~Daemon();
+
+    /// Initializez logger
+    ///
+    /// This method initializes logger. I
+    static void loggerInit(const char* log_name, bool verbose, bool stand_alone);
+};
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

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

@@ -60,6 +60,7 @@ libdhcpsrv_unittests_SOURCES += csv_lease_file4_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc
+libdhcpsrv_unittests_SOURCES += daemon_unittest.cc
 libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_file_io.cc lease_file_io.h
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc

+ 32 - 0
src/lib/dhcpsrv/tests/daemon_unittest.cc

@@ -0,0 +1,32 @@
+// 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 <exceptions/exceptions.h>
+#include <dhcpsrv/daemon.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+// Very simple test. Checks whether Daemon can be instantiated.
+TEST(DaemonTest, noop) {
+    EXPECT_NO_THROW(Daemon x);
+}
+
+// More tests will appear here as we develop Daemon class.
+
+};