Browse Source

[3400] Changes after review:

- config-reload command added
- config and command handlers cleaned up
- files renamed
- Majority of the backend code is now common
- Added unit-test for config-reload command
Tomek Mrugalski 11 years ago
parent
commit
b4d53d935f

+ 4 - 3
src/bin/dhcp6/Makefile.am

@@ -54,14 +54,15 @@ pkglibexec_PROGRAMS = b10-dhcp6
 b10_dhcp6_SOURCES  = main.cc
 b10_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h
 b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
-b10_dhcp6_SOURCES += ctrl_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_BIND10
-b10_dhcp6_SOURCES += json_config_parser.cc json_config_parser.h ctrl_bind10_dhcp6_srv.cc
+b10_dhcp6_SOURCES += bundy_backend.cc
 endif
 
 if CONFIG_BACKEND_JSON
-b10_dhcp6_SOURCES += json_config_parser.cc json_config_parser.h ctrl_json_dhcp6_srv.cc
+b10_dhcp6_SOURCES += jsonfile_backend.cc
 endif
 
 nodist_b10_dhcp6_SOURCES = dhcp6_messages.h dhcp6_messages.cc

+ 38 - 121
src/bin/dhcp6/ctrl_bind10_dhcp6_srv.cc

@@ -47,10 +47,33 @@ using namespace std;
 namespace isc {
 namespace dhcp {
 
-ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
-
+/// @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
-ControlledDhcpv6Srv::dhcp6StubConfigHandler(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
@@ -65,9 +88,9 @@ ControlledDhcpv6Srv::dhcp6StubConfigHandler(ConstElementPtr) {
 }
 
 ConstElementPtr
-ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
+bundyConfigHandler(ConstElementPtr new_config) {
 
-    if (!server_ || !server_->config_session_) {
+    if (!ControlledDhcpv6Srv::getInstance() || !config_session_) {
         // That should never happen as we install config_handler
         // after we instantiate the server.
         ConstElementPtr answer =
@@ -91,7 +114,7 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
     // 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();
+    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).
@@ -105,92 +128,14 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
     }
 
     // Configure the server.
-    ConstElementPtr answer = configureDhcp6Server(*server_, merged_config);
-
-    // Check that configuration was successful. If not, do not reopen sockets.
-    int rcode = 0;
-    parseAnswer(rcode, answer);
-    if (rcode != 0) {
-        return (answer);
-    }
-
-    // Server will start DDNS communications if its enabled.
-    try {
-        server_->startD2();
-    } catch (const std::exception& ex) {
-        std::ostringstream err;
-        err << "error starting DHCP_DDNS client "
-                " after server reconfiguration: " << ex.what();
-        return (isc::config::createAnswer(1, err.str()));
-    }
 
-    // Configuration may change active interfaces. Therefore, we have to reopen
-    // sockets according to new configuration. This operation is not exception
-    // safe and we really don't want to emit exceptions to the callback caller.
-    // Instead, catch an exception and create appropriate answer.
-    try {
-        server_->openActiveSockets(server_->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);
+    return (ControlledDhcpv6Srv::processConfig(merged_config));
 }
 
-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.
-    if (server_) {
-        server_->io_service_.run_one();
-    }
-}
-
-
 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.
+    // 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).
@@ -217,18 +162,18 @@ ControlledDhcpv6Srv::init(const std::string& /* config_file*/) {
     // been lost.
     config_session_ = new ModuleCCSession(specfile, *cc_session_,
                                           dhcp6StubConfigHandler,
-                                          dhcp6CommandHandler, false);
+                                          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(dhcp6ConfigHandler);
+    config_session_->setConfigHandler(bundyConfigHandler);
 
     try {
         // Pull the full configuration out from the session.
-        configureDhcp6Server(*this, config_session_->getFullConfig());
+        processConfig(config_session_->getFullConfig());
 
         // Server will start DDNS communications if its enabled.
         server_->startD2();
@@ -242,7 +187,7 @@ ControlledDhcpv6Srv::init(const std::string& /* config_file*/) {
 
     }
 
-    /// Integrate the asynchronous I/O model of BIND 10 configuration
+    /// 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();
@@ -271,34 +216,6 @@ void ControlledDhcpv6Srv::cleanup() {
     }
 }
 
-ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
-    : Dhcpv6Srv(port), cc_session_(NULL), config_session_(NULL) {
-    server_ = this; // remember this instance for use in callback
-}
-
-void ControlledDhcpv6Srv::shutdown() {
-    io_service_.stop(); // Stop ASIO transmissions
-    Dhcpv6Srv::shutdown(); // Initiate DHCPv6 shutdown procedure.
-}
-
-ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
-    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
 Daemon::loggerInit(const char* log_name, bool verbose, bool stand_alone) {
     isc::log::initLogger(log_name,
@@ -306,5 +223,5 @@ Daemon::loggerInit(const char* log_name, bool verbose, bool stand_alone) {
                          isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
 }
 
-};
-};
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 183 - 0
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -0,0 +1,183 @@
+// 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 <cc/data.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/dhcp6_log.h>
+#include <hooks/hooks_manager.h>
+#include <dhcp6/json_config_parser.h>
+
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
+
+ConstElementPtr
+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::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));
+}
+
+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());
+
+    ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
+
+    if (!srv) {
+        ConstElementPtr no_srv = isc::config::createAnswer(1,
+          "Server object not initialized, can't process command '" +
+          command + "'.");
+        return (no_srv);
+    }
+
+    try {
+        if (command == "shutdown") {
+            return (srv->commandShutdownHandler(command, args));
+
+        } 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 {
+        srv->startD2();
+    } catch (const std::exception& ex) {
+        std::ostringstream err;
+        err << "error starting DHCP_DDNS client "
+                " after server reconfiguration: " << ex.what();
+        return (isc::config::createAnswer(1, err.str()));
+    }
+
+    // Configuration may change active interfaces. Therefore, we have to reopen
+    // sockets according to new configuration. This operation is not exception
+    // safe and we really don't want to emit exceptions to the callback caller.
+    // Instead, catch an exception and create appropriate answer.
+    try {
+        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);
+}
+
+ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
+    : Dhcpv6Srv(port) {
+    if (server_) {
+        isc_throw(InvalidOperation,
+                  "There is another Dhcpv6Srv instance already.");
+    }
+    server_ = this; // remember this instance for use in callback
+}
+
+void ControlledDhcpv6Srv::shutdown() {
+    io_service_.stop(); // Stop ASIO transmissions
+    Dhcpv6Srv::shutdown(); // Initiate DHCPv6 shutdown procedure.
+}
+
+ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
+    cleanup();
+
+    server_ = NULL; // forget this instance. There should be no callback anymore
+                    // at this stage anyway.
+}
+
+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

+ 74 - 51
src/bin/dhcp6/ctrl_dhcp6_srv.h

@@ -44,48 +44,53 @@ public:
     ControlledDhcpv6Srv(uint16_t port = DHCP6_SERVER_PORT);
 
     /// @brief Destructor.
-    ~ControlledDhcpv6Srv();
+    virtual ~ControlledDhcpv6Srv();
 
     /// @brief Initializes the server.
     ///
     /// 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
+    /// *_backend.cc
     ///
     /// @return true if initialization was successful, false if it failed
     bool 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.
+    /// 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.
@@ -94,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

+ 4 - 32
src/bin/dhcp6/ctrl_json_dhcp6_srv.cc

@@ -42,13 +42,6 @@ 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
@@ -70,7 +63,7 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
         json = Element::fromJSON(config);
 
         // Use parsed JSON structures to configure the server
-        result = configureDhcp6Server(*this, json);
+        result = processCommand("config-reload", json);
 
     }  catch (const std::exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
@@ -101,12 +94,9 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
         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();
+    // We don't need to call openActiveSockets() or startD2() as these
+    // methods are called in processConfig() which is called by
+    // processCommand("reload-config", ...)
 
     return (true);
 }
@@ -115,23 +105,5 @@ 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();
-}
-
 };
 };

+ 10 - 7
src/bin/dhcp6/tests/Makefile.am

@@ -79,23 +79,26 @@ 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.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_BIND10
-dhcp6_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h ../ctrl_bind10_dhcp6_srv.cc
-dhcp6_unittests_SOURCES += ctrl_bind10_dhcp6_srv_unittest.cc
-dhcp6_unittests_SOURCES += config_parser_unittest.cc
+# For Bundy backend, we only need to run the usual tests. There are no
+# Bundy-specific tests.
+dhcp6_unittests_SOURCES += ../bundy_backend.cc
+dhcp6_unittests_SOURCES += bundy_backend_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 += ctrl_json_dhcp6_srv_unittest.cc
-dhcp6_unittests_SOURCES += config_parser_unittest.cc
+dhcp6_unittests_SOURCES += ../jsonfile_backend.cc
+dhcp6_unittests_SOURCES += jsonfile_backend_unittest.cc
 endif
 
-
 nodist_dhcp6_unittests_SOURCES  = ../dhcp6_messages.h ../dhcp6_messages.cc
 nodist_dhcp6_unittests_SOURCES += marker_file.h test_libraries.h
 

+ 71 - 20
src/bin/dhcp6/tests/ctrl_bind10_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

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

@@ -46,12 +46,12 @@ public:
 };
 
 
-class ControlledJSONDhcpv6SrvTest : public ::testing::Test {
+class JSONFileBackendTest : public ::testing::Test {
 public:
-    ControlledJSONDhcpv6SrvTest() {
+    JSONFileBackendTest() {
     }
 
-    ~ControlledJSONDhcpv6SrvTest() {
+    ~JSONFileBackendTest() {
     };
 
     void writeFile(const std::string& file_name, const std::string& content) {
@@ -66,10 +66,10 @@ public:
     static const char* TEST_FILE;
 };
 
-const char* ControlledJSONDhcpv6SrvTest::TEST_FILE = "test-config.json";
+const char* JSONFileBackendTest::TEST_FILE = "test-config.json";
 
 // This test checks if configuration can be read from a JSON file.
-TEST_F(ControlledJSONDhcpv6SrvTest, jsonFile) {
+TEST_F(JSONFileBackendTest, jsonFile) {
 
     // Prepare configuration file.
     string config = "{ \"interfaces\": [ \"*\" ],"
@@ -141,7 +141,7 @@ TEST_F(ControlledJSONDhcpv6SrvTest, jsonFile) {
 }
 
 // This test checks if configuration can be read from a JSON file.
-TEST_F(ControlledJSONDhcpv6SrvTest, comments) {
+TEST_F(JSONFileBackendTest, comments) {
 
     string config_hash_comments = "# This is a comment. It should be \n"
         "#ignored. Real config starts in line below\n"