Parcourir la source

[3400] Initial JSON file read support added in b10-dhcp6

- added --with-kea-config=JSON,BIND10 added to configure.ac
- JSON-file config backend added for b10-dhcp6
- Config file can now be read from a JSON file
Tomek Mrugalski il y a 11 ans
Parent
commit
c8899cc4eb

+ 33 - 0
configure.ac

@@ -565,6 +565,9 @@ AC_SUBST(PYCOVERAGE)
 AC_SUBST(PYCOVERAGE_RUN)
 AC_SUBST(PYCOVERAGE_RUN)
 AC_SUBST(USE_PYCOVERAGE)
 AC_SUBST(USE_PYCOVERAGE)
 
 
+
+
+
 enable_gtest="no"
 enable_gtest="no"
 GTEST_INCLUDES=
 GTEST_INCLUDES=
 
 
@@ -1282,6 +1285,32 @@ AC_SUBST(PERL)
 AC_PATH_PROGS(AWK, gawk awk)
 AC_PATH_PROGS(AWK, gawk awk)
 AC_SUBST(AWK)
 AC_SUBST(AWK)
 
 
+
+# Kea configuration backend section
+# Currently there are 2 backends available: BIND10 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: BIND10 (default) or JSON]),
+    [CONFIG_BACKEND="$withval"],
+    [CONFIG_BACKEND=BIND10])
+
+AM_CONDITIONAL(CONFIG_BACKEND_BIND10, test "x$CONFIG_BACKEND" = "xBIND10")
+AM_CONDITIONAL(CONFIG_BACKEND_JSON,   test "x$CONFIG_BACKEND" = "xJSON")
+
+if test "x$CONFIG_BACKEND" = "xBIND10"; then
+    AC_DEFINE(CONFIG_BACKEND_BIND10, 1, [Define to 1 if Kea config was set to BIND10])
+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" != "xBIND10" && test "x$CONFIG_BACKEND" != "xJSON"; then
+   AC_MSG_ERROR("Invalid configuration backend specified: $CONFIG_BACKEND. The only supported are: BIND10 JSON")
+fi
+
 AC_ARG_ENABLE(generate_docs, [AC_HELP_STRING([--enable-generate-docs],
 AC_ARG_ENABLE(generate_docs, [AC_HELP_STRING([--enable-generate-docs],
   [regenerate documentation using Docbook [default=no]])],
   [regenerate documentation using Docbook [default=no]])],
   enable_generate_docs=$enableval, enable_generate_docs=no)
   enable_generate_docs=$enableval, enable_generate_docs=no)
@@ -1619,6 +1648,10 @@ SQLite:
   SQLITE_VERSION:  ${SQLITE_VERSION}
   SQLITE_VERSION:  ${SQLITE_VERSION}
   SQLITE_CFLAGS:   ${SQLITE_CFLAGS}
   SQLITE_CFLAGS:   ${SQLITE_CFLAGS}
   SQLITE_LIBS:     ${SQLITE_LIBS}
   SQLITE_LIBS:     ${SQLITE_LIBS}
+
+Kea config backend:
+  CONFIG_BACKEND: ${CONFIG_BACKEND}
+
 END
 END
 
 
 # Avoid confusion on DNS/DHCP and only mention MySQL if it
 # Avoid confusion on DNS/DHCP and only mention MySQL if it

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

@@ -52,10 +52,17 @@ BUILT_SOURCES = spec_config.h dhcp6_messages.h dhcp6_messages.cc
 pkglibexec_PROGRAMS = b10-dhcp6
 pkglibexec_PROGRAMS = b10-dhcp6
 
 
 b10_dhcp6_SOURCES  = main.cc
 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_log.cc dhcp6_log.h
 b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
 b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
+b10_dhcp6_SOURCES += ctrl_dhcp6_srv.h
+
+if CONFIG_BACKEND_BIND10
+b10_dhcp6_SOURCES += json_config_parser.cc json_config_parser.h ctrl_bind10_dhcp6_srv.cc
+endif
+
+if CONFIG_BACKEND_JSON
+b10_dhcp6_SOURCES += json_config_parser.cc json_config_parser.h ctrl_json_dhcp6_srv.cc
+endif
 
 
 nodist_b10_dhcp6_SOURCES = dhcp6_messages.h dhcp6_messages.cc
 nodist_b10_dhcp6_SOURCES = dhcp6_messages.h dhcp6_messages.cc
 EXTRA_DIST += dhcp6_messages.mes
 EXTRA_DIST += dhcp6_messages.mes

+ 20 - 4
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -21,7 +21,7 @@
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
-#include <dhcp6/config_parser.h>
+#include <dhcp6/json_config_parser.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/spec_config.h>
 #include <dhcp6/spec_config.h>
@@ -186,7 +186,14 @@ void ControlledDhcpv6Srv::sessionReader(void) {
     }
     }
 }
 }
 
 
-void ControlledDhcpv6Srv::establishSession() {
+
+bool
+ControlledDhcpv6Srv::init(const std::string& /* config_file*/) {
+    // This is BIND10 configuration backed. It established control session
+    // that is used to connect to BIND10 framework.
+    //
+    // Creates session that will be used to receive commands and updated
+    // configuration from cfgmgr (or indirectly from user via bindctl).
 
 
     string specfile;
     string specfile;
     if (getenv("B10_FROM_BUILD")) {
     if (getenv("B10_FROM_BUILD")) {
@@ -242,9 +249,11 @@ void ControlledDhcpv6Srv::establishSession() {
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTED)
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTED)
               .arg(ctrl_socket);
               .arg(ctrl_socket);
     IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
     IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
+
+    return (true);
 }
 }
 
 
-void ControlledDhcpv6Srv::disconnectSession() {
+void ControlledDhcpv6Srv::cleanup() {
     if (config_session_) {
     if (config_session_) {
         delete config_session_;
         delete config_session_;
         config_session_ = NULL;
         config_session_ = NULL;
@@ -273,7 +282,7 @@ void ControlledDhcpv6Srv::shutdown() {
 }
 }
 
 
 ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
 ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
-    disconnectSession();
+    cleanup();
 
 
     server_ = NULL; // forget this instance. There should be no callback anymore
     server_ = NULL; // forget this instance. There should be no callback anymore
                     // at this stage anyway.
                     // at this stage anyway.
@@ -290,5 +299,12 @@ ControlledDhcpv6Srv::execDhcpv6ServerCommand(const std::string& command_id,
     }
     }
 }
 }
 
 
+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);
+}
+
 };
 };
 };
 };

+ 9 - 5
src/bin/dhcp6/ctrl_dhcp6_srv.h

@@ -46,11 +46,15 @@ public:
     /// @brief Destructor.
     /// @brief Destructor.
     ~ControlledDhcpv6Srv();
     ~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
+    /// ctrl_*_dhcp6_srv.cc
+    ///
+    /// @return true if initialization was successful, false if it failed
+    bool init(const std::string& config_file);
 
 
     /// @brief Terminates existing msgq session.
     /// @brief Terminates existing msgq session.
     ///
     ///
@@ -59,7 +63,7 @@ public:
     /// may be received.
     /// may be received.
     ///
     ///
     /// It is ok to call this method when session is disconnected already.
     /// It is ok to call this method when session is disconnected already.
-    void disconnectSession();
+    void cleanup();
 
 
     /// @brief Initiates shutdown procedure for the whole DHCPv6 server.
     /// @brief Initiates shutdown procedure for the whole DHCPv6 server.
     void shutdown();
     void shutdown();

+ 137 - 0
src/bin/dhcp6/ctrl_json_dhcp6_srv.cc

@@ -0,0 +1,137 @@
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <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 <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 {
+
+// Need to provide dummy reader. JSON-file backend does not use any control
+// readers for now. Eventually, we may consider having a socket (named socket?)
+// that other processes (like IPAM) could write to, triggering specific actions.
+// For now, it's a no-op method.
+void ControlledDhcpv6Srv::sessionReader(void) {
+}
+
+bool
+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 result;
+
+    // Basic sanity check: file name must not be empty.
+    if (file_name.empty()) {
+        isc_throw(BadValue, "JSON configuration file not specified");
+    }
+
+    try {
+        // Read contents of the file
+        string config = readFile(file_name);
+
+        // Try to parse it as JSON
+        json = Element::fromJSON(config);
+
+        // Use parsed JSON structures to configure the server
+        result = configureDhcp6Server(*this, json);
+
+    }  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 (false);
+    }
+
+    // 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);
+        return (false);
+    }
+
+    // Configuration may disable or enable interfaces so we have to
+    // reopen sockets according to new configuration.
+    openActiveSockets(getPort());
+
+    // Server will start DDNS communications if its enabled.
+    this->startD2();
+
+    return (true);
+}
+
+void ControlledDhcpv6Srv::cleanup() {
+    // Nothing to do here. No need to disconnect from anything.
+}
+
+ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
+    : Dhcpv6Srv(port), cc_session_(NULL), config_session_(NULL) {
+}
+
+void ControlledDhcpv6Srv::shutdown() {
+
+    // Stop ASIO transmissions. Even though we didn't use it for
+    // configuration reading, there may be on-going transmissions
+    // with D2.
+    io_service_.stop();
+
+    Dhcpv6Srv::shutdown(); // Initiate DHCPv6 shutdown procedure.
+}
+
+ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
+    cleanup();
+}
+
+};
+};

+ 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 IPv6 DHCP server has encountered a fatal error and is terminating.
 The reason for the failure is included in the message.
 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
 % DHCP6_SHUTDOWN server shutdown
 The IPv6 DHCP server has terminated normally.
 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/d2_client_mgr.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <hooks/callout_handle.h>
 #include <hooks/callout_handle.h>
-
-#include <boost/noncopyable.hpp>
+#include <dhcpsrv/daemon.h>
 
 
 #include <iostream>
 #include <iostream>
 #include <queue>
 #include <queue>
@@ -52,11 +51,7 @@ public:
 /// that is going to be used as server-identifier, receives incoming
 /// that is going to be used as server-identifier, receives incoming
 /// packets, processes them, manages leases assignment and generates
 /// packets, processes them, manages leases assignment and generates
 /// appropriate responses.
 /// 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:
 public:
     /// @brief defines if certain option may, must or must not appear
     /// @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 <cc/data.h>
 #include <config/ccsession.h>
 #include <config/ccsession.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/libdhcp++.h>
-#include <dhcp6/config_parser.h>
+#include <dhcp6/json_config_parser.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>

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


+ 43 - 19
src/bin/dhcp6/main.cc

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

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

@@ -75,18 +75,26 @@ dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += fqdn_unittest.cc
 dhcp6_unittests_SOURCES += fqdn_unittest.cc
 dhcp6_unittests_SOURCES += hooks_unittest.cc
 dhcp6_unittests_SOURCES += hooks_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6_test_utils.cc dhcp6_test_utils.h
 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 += d2_unittest.cc d2_unittest.h
 dhcp6_unittests_SOURCES += marker_file.cc
 dhcp6_unittests_SOURCES += marker_file.cc
 dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.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 += wireshark.cc
 dhcp6_unittests_SOURCES += wireshark.cc
 dhcp6_unittests_SOURCES += dhcp6_client.cc dhcp6_client.h
 dhcp6_unittests_SOURCES += dhcp6_client.cc dhcp6_client.h
 dhcp6_unittests_SOURCES += rebind_unittest.cc
 dhcp6_unittests_SOURCES += rebind_unittest.cc
 
 
+if CONFIG_BACKEND_BIND10
+dhcp6_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h ../ctrl_bind10_dhcp6_srv.cc
+dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
+dhcp6_unittests_SOURCES += config_parser_unittest.cc
+endif
+
+if CONFIG_BACKEND_JSON
+dhcp6_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h ../ctrl_json_dhcp6_srv.cc
+dhcp6_unittests_SOURCES += config_parser_unittest.cc
+endif
+
+
 nodist_dhcp6_unittests_SOURCES  = ../dhcp6_messages.h ../dhcp6_messages.cc
 nodist_dhcp6_unittests_SOURCES  = ../dhcp6_messages.h ../dhcp6_messages.cc
 nodist_dhcp6_unittests_SOURCES += marker_file.h test_libraries.h
 nodist_dhcp6_unittests_SOURCES += marker_file.h test_libraries.h
 
 

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

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

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

@@ -13,7 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
-#include <dhcp6/config_parser.h>
+#include <dhcp6/json_config_parser.h>
 #include <dhcp6/tests/d2_unittest.h>
 #include <dhcp6/tests/d2_unittest.h>
 #include <dhcpsrv/cfgmgr.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_string.h>
 #include <dhcp/option_vendor.h>
 #include <dhcp/option_vendor.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
-#include <dhcp6/config_parser.h>
+#include <dhcp6/json_config_parser.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
@@ -197,12 +197,12 @@ TEST_F(Dhcpv6SrvTest, basic) {
 
 
     ASSERT_NO_THROW( {
     ASSERT_NO_THROW( {
         // Skip opening any sockets
         // Skip opening any sockets
-        srv.reset(new Dhcpv6Srv(0));
+        srv.reset(new NakedDhcpv6Srv(0));
     });
     });
     srv.reset();
     srv.reset();
     ASSERT_NO_THROW({
     ASSERT_NO_THROW({
         // open an unpriviledged port
         // 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 <gtest/gtest.h>
 #include <dhcp6/tests/dhcp6_test_utils.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::data;
 using namespace isc::dhcp;
 using namespace isc::dhcp;

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

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

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

@@ -16,7 +16,7 @@
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <cc/data.h>
 #include <dhcp/tests/iface_mgr_test_config.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_test_utils.h>
 #include <dhcp6/tests/dhcp6_client.h>
 #include <dhcp6/tests/dhcp6_client.h>
 
 

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

@@ -3,6 +3,7 @@ SUBDIRS = . tests
 dhcp_data_dir = @localstatedir@/@PACKAGE@
 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 = -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)
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 if HAVE_MYSQL
 if HAVE_MYSQL
 AM_CPPFLAGS += $(MYSQL_CPPFLAGS)
 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 += 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_cfg.cc d2_client_cfg.h
 libkea_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.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 += dbaccess_parser.cc dbaccess_parser.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libkea_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.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.cc lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.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
 libkea_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
+
 if HAVE_MYSQL
 if HAVE_MYSQL
 libkea_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
 endif
 endif

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

@@ -0,0 +1,103 @@
+// 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 <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 <dhcpsrv/daemon.h>
+#include <exceptions/exceptions.h>
+#include <fstream>
+#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() {
+}
+
+bool Daemon::init(const std::string&) {
+    return false;
+}
+
+void Daemon::cleanup() {
+
+}
+
+void Daemon::shutdown() {
+
+}
+
+Daemon::~Daemon() {
+}
+
+std::string Daemon::readFile(const std::string& file_name) {
+
+    // This is the fastest method to read a file according to:
+    // http://insanecoding.blogspot.com/2011/11/how-to-read-in-file-in-c.html
+
+    std::ifstream infile(file_name.c_str(), std::ios::in | std::ios::binary);
+    if (infile.is_open())
+    {
+        std::string contents;
+        infile.seekg(0, std::ios::end);
+        contents.resize(infile.tellg());
+        infile.seekg(0, std::ios::beg);
+        infile.read(&contents[0], contents.size());
+        infile.close();
+        return(contents);
+    }
+    isc_throw(InvalidOperation, "Failed to read file " << file_name << ",error:"
+              << errno);
+}
+
+void Daemon::loggerInit(const char* log_name, bool verbose, bool ) {
+    // This method configures logger. For now it is very simple.
+    // We'll make it more robust once we add support for JSON-based logging
+    // configuration.
+
+    using namespace isc::log;
+
+    Severity severity = b10LoggerSeverity(verbose ? isc::log::DEBUG : isc::log::INFO);
+
+    // Set a directory for creating lockfiles when running tests
+    // @todo: Find out why this is needed. Without this, the logger doesn't
+    // work.
+    setenv("B10_LOCKFILE_DIR_FROM_BUILD", TOP_BUILDDIR, 1);
+    
+    // Initialize logging
+    initLogger(log_name, severity, isc::log::MAX_DEBUG_LEVEL, NULL);
+
+    // Now configure logger output to stdout.
+    /// @todo: Make this configurable as part of #3427.
+    LoggerSpecification spec(log_name, severity,
+                             b10LoggerDbglevel(isc::log::MAX_DEBUG_LEVEL));
+    OutputOption option;
+    option.destination = OutputOption::DEST_CONSOLE;
+    option.stream = OutputOption::STR_STDOUT;
+
+    spec.addOutputOption(option);
+    LoggerManager manager;
+    manager.process(spec);
+}
+
+};
+};

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

@@ -0,0 +1,94 @@
+// 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).
+    /// @return true if initialization was successful, false if it failed
+    virtual bool 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);
+
+    protected:
+
+    /// @brief reads file and return its content as a string
+    ///
+    /// This is an utility method that is expected to be used by several daemons.
+    /// It reads contents of a text file and return it as a string.
+    ///
+    /// @param file_name name of the file to read
+    /// @return content of the file
+    std::string readFile(const std::string& file_name);
+};
+
+}; // 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 += csv_lease_file6_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_udp_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 += dbaccess_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_file_io.cc lease_file_io.h
 libdhcpsrv_unittests_SOURCES += lease_file_io.cc lease_file_io.h
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc

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

@@ -0,0 +1,65 @@
+// 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 <fstream>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+/// Temporary file name used in some tests
+const char* TEMP_FILE="temp-file.json";
+
+namespace {
+
+/// @brief Test-friendly version of the daemon
+///
+/// This class exposes internal Daemon class methods.
+class NakedDaemon: public Daemon {
+public:
+
+    using Daemon::readFile;
+};
+
+TEST(Daemon, readFile) {
+    NakedDaemon x;
+
+    const char* content = "Horse doesn't eat cucumber salad";
+
+    // 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();
+
+    // Check that the read content is correct
+    EXPECT_EQ(std::string(content), x.readFile(TEMP_FILE));
+
+    unlink(TEMP_FILE);
+}
+
+TEST(Daemon, readFileError) {
+    NakedDaemon x;
+
+    // Check that the read content is correct
+    EXPECT_THROW(x.readFile("no-such-file.txt"), isc::InvalidOperation);
+
+    unlink(TEMP_FILE);
+}
+
+};