Parcourir la source

[master] Merge branch 'trac5213' (config-reload, set-config renamed to config-set)

Tomek Mrugalski il y a 8 ans
Parent
commit
b209c2b577

+ 38 - 10
doc/guide/ctrl-channel.xml

@@ -252,7 +252,7 @@ $ curl -X POST -H "Content-Type: application/json" -d '{ "command": "config-get"
         configuration used by the server. This command does not take any
         parameters. The configuration returned is roughly equal to the
         configuration that was loaded using -c command line option during server
-        start-up or later set using set-config command. However, there may be
+        start-up or later set using config-set command. However, there may be
         certain differences. Comments are not retained. If the original
         configuration used file inclusion, the returned configuration will
         include all parameters from all the included files.</para>
@@ -272,6 +272,35 @@ $ curl -X POST -H "Content-Type: application/json" -d '{ "command": "config-get"
         </para>
       </section> <!-- end of command-config-get -->
 
+      <section id="command-config-reload">
+        <title>config-reload</title>
+
+        <para>The <emphasis>config-reload</emphasis> command instructs
+        Kea to load again the configuration file that was used
+        previously. This operation is useful if the configuration file
+        has been changed by some external sources. For example, a
+        sysadmin can tweak the configuration file and use this command
+        to force Kea pick up the changes.</para>
+
+        <para>Caution should be taken when mixing this with config-set
+        commands. Kea remembers the location of the configuration file
+        it was started with. This configuration can be significantly
+        changed using config-set command. When config-reload is issued
+        after config-set, Kea will attempt to reload its original
+        configuration from the file, possibly losing all changes
+        introduced using config-set or other commands.</para>
+
+        <para><emphasis>config-reload</emphasis> does not take any parameters.
+        An example command invocation looks like this:
+<screen>
+{
+    "command": "config-reload"
+}
+</screen>
+        </para>
+      </section> <!-- end of command-config-reload -->
+
+
       <section id="command-config-test">
         <title>config-test</title>
 
@@ -282,7 +311,7 @@ $ curl -X POST -H "Content-Type: application/json" -d '{ "command": "config-get"
      configuration for the target server along with an optional Logger
      configuration. As for the <command>-t</command> command some sanity checks
      are not performed so it is possible a configuration which successfully
-     passes this command will still fail in <command>set-config</command>
+     passes this command will still fail in <command>config-set</command>
      command or at launch time.
      The structure of the command is as follows:
       </para>
@@ -422,11 +451,11 @@ $ curl -X POST -H "Content-Type: application/json" -d '{ "command": "config-get"
       </para>
     </section> <!-- end of command-list-commands -->
 
-    <section id="command-set-config">
-      <title>set-config</title>
+    <section id="command-config-set">
+      <title>config-set</title>
 
       <para>
-    The <emphasis>set-config</emphasis> command instructs the server to replace
+    The <emphasis>config-set</emphasis> command instructs the server to replace
     its current configuration with the new configuration supplied in the
     command's arguments. The supplied configuration is expected to be the full
     configuration for the target server along with an optional Logger
@@ -436,7 +465,7 @@ $ curl -X POST -H "Content-Type: application/json" -d '{ "command": "config-get"
       </para>
 <screen>
 {
-    "command": "set-config",
+    "command": "config-set",
     "arguments":  {
         "&#60;server&#62;": {
         },
@@ -451,7 +480,7 @@ $ curl -X POST -H "Content-Type: application/json" -d '{ "command": "config-get"
       </para>
 <screen>
 {
-    "command": "set-config",
+    "command": "config-set",
     "arguments":  {
         "Dhcp6": {
             :
@@ -479,7 +508,7 @@ $ curl -X POST -H "Content-Type: application/json" -d '{ "command": "config-get"
     {"result": 1, "text": "unsupported parameter: BOGUS (&#60;string&#62;:16:26)" }
 </screen>
       </para>
-    </section> <!-- end of command-set-config -->
+    </section> <!-- end of command-config-set -->
 
     <section id="command-shutdown">
       <title>shutdown</title>
@@ -491,8 +520,7 @@ $ curl -X POST -H "Content-Type: application/json" -d '{ "command": "config-get"
         command may look like this:
 <screen>
 {
-    "command": "shutdown",
-    "arguments": { }
+    "command": "shutdown"
 }
 </screen>
       </para>

+ 2 - 1
doc/guide/dhcp4-srv.xml

@@ -3721,11 +3721,12 @@ src/lib/dhcpsrv/cfg_host_operations.cc -->
         <itemizedlist>
             <listitem>build-report</listitem>
             <listitem>config-get</listitem>
+            <listitem>config-reload</listitem>
+            <listitem>config-set</listitem>
             <listitem>config-test</listitem>
             <listitem>config-write</listitem>
             <listitem>leases-reclaim</listitem>
             <listitem>list-commands</listitem>
-            <listitem>set-config</listitem>
             <listitem>shutdown</listitem>
             <listitem>version-get</listitem>
         </itemizedlist>

+ 2 - 1
doc/guide/dhcp6-srv.xml

@@ -3961,11 +3961,12 @@ If not specified, the default value is:
         <itemizedlist>
             <listitem>build-report</listitem>
             <listitem>config-get</listitem>
+            <listitem>config-reload</listitem>
+            <listitem>config-set</listitem>
             <listitem>config-test</listitem>
             <listitem>config-write</listitem>
             <listitem>leases-reclaim</listitem>
             <listitem>list-commands</listitem>
-            <listitem>set-config</listitem>
             <listitem>shutdown</listitem>
             <listitem>version-get</listitem>
         </itemizedlist>

+ 1 - 1
src/bin/agent/agent_hooks.dox

@@ -46,7 +46,7 @@ command.
    will only return the list of commands supported by the hook library.
    The callout can modify the command arguments to influence the command
    processing by the Command Manager. For example, it may freely modify
-   the configuration received in 'set-config' before it is processed by
+   the configuration received in 'config-set' before it is processed by
    the server. The SKIP action is not set in this case.
 
  - <b>Next step status</b>: if any callout sets the next step action to SKIP,

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

@@ -66,7 +66,6 @@ libdhcp4_la_SOURCES += dhcp4to6_ipc.cc dhcp4to6_ipc.h
 libdhcp4_la_SOURCES += dhcp4_lexer.ll location.hh position.hh stack.hh
 libdhcp4_la_SOURCES += dhcp4_parser.cc dhcp4_parser.h
 libdhcp4_la_SOURCES += parser_context.cc parser_context.h parser_context_decl.h
-libdhcp4_la_SOURCES += kea_controller.cc
 libdhcp4_la_SOURCES += simple_parser4.cc simple_parser4.h
 
 nodist_libdhcp4_la_SOURCES = dhcp4_messages.h dhcp4_messages.cc

+ 168 - 16
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -7,28 +7,163 @@
 #include <config.h>
 #include <cc/data.h>
 #include <cc/command_interpreter.h>
+#include <config/command_mgr.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/dhcp4to6_ipc.h>
-#include <hooks/hooks_manager.h>
+#include <dhcp4/parser_context.h>
 #include <dhcp4/json_config_parser.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfg_db_access.h>
-#include <config/command_mgr.h>
+#include <hooks/hooks_manager.h>
 #include <stats/stats_mgr.h>
 #include <cfgrpt/config_report.h>
+#include <signal.h>
 
 using namespace isc::data;
+using namespace isc::dhcp;
 using namespace isc::hooks;
 using namespace isc::config;
 using namespace isc::stats;
 using namespace std;
 
+namespace {
+
+/// @brief Signals handler for DHCPv4 server.
+///
+/// This signal handler handles the following signals received by the DHCPv4
+/// server process:
+/// - SIGHUP - triggers server's dynamic reconfiguration.
+/// - SIGTERM - triggers server's shut down.
+/// - SIGINT - triggers server's shut down.
+///
+/// @param signo Signal number received.
+void signalHandler(int signo) {
+    // SIGHUP signals a request to reconfigure the server.
+    if (signo == SIGHUP) {
+        ControlledDhcpv4Srv::processCommand("config-reload",
+                                            ConstElementPtr());
+    } else if ((signo == SIGTERM) || (signo == SIGINT)) {
+        ControlledDhcpv4Srv::processCommand("shutdown",
+                                            ConstElementPtr());
+    }
+}
+
+}
+
 namespace isc {
 namespace dhcp {
 
 ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
 
+void
+ControlledDhcpv4Srv::init(const std::string& file_name) {
+    // Configure the server using JSON file.
+    ConstElementPtr result = loadConfigFile(file_name);
+    int rcode;
+    ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
+    if (rcode != 0) {
+        string reason = comment ? comment->stringValue() :
+            "no details available";
+        isc_throw(isc::BadValue, reason);
+    }
+
+    // We don't need to call openActiveSockets() or startD2() as these
+    // methods are called in processConfig() which is called by
+    // processCommand("config-set", ...)
+
+    // Set signal handlers. When the SIGHUP is received by the process
+    // the server reconfiguration will be triggered. When SIGTERM or
+    // SIGINT will be received, the server will start shutting down.
+    signal_set_.reset(new isc::util::SignalSet(SIGINT, SIGHUP, SIGTERM));
+    // Set the pointer to the handler function.
+    signal_handler_ = signalHandler;
+}
+
+void ControlledDhcpv4Srv::cleanup() {
+    // Nothing to do here. No need to disconnect from anything.
+}
+
+/// @brief Configure DHCPv4 server using the configuration file specified.
+///
+/// This function is used to both configure the DHCP server on its startup
+/// and dynamically reconfigure the server when SIGHUP signal is received.
+///
+/// It fetches DHCPv4 server's configuration from the 'Dhcp4' section of
+/// the JSON configuration file.
+///
+/// @param file_name Configuration file location.
+/// @return status of the command
+ConstElementPtr
+ControlledDhcpv4Srv::loadConfigFile(const std::string& file_name) {
+    // This is a configuration backend implementation that reads the
+    // configuration from a JSON file.
+
+    isc::data::ConstElementPtr json;
+    isc::data::ConstElementPtr dhcp4;
+    isc::data::ConstElementPtr logger;
+    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(isc::BadValue, "JSON configuration file not specified."
+                      " Please use -c command line option.");
+        }
+
+        // Read contents of the file and parse it as JSON
+        Parser4Context parser;
+        json = parser.parseFile(file_name, Parser4Context::PARSER_DHCP4);
+        if (!json) {
+            isc_throw(isc::BadValue, "no configuration found");
+        }
+
+        // Let's do sanity check before we call json->get() which
+        // works only for map.
+        if (json->getType() != isc::data::Element::map) {
+            isc_throw(isc::BadValue, "Configuration file is expected to be "
+                      "a map, i.e., start with { and end with } and contain "
+                      "at least an entry called 'Dhcp4' that itself is a map. "
+                      << file_name
+                      << " is a valid JSON, but its top element is not a map."
+                      " Did you forget to add { } around your configuration?");
+        }
+
+        // Use parsed JSON structures to configure the server
+        result = ControlledDhcpv4Srv::processCommand("config-set", json);
+        if (!result) {
+            // Undetermined status of the configuration. This should never
+            // happen, but as the configureDhcp4Server returns a pointer, it is
+            // theoretically possible that it will return NULL.
+            isc_throw(isc::BadValue, "undefined result of "
+                      "processCommand(\"config-set\", json)");
+        }
+
+        // Now check is the returned result is successful (rcode=0) or not
+        // (see @ref isc::config::parseAnswer).
+        int rcode;
+        ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
+        if (rcode != 0) {
+            string reason = comment ? comment->stringValue() :
+                "no details available";
+            isc_throw(isc::BadValue, reason);
+        }
+    }  catch (const std::exception& ex) {
+        // If configuration failed at any stage, we drop the staging
+        // configuration and continue to use the previous one.
+        CfgMgr::instance().rollback();
+
+        LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
+            .arg(file_name).arg(ex.what());
+        isc_throw(isc::BadValue, "configuration error using file '"
+                  << file_name << "': " << ex.what());
+    }
+
+    return (result);
+}
+
+
 ConstElementPtr
 ControlledDhcpv4Srv::commandShutdownHandler(const string&, ConstElementPtr) {
     if (ControlledDhcpv4Srv::getInstance()) {
@@ -63,9 +198,22 @@ ControlledDhcpv4Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
 
 ConstElementPtr
 ControlledDhcpv4Srv::commandConfigReloadHandler(const string&,
-                                                ConstElementPtr args) {
-    // Use set-config as it handles logging and server config
-    return (commandSetConfigHandler("set-config", args));
+                                                ConstElementPtr /*args*/) {
+
+    // Get configuration file name.
+    std::string file = ControlledDhcpv4Srv::getInstance()->getConfigFile();
+    try {
+        LOG_INFO(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION).arg(file);
+        return (loadConfigFile(file));
+    } catch (const std::exception& ex) {
+        // Log the unsuccessful reconfiguration. The reason for failure
+        // should be already logged. Don't rethrow an exception so as
+        // the server keeps working.
+        LOG_ERROR(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION_FAIL)
+            .arg(file);
+        return (createAnswer(CONTROL_RESULT_ERROR,
+                             "Config reload failed:" + string(ex.what())));
+    }
 }
 
 ConstElementPtr
@@ -147,7 +295,7 @@ ControlledDhcpv4Srv::commandConfigWriteHandler(const string&,
 }
 
 ConstElementPtr
-ControlledDhcpv4Srv::commandSetConfigHandler(const string&,
+ControlledDhcpv4Srv::commandConfigSetHandler(const string&,
                                              ConstElementPtr args) {
     const int status_code = CONTROL_RESULT_ERROR; // 1 indicates an error
     ConstElementPtr dhcp4;
@@ -293,15 +441,17 @@ ControlledDhcpv4Srv::commandLeasesReclaimHandler(const string&,
 ConstElementPtr
 ControlledDhcpv4Srv::processCommand(const string& command,
                                     ConstElementPtr args) {
+    string txt = args ? args->str() : "(none)";
+
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
-              .arg(command).arg(args->str());
+              .arg(command).arg(txt);
 
     ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
 
     if (!srv) {
         ConstElementPtr no_srv = isc::config::createAnswer(1,
           "Server object not initialized, so can't process command '" +
-          command + "', arguments: '" + args->str() + "'.");
+          command + "', arguments: '" + txt + "'.");
         return (no_srv);
     }
 
@@ -315,8 +465,8 @@ ControlledDhcpv4Srv::processCommand(const string& command,
         } else if (command == "config-reload") {
             return (srv->commandConfigReloadHandler(command, args));
 
-        } else if (command == "set-config") {
-            return (srv->commandSetConfigHandler(command, args));
+        } else if (command == "config-set") {
+            return (srv->commandConfigSetHandler(command, args));
 
         } else if (command == "config-get") {
             return (srv->commandConfigGetHandler(command, args));
@@ -343,7 +493,7 @@ ControlledDhcpv4Srv::processCommand(const string& command,
     } catch (const Exception& ex) {
         return (isc::config::createAnswer(1, "Error while processing command '"
                                           + command + "':" + ex.what() +
-                                          ", params: '" + args->str() + "'"));
+                                          ", params: '" + txt + "'"));
     }
 }
 
@@ -491,7 +641,11 @@ ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)
     CommandMgr::instance().registerCommand("config-get",
         boost::bind(&ControlledDhcpv4Srv::commandConfigGetHandler, this, _1, _2));
 
-    /// @todo: register config-reload (see CtrlDhcpv4Srv::commandConfigReloadHandler)
+    CommandMgr::instance().registerCommand("config-reload",
+        boost::bind(&ControlledDhcpv4Srv::commandConfigReloadHandler, this, _1, _2));
+
+    CommandMgr::instance().registerCommand("config-set",
+        boost::bind(&ControlledDhcpv4Srv::commandConfigSetHandler, this, _1, _2));
 
     CommandMgr::instance().registerCommand("config-test",
         boost::bind(&ControlledDhcpv4Srv::commandConfigTestHandler, this, _1, _2));
@@ -505,9 +659,6 @@ ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)
     CommandMgr::instance().registerCommand("leases-reclaim",
         boost::bind(&ControlledDhcpv4Srv::commandLeasesReclaimHandler, this, _1, _2));
 
-    CommandMgr::instance().registerCommand("set-config",
-        boost::bind(&ControlledDhcpv4Srv::commandSetConfigHandler, this, _1, _2));
-
     CommandMgr::instance().registerCommand("shutdown",
         boost::bind(&ControlledDhcpv4Srv::commandShutdownHandler, this, _1, _2));
 
@@ -555,11 +706,12 @@ ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
         // Deregister any registered commands (please keep in alphabetic order)
         CommandMgr::instance().deregisterCommand("build-report");
         CommandMgr::instance().deregisterCommand("config-get");
+        CommandMgr::instance().deregisterCommand("config-reload");
         CommandMgr::instance().deregisterCommand("config-test");
         CommandMgr::instance().deregisterCommand("config-write");
         CommandMgr::instance().deregisterCommand("leases-reclaim");
         CommandMgr::instance().deregisterCommand("libreload");
-        CommandMgr::instance().deregisterCommand("set-config");
+        CommandMgr::instance().deregisterCommand("config-set");
         CommandMgr::instance().deregisterCommand("shutdown");
         CommandMgr::instance().deregisterCommand("statistic-get");
         CommandMgr::instance().deregisterCommand("statistic-get-all");

+ 19 - 9
src/bin/dhcp4/ctrl_dhcp4_srv.h

@@ -34,15 +34,25 @@ public:
 
     /// @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
-    /// *_backend.cc
+    /// It reads the JSON file from disk or may perform any other setup
+    /// operation. In particular, it also install signal handlers.
     ///
-    /// This method may throw if initialization fails. Exception types may be
-    /// specific to used configuration backend.
+    /// This method may throw if initialization fails.
     void init(const std::string& config_file);
 
+    /// @brief Loads specific config file
+    ///
+    /// This utility method is called whenever we know a filename of the config
+    /// and need to load it. It calls config-set command once the content of
+    /// the file has been loaded and verified to be a sane JSON configuration.
+    /// config-set handler will process the config file (load it as current
+    /// configuration).
+    ///
+    /// @param file_name name of the file to be loaded
+    /// @return status of the file loading and outcome of config-set
+    isc::data::ConstElementPtr
+    loadConfigFile(const std::string& file_name);
+
     /// @brief Performs cleanup, immediately before termination
     ///
     /// This method performs final clean up, just before the Dhcpv4Srv object
@@ -185,9 +195,9 @@ private:
     commandConfigWriteHandler(const std::string& command,
                               isc::data::ConstElementPtr args);
 
-    /// @brief handler for processing 'set-config' command
+    /// @brief handler for processing 'config-set' command
     ///
-    /// This handler processes set-config command, which processes
+    /// This handler processes config-set command, which processes
     /// configuration specified in args parameter.
     /// @param command (parameter ignored)
     /// @param args configuration to be processed. Expected format:
@@ -196,7 +206,7 @@ private:
     ///
     /// @return status of the command
     isc::data::ConstElementPtr
-    commandSetConfigHandler(const std::string& command,
+    commandConfigSetHandler(const std::string& command,
                             isc::data::ConstElementPtr args);
 
     /// @brief handler for processing 'config-test' command

+ 1 - 1
src/bin/dhcp4/dhcp4_hooks.dox

@@ -331,7 +331,7 @@ to the end of this list.
    will only return the list of commands supported by the hook library.
    The callout can modify the command arguments to influence the command
    processing by the Command Manager. For example, it may freely modify
-   the configuration received in 'set-config' before it is processed by
+   the configuration received in 'config-set' before it is processed by
    the server. The SKIP action is not set in this case.
 
  - <b>Next step status</b>: if any callout sets the next step action to SKIP,

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

@@ -1,157 +0,0 @@
-// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-#include <config.h>
-
-#include <dhcp4/json_config_parser.h>
-#include <dhcp4/ctrl_dhcp4_srv.h>
-#include <dhcp4/dhcp4_log.h>
-#include <dhcp4/parser_context.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <exceptions/exceptions.h>
-
-#include <string>
-
-using namespace isc::asiolink;
-using namespace isc::dhcp;
-using namespace std;
-
-namespace {
-
-/// @brief Configure DHCPv4 server using the configuration file specified.
-///
-/// This function is used to both configure the DHCP server on its startup
-/// and dynamically reconfigure the server when SIGHUP signal is received.
-///
-/// It fetches DHCPv6 server's configuration from the 'Dhcp4' section of
-/// the JSON configuration file.
-///
-/// @param file_name Configuration file location.
-void configure(const std::string& file_name) {
-    // This is a configuration backend implementation that reads the
-    // configuration from a JSON file.
-
-    isc::data::ConstElementPtr json;
-    isc::data::ConstElementPtr dhcp4;
-    isc::data::ConstElementPtr logger;
-    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(isc::BadValue, "JSON configuration file not specified."
-                      " Please use -c command line option.");
-        }
-
-        // Read contents of the file and parse it as JSON
-        Parser4Context parser;
-        json = parser.parseFile(file_name, Parser4Context::PARSER_DHCP4);
-        if (!json) {
-            isc_throw(isc::BadValue, "no configuration found");
-        }
-
-        // Let's do sanity check before we call json->get() which
-        // works only for map.
-        if (json->getType() != isc::data::Element::map) {
-            isc_throw(isc::BadValue, "Configuration file is expected to be "
-                      "a map, i.e., start with { and end with } and contain "
-                      "at least an entry called 'Dhcp4' that itself is a map. "
-                      << file_name
-                      << " is a valid JSON, but its top element is not a map."
-                      " Did you forget to add { } around your configuration?");
-        }
-
-        // Use parsed JSON structures to configure the server
-        result = ControlledDhcpv4Srv::processCommand("set-config", json);
-        if (!result) {
-            // Undetermined status of the configuration. This should never
-            // happen, but as the configureDhcp4Server returns a pointer, it is
-            // theoretically possible that it will return NULL.
-            isc_throw(isc::BadValue, "undefined result of "
-                      "processCommand(\"set-config\", json)");
-        }
-
-        // Now check is the returned result is successful (rcode=0) or not
-        // (see @ref isc::config::parseAnswer).
-        int rcode;
-        isc::data::ConstElementPtr comment =
-            isc::config::parseAnswer(rcode, result);
-        if (rcode != 0) {
-            string reason = comment ? comment->stringValue() :
-                "no details available";
-            isc_throw(isc::BadValue, reason);
-        }
-    }  catch (const std::exception& ex) {
-        // If configuration failed at any stage, we drop the staging
-        // configuration and continue to use the previous one.
-        CfgMgr::instance().rollback();
-
-        LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
-            .arg(file_name).arg(ex.what());
-        isc_throw(isc::BadValue, "configuration error using file '"
-                  << file_name << "': " << ex.what());
-    }
-}
-
-/// @brief Signals handler for DHCPv4 server.
-///
-/// This signal handler handles the following signals received by the DHCPv4
-/// server process:
-/// - SIGHUP - triggers server's dynamic reconfiguration.
-/// - SIGTERM - triggers server's shut down.
-/// - SIGINT - triggers server's shut down.
-///
-/// @param signo Signal number received.
-void signalHandler(int signo) {
-    // SIGHUP signals a request to reconfigure the server.
-    if (signo == SIGHUP) {
-        // Get configuration file name.
-        std::string file = ControlledDhcpv4Srv::getInstance()->getConfigFile();
-        try {
-            LOG_INFO(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION).arg(file);
-            configure(file);
-        } catch (const std::exception& ex) {
-            // Log the unsuccessful reconfiguration. The reason for failure
-            // should be already logged. Don't rethrow an exception so as
-            // the server keeps working.
-            LOG_ERROR(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION_FAIL)
-                .arg(file);
-        }
-    } else if ((signo == SIGTERM) || (signo == SIGINT)) {
-        isc::data::ElementPtr params(new isc::data::MapElement());
-        ControlledDhcpv4Srv::processCommand("shutdown", params);
-    }
-}
-
-}
-
-namespace isc {
-namespace dhcp {
-
-void
-ControlledDhcpv4Srv::init(const std::string& file_name) {
-    // Configure the server using JSON file.
-    configure(file_name);
-
-    // We don't need to call openActiveSockets() or startD2() as these
-    // methods are called in processConfig() which is called by
-    // processCommand("set-config", ...)
-
-    // Set signal handlers. When the SIGHUP is received by the process
-    // the server reconfiguration will be triggered. When SIGTERM or
-    // SIGINT will be received, the server will start shutting down.
-    signal_set_.reset(new isc::util::SignalSet(SIGINT, SIGHUP, SIGTERM));
-    // Set the pointer to the handler function.
-    signal_handler_ = signalHandler;
-}
-
-void ControlledDhcpv4Srv::cleanup() {
-    // Nothing to do here. No need to disconnect from anything.
-}
-
-};
-};

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

@@ -383,10 +383,10 @@ TEST_F(CtrlChannelDhcpv4SrvTest, commandsRegistration) {
     EXPECT_TRUE(command_list.find("\"list-commands\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"build-report\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"config-get\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"config-set\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"config-write\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"leases-reclaim\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"libreload\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"set-config\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"shutdown\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"statistic-get\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"statistic-get-all\"") != string::npos);
@@ -594,13 +594,13 @@ TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelStats) {
               response);
 }
 
-// Check that the "set-config" command will replace current configuration
-TEST_F(CtrlChannelDhcpv4SrvTest, set_config) {
+// Check that the "config-set" command will replace current configuration
+TEST_F(CtrlChannelDhcpv4SrvTest, configSet) {
     createUnixChannelServer();
 
     // Define strings to permutate the config arguments
     // (Note the line feeds makes errors easy to find)
-    string set_config_txt = "{ \"command\": \"set-config\" \n";
+    string set_config_txt = "{ \"command\": \"config-set\" \n";
     string args_txt = " \"arguments\": { \n";
     string dhcp4_cfg_txt =
         "    \"Dhcp4\": { \n"
@@ -665,7 +665,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, set_config) {
         << logger_txt
         << "}}";
 
-    // Send the set-config command
+    // Send the config-set command
     std::string response;
     sendUnixCommand(os.str(), response);
 
@@ -691,7 +691,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, set_config) {
         << "}\n"                      // close dhcp4
         "}}";
 
-    // Send the set-config command
+    // Send the config-set command
     sendUnixCommand(os.str(), response);
 
     // Should fail with a syntax error
@@ -720,7 +720,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, set_config) {
     // Verify the control channel socket exists.
     ASSERT_TRUE(fileExists(socket_path_));
 
-    // Send the set-config command.
+    // Send the config-set command.
     sendUnixCommand(os.str(), response);
 
     // Verify the control channel socket no longer exists.
@@ -752,11 +752,12 @@ TEST_F(CtrlChannelDhcpv4SrvTest, listCommands) {
     // We expect the server to report at least the following commands:
     checkListCommands(rsp, "build-report");
     checkListCommands(rsp, "config-get");
+    checkListCommands(rsp, "config-reload");
+    checkListCommands(rsp, "config-set");
     checkListCommands(rsp, "config-write");
     checkListCommands(rsp, "list-commands");
     checkListCommands(rsp, "leases-reclaim");
     checkListCommands(rsp, "libreload");
-    checkListCommands(rsp, "set-config");
     checkListCommands(rsp, "shutdown");
     checkListCommands(rsp, "statistic-get");
     checkListCommands(rsp, "statistic-get-all");
@@ -797,7 +798,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configTest) {
 
     // Define strings to permutate the config arguments
     // (Note the line feeds makes errors easy to find)
-    string set_config_txt = "{ \"command\": \"set-config\" \n";
+    string set_config_txt = "{ \"command\": \"config-set\" \n";
     string config_test_txt = "{ \"command\": \"config-test\" \n";
     string args_txt = " \"arguments\": { \n";
     string dhcp4_cfg_txt =
@@ -863,7 +864,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configTest) {
         << logger_txt
         << "}}";
 
-    // Send the set-config command
+    // Send the config-set command
     std::string response;
     sendUnixCommand(os.str(), response);
 
@@ -925,8 +926,8 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configTest) {
 
     // Verify the configuration was successful.
     EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration seems sane. "
-	      "Control-socket, hook-libraries, and D2 configuration were "
-	      "sanity checked, but not applied.\" }",
+              "Control-socket, hook-libraries, and D2 configuration were "
+              "sanity checked, but not applied.\" }",
               response);
 
     // Check that the config was not applied
@@ -1000,4 +1001,95 @@ TEST_F(CtrlChannelDhcpv4SrvTest, writeConfigInvalidEscape) {
                      "Using \\ in filename is not allowed.");
 }
 
+// Tests if config-reload attempts to reload a file and reports that the
+// file is missing.
+TEST_F(CtrlChannelDhcpv4SrvTest, configReloadMissingFile) {
+    createUnixChannelServer();
+    std::string response;
+
+    // This is normally set to whatever value is passed to -c when the server is
+    // started, but we're not starting it that way, so need to set it by hand.
+    server_->setConfigFile("test6.json");
+
+    // Tell the server to reload its configuration. It should attempt to load
+    // test6.json (and fail, because the file is not there).
+    sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+    // Verify the reload was rejected.
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"Config reload failed:"
+              "configuration error using file 'test6.json': Unable to open file "
+              "test6.json\" }",
+              response);
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is not a valid JSON.
+TEST_F(CtrlChannelDhcpv4SrvTest, configReloadBrokenFile) {
+    createUnixChannelServer();
+    std::string response;
+
+    // This is normally set to whatever value is passed to -c when the server is
+    // started, but we're not starting it that way, so need to set it by hand.
+    server_->setConfigFile("test7.json");
+
+    // Although Kea is smart, its AI routines are not smart enough to handle
+    // this one... at least not yet.
+    ofstream f("test7.json", ios::trunc);
+    f << "gimme some addrs, bro!";
+    f.close();
+
+    // Now tell Kea to reload its config.
+    sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+    // Verify the reload will fail.
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"Config reload failed:"
+              "configuration error using file 'test7.json': "
+              "test7.json:1.1: Invalid character: g\" }",
+              response);
+
+    ::remove("test7.json");
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is loaded correctly.
+TEST_F(CtrlChannelDhcpv4SrvTest, configReloadValid) {
+    createUnixChannelServer();
+    std::string response;
+
+    // This is normally set to whatever value is passed to -c when the server is
+    // started, but we're not starting it that way, so need to set it by hand.
+    server_->setConfigFile("test8.json");
+
+    // Ok, enough fooling around. Let's create a valid config.
+    const std::string cfg_txt =
+        "{ \"Dhcp4\": {"
+        "    \"interfaces-config\": {"
+        "        \"interfaces\": [ \"*\" ]"
+        "    },"
+        "    \"subnet4\": ["
+        "        { \"subnet\": \"192.0.2.0/24\" },"
+        "        { \"subnet\": \"192.0.3.0/24\" }"
+        "     ],"
+        "    \"valid-lifetime\": 4000,"
+        "    \"lease-database\": {"
+        "       \"type\": \"memfile\", \"persist\": false }"
+        "} }";
+    ofstream f("test8.json", ios::trunc);
+    f << cfg_txt;
+    f.close();
+
+    // This command should reload test8.json config.
+    sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+    // Verify the configuration was successful.
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+              response);
+
+    // Check that the config was indeed applied.
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(2, subnets->size());
+
+    ::remove("test8.json");
+}
+
 } // End of anonymous namespace

+ 0 - 1
src/bin/dhcp6/Makefile.am

@@ -67,7 +67,6 @@ libdhcp6_la_SOURCES += dhcp6to4_ipc.cc dhcp6to4_ipc.h
 libdhcp6_la_SOURCES += dhcp6_lexer.ll location.hh position.hh stack.hh
 libdhcp6_la_SOURCES += dhcp6_parser.cc dhcp6_parser.h
 libdhcp6_la_SOURCES += parser_context.cc parser_context.h parser_context_decl.h
-libdhcp6_la_SOURCES += kea_controller.cc
 libdhcp6_la_SOURCES += simple_parser6.cc simple_parser6.h
 
 nodist_libdhcp6_la_SOURCES = dhcp6_messages.h dhcp6_messages.cc

+ 166 - 16
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -15,11 +15,14 @@
 #include <dhcp6/dhcp6to4_ipc.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/json_config_parser.h>
+#include <dhcp6/parser_context.h>
 #include <hooks/hooks_manager.h>
 #include <stats/stats_mgr.h>
 #include <cfgrpt/config_report.h>
+#include <signal.h>
 
 using namespace isc::config;
+using namespace isc::dhcp;
 using namespace isc::data;
 using namespace isc::hooks;
 using namespace isc::stats;
@@ -30,6 +33,26 @@ namespace {
 // Name of the file holding server identifier.
 static const char* SERVER_DUID_FILE = "kea-dhcp6-serverid";
 
+/// @brief Signals handler for DHCPv6 server.
+///
+/// This signal handler handles the following signals received by the DHCPv6
+/// server process:
+/// - SIGHUP - triggers server's dynamic reconfiguration.
+/// - SIGTERM - triggers server's shut down.
+/// - SIGINT - triggers server's shut down.
+///
+/// @param signo Signal number received.
+void signalHandler(int signo) {
+    // SIGHUP signals a request to reconfigure the server.
+    if (signo == SIGHUP) {
+        ControlledDhcpv6Srv::processCommand("config-reload",
+                                            ConstElementPtr());
+    } else if ((signo == SIGTERM) || (signo == SIGINT)) {
+        ControlledDhcpv6Srv::processCommand("shutdown",
+                                            ConstElementPtr());
+    }
+}
+
 }
 
 namespace isc {
@@ -37,6 +60,116 @@ namespace dhcp {
 
 ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
 
+/// @brief Configure DHCPv6 server using the configuration file specified.
+///
+/// This function is used to both configure the DHCP server on its startup
+/// and dynamically reconfigure the server when SIGHUP signal is received.
+///
+/// It fetches DHCPv6 server's configuration from the 'Dhcp6' section of
+/// the JSON configuration file.
+///
+/// @param file_name Configuration file location.
+/// @return status of the command
+ConstElementPtr
+ControlledDhcpv6Srv::loadConfigFile(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 logger;
+    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(isc::BadValue, "JSON configuration file not specified. Please "
+                      "use -c command line option.");
+        }
+
+        // Read contents of the file and parse it as JSON
+        Parser6Context parser;
+        json = parser.parseFile(file_name, Parser6Context::PARSER_DHCP6);
+        if (!json) {
+            isc_throw(isc::BadValue, "no configuration found");
+        }
+
+        // Let's do sanity check before we call json->get() which
+        // works only for map.
+        if (json->getType() != isc::data::Element::map) {
+            isc_throw(isc::BadValue, "Configuration file is expected to be "
+                      "a map, i.e., start with { and end with } and contain "
+                      "at least an entry called 'Dhcp6' that itself is a map. "
+                      << file_name
+                      << " is a valid JSON, but its top element is not a map."
+                      " Did you forget to add { } around your configuration?");
+        }
+
+        // Use parsed JSON structures to configure the server
+        result = ControlledDhcpv6Srv::processCommand("config-set", json);
+        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.
+            isc_throw(isc::BadValue, "undefined result of "
+                      "processCommand(\"config-set\", json)");
+        }
+
+        // Now check is the returned result is successful (rcode=0) or not
+        // (see @ref isc::config::parseAnswer).
+        int rcode;
+        isc::data::ConstElementPtr comment =
+            isc::config::parseAnswer(rcode, result);
+        if (rcode != 0) {
+            string reason = comment ? comment->stringValue() :
+                "no details available";
+            isc_throw(isc::BadValue, reason);
+        }
+    }  catch (const std::exception& ex) {
+        // If configuration failed at any stage, we drop the staging
+        // configuration and continue to use the previous one.
+        CfgMgr::instance().rollback();
+
+        LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
+            .arg(file_name).arg(ex.what());
+        isc_throw(isc::BadValue, "configuration error using file '"
+                  << file_name << "': " << ex.what());
+    }
+
+    return (result);
+}
+
+
+void
+ControlledDhcpv6Srv::init(const std::string& file_name) {
+    // Configure the server using JSON file.
+    ConstElementPtr result = loadConfigFile(file_name);
+    int rcode;
+    ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
+    if (rcode != 0) {
+        string reason = comment ? comment->stringValue() :
+            "no details available";
+        isc_throw(isc::BadValue, reason);
+    }
+
+    // We don't need to call openActiveSockets() or startD2() as these
+    // methods are called in processConfig() which is called by
+    // processCommand("config-set", ...)
+
+    // Set signal handlers. When the SIGHUP is received by the process
+    // the server reconfiguration will be triggered. When SIGTERM or
+    // SIGINT will be received, the server will start shutting down.
+    signal_set_.reset(new isc::util::SignalSet(SIGINT, SIGHUP, SIGTERM));
+    // Set the pointer to the handler function.
+    signal_handler_ = signalHandler;
+}
+
+void ControlledDhcpv6Srv::cleanup() {
+    // Nothing to do here. No need to disconnect from anything.
+}
+
+
 ConstElementPtr
 ControlledDhcpv6Srv::commandShutdownHandler(const string&, ConstElementPtr) {
     if (ControlledDhcpv6Srv::server_) {
@@ -68,9 +201,22 @@ ControlledDhcpv6Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
 }
 
 ConstElementPtr
-ControlledDhcpv6Srv::commandConfigReloadHandler(const string&, ConstElementPtr args) {
-    // Use set-config as it handles logging and server config
-    return (commandSetConfigHandler("set-config", args));
+ControlledDhcpv6Srv::commandConfigReloadHandler(const string&,
+                                                ConstElementPtr /*args*/) {
+    // Get configuration file name.
+    std::string file = ControlledDhcpv6Srv::getInstance()->getConfigFile();
+    try {
+        LOG_INFO(dhcp6_logger, DHCP6_DYNAMIC_RECONFIGURATION).arg(file);
+        return (loadConfigFile(file));
+    } catch (const std::exception& ex) {
+        // Log the unsuccessful reconfiguration. The reason for failure
+        // should be already logged. Don't rethrow an exception so as
+        // the server keeps working.
+        LOG_ERROR(dhcp6_logger, DHCP6_DYNAMIC_RECONFIGURATION_FAIL)
+            .arg(file);
+        return (createAnswer(CONTROL_RESULT_ERROR,
+                             "Config reload failed:" + string(ex.what())));
+    }
 }
 
 ConstElementPtr
@@ -152,7 +298,7 @@ ControlledDhcpv6Srv::commandConfigWriteHandler(const string&, ConstElementPtr ar
 }
 
 ConstElementPtr
-ControlledDhcpv6Srv::commandSetConfigHandler(const string&,
+ControlledDhcpv6Srv::commandConfigSetHandler(const string&,
                                              ConstElementPtr args) {
     const int status_code = CONTROL_RESULT_ERROR;
     ConstElementPtr dhcp6;
@@ -297,15 +443,17 @@ ControlledDhcpv6Srv::commandLeasesReclaimHandler(const string&,
 isc::data::ConstElementPtr
 ControlledDhcpv6Srv::processCommand(const std::string& command,
                                     isc::data::ConstElementPtr args) {
+    string txt = args ? args->str() : "(none)";
+
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_COMMAND_RECEIVED)
-              .arg(command).arg(args->str());
+              .arg(command).arg(txt);
 
     ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
 
     if (!srv) {
         ConstElementPtr no_srv = isc::config::createAnswer(1,
           "Server object not initialized, can't process command '" +
-          command + "'.");
+          command + "', arguments: '" + txt + "'.");
         return (no_srv);
     }
 
@@ -319,8 +467,8 @@ ControlledDhcpv6Srv::processCommand(const std::string& command,
         } else if (command == "config-reload") {
             return (srv->commandConfigReloadHandler(command, args));
 
-        } else if (command == "set-config") {
-            return (srv->commandSetConfigHandler(command, args));
+        } else if (command == "config-set") {
+            return (srv->commandConfigSetHandler(command, args));
 
         } else if (command == "config-get") {
             return (srv->commandConfigGetHandler(command, args));
@@ -486,18 +634,18 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
 
 isc::data::ConstElementPtr
 ControlledDhcpv6Srv::checkConfig(isc::data::ConstElementPtr config) {
- 
+
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_RECEIVED)
         .arg(config->str());
- 
+
     ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
- 
+
     if (!srv) {
         ConstElementPtr no_srv = isc::config::createAnswer(1,
             "Server object not initialized, can't process config.");
         return (no_srv);
     }
- 
+
     return (configureDhcp6Server(*srv, config, true));
 }
 
@@ -517,7 +665,8 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
     CommandMgr::instance().registerCommand("config-get",
         boost::bind(&ControlledDhcpv6Srv::commandConfigGetHandler, this, _1, _2));
 
-    /// @todo: register config-reload (see CtrlDhcpv6Srv::commandConfigReloadHandler)
+    CommandMgr::instance().registerCommand("config-reload",
+        boost::bind(&ControlledDhcpv6Srv::commandConfigReloadHandler, this, _1, _2));
 
     CommandMgr::instance().registerCommand("config-test",
         boost::bind(&ControlledDhcpv6Srv::commandConfigTestHandler, this, _1, _2));
@@ -531,8 +680,8 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
     CommandMgr::instance().registerCommand("libreload",
         boost::bind(&ControlledDhcpv6Srv::commandLibReloadHandler, this, _1, _2));
 
-    CommandMgr::instance().registerCommand("set-config",
-        boost::bind(&ControlledDhcpv6Srv::commandSetConfigHandler, this, _1, _2));
+    CommandMgr::instance().registerCommand("config-set",
+        boost::bind(&ControlledDhcpv6Srv::commandConfigSetHandler, this, _1, _2));
 
     CommandMgr::instance().registerCommand("shutdown",
         boost::bind(&ControlledDhcpv6Srv::commandShutdownHandler, this, _1, _2));
@@ -580,11 +729,12 @@ ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
         // Deregister any registered commands (please keep in alphabetic order)
         CommandMgr::instance().deregisterCommand("build-report");
         CommandMgr::instance().deregisterCommand("config-get");
+        CommandMgr::instance().deregisterCommand("config-set");
+        CommandMgr::instance().deregisterCommand("config-reload");
         CommandMgr::instance().deregisterCommand("config-test");
         CommandMgr::instance().deregisterCommand("config-write");
         CommandMgr::instance().deregisterCommand("leases-reclaim");
         CommandMgr::instance().deregisterCommand("libreload");
-        CommandMgr::instance().deregisterCommand("set-config");
         CommandMgr::instance().deregisterCommand("shutdown");
         CommandMgr::instance().deregisterCommand("statistic-get");
         CommandMgr::instance().deregisterCommand("statistic-get-all");

+ 22 - 12
src/bin/dhcp6/ctrl_dhcp6_srv.h

@@ -34,15 +34,25 @@ public:
 
     /// @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
-    /// *_backend.cc
+    /// It reads the JSON file from disk or may perform any other setup
+    /// operation. In particular, it also install signal handlers.
     ///
-    /// This method may throw if initialization fails. Exception types may be
-    /// specific to used configuration backend.
+    /// This method may throw if initialization fails.
     void init(const std::string& config_file);
 
+    /// @brief Loads specific configuration file
+    ///
+    /// This utility method is called whenever we know a filename of the config
+    /// and need to load it. It calls config-set command once the content of
+    /// the file has been loaded and verified to be a sane JSON configuration.
+    /// config-set handler will process the config file (apply it as current
+    /// configuration).
+    ///
+    /// @param file_name name of the file to be loaded
+    /// @return status of the file loading and outcome of config-set
+    isc::data::ConstElementPtr
+    loadConfigFile(const std::string& file_name);
+
     /// @brief Performs cleanup, immediately before termination
     ///
     /// This method performs final clean up, just before the Dhcpv6Srv object
@@ -63,7 +73,7 @@ public:
     /// - config-reload
     /// - config-test
     /// - leases-reclaim
-    /// - libreload    
+    /// - libreload
     /// - shutdown
     /// ...
     ///
@@ -185,9 +195,9 @@ private:
     commandConfigWriteHandler(const std::string& command,
                               isc::data::ConstElementPtr args);
 
-    /// @brief handler for processing 'set-config' command
+    /// @brief handler for processing 'config-set' command
     ///
-    /// This handler processes set-config command, which processes
+    /// This handler processes config-set command, which processes
     /// configuration specified in args parameter.
     /// @param command (parameter ignored)
     /// @param args configuration to be processed. Expected format:
@@ -196,7 +206,7 @@ private:
     ///
     /// @return status of the command
     isc::data::ConstElementPtr
-    commandSetConfigHandler(const std::string& command,
+    commandConfigSetHandler(const std::string& command,
                             isc::data::ConstElementPtr args);
 
     /// @brief handler for processing 'config-test' command
@@ -218,7 +228,7 @@ private:
     /// This handler processes version-get command, which returns
     /// over the control channel the -v and -V command line arguments.
     /// @param command (parameter ignored)
-    /// @param args (parameter ignored) 
+    /// @param args (parameter ignored)
     ///
     /// @return status of the command with the version in text and
     /// the extended version in arguments.
@@ -231,7 +241,7 @@ private:
     /// This handler processes build-report command, which returns
     /// over the control channel the -W command line argument.
     /// @param command (parameter ignored)
-    /// @param args (parameter ignored) 
+    /// @param args (parameter ignored)
     ///
     /// @return status of the command with the config report
     isc::data::ConstElementPtr

+ 1 - 1
src/bin/dhcp6/dhcp6_hooks.dox

@@ -373,7 +373,7 @@ to the end of this list.
    will only return the list of commands supported by the hook library.
    The callout can modify the command arguments to influence the command
    processing by the Command Manager. For example, it may freely modify
-   the configuration received in 'set-config' before it is processed by
+   the configuration received in 'config-set' before it is processed by
    the server. The SKIP action is not set in this case.
 
  - <b>Next step status</b>: if any callout sets the next step action to SKIP,

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

@@ -1,162 +0,0 @@
-// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-#include <config.h>
-
-#include <asiolink/asio_wrapper.h>
-#include <asiolink/asiolink.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <dhcpsrv/parsers/dhcp_config_parser.h>
-#include <dhcp6/json_config_parser.h>
-#include <dhcp6/ctrl_dhcp6_srv.h>
-#include <dhcp6/parser_context.h>
-#include <dhcp6/dhcp6_log.h>
-#include <exceptions/exceptions.h>
-
-#include <signal.h>
-
-#include <string>
-
-using namespace isc::asiolink;
-using namespace isc::dhcp;
-using namespace std;
-
-namespace {
-
-/// @brief Configure DHCPv6 server using the configuration file specified.
-///
-/// This function is used to both configure the DHCP server on its startup
-/// and dynamically reconfigure the server when SIGHUP signal is received.
-///
-/// It fetches DHCPv6 server's configuration from the 'Dhcp6' section of
-/// the JSON configuration file.
-///
-/// @param file_name Configuration file location.
-void configure(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 logger;
-    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(isc::BadValue, "JSON configuration file not specified. Please "
-                      "use -c command line option.");
-        }
-
-        // Read contents of the file and parse it as JSON
-        Parser6Context parser;
-        json = parser.parseFile(file_name, Parser6Context::PARSER_DHCP6);
-        if (!json) {
-            isc_throw(isc::BadValue, "no configuration found");
-        }
-
-        // Let's do sanity check before we call json->get() which
-        // works only for map.
-        if (json->getType() != isc::data::Element::map) {
-            isc_throw(isc::BadValue, "Configuration file is expected to be "
-                      "a map, i.e., start with { and end with } and contain "
-                      "at least an entry called 'Dhcp6' that itself is a map. "
-                      << file_name
-                      << " is a valid JSON, but its top element is not a map."
-                      " Did you forget to add { } around your configuration?");
-        }
-
-        // Use parsed JSON structures to configure the server
-        result = ControlledDhcpv6Srv::processCommand("set-config", json);
-        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.
-            isc_throw(isc::BadValue, "undefined result of "
-                      "processCommand(\"set-config\", json)");
-        }
-
-        // Now check is the returned result is successful (rcode=0) or not
-        // (see @ref isc::config::parseAnswer).
-        int rcode;
-        isc::data::ConstElementPtr comment =
-            isc::config::parseAnswer(rcode, result);
-        if (rcode != 0) {
-            string reason = comment ? comment->stringValue() :
-                "no details available";
-            isc_throw(isc::BadValue, reason);
-        }
-    }  catch (const std::exception& ex) {
-        // If configuration failed at any stage, we drop the staging
-        // configuration and continue to use the previous one.
-        CfgMgr::instance().rollback();
-
-        LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
-            .arg(file_name).arg(ex.what());
-        isc_throw(isc::BadValue, "configuration error using file '"
-                  << file_name << "': " << ex.what());
-    }
-}
-
-/// @brief Signals handler for DHCPv6 server.
-///
-/// This signal handler handles the following signals received by the DHCPv6
-/// server process:
-/// - SIGHUP - triggers server's dynamic reconfiguration.
-/// - SIGTERM - triggers server's shut down.
-/// - SIGINT - triggers server's shut down.
-///
-/// @param signo Signal number received.
-void signalHandler(int signo) {
-    // SIGHUP signals a request to reconfigure the server.
-    if (signo == SIGHUP) {
-        // Get configuration file name.
-        std::string file = ControlledDhcpv6Srv::getInstance()->getConfigFile();
-        try {
-            LOG_INFO(dhcp6_logger, DHCP6_DYNAMIC_RECONFIGURATION).arg(file);
-            configure(file);
-        } catch (const std::exception& ex) {
-            // Log the unsuccessful reconfiguration. The reason for failure
-            // should be already logged. Don't rethrow an exception so as
-            // the server keeps working.
-            LOG_ERROR(dhcp6_logger, DHCP6_DYNAMIC_RECONFIGURATION_FAIL)
-                .arg(file);
-        }
-    } else if ((signo == SIGTERM) || (signo == SIGINT)) {
-        isc::data::ElementPtr params(new isc::data::MapElement());
-        ControlledDhcpv6Srv::processCommand("shutdown", params);
-    }
-}
-
-}
-
-namespace isc {
-namespace dhcp {
-
-void
-ControlledDhcpv6Srv::init(const std::string& file_name) {
-    // Configure the server using JSON file.
-    configure(file_name);
-
-    // We don't need to call openActiveSockets() or startD2() as these
-    // methods are called in processConfig() which is called by
-    // processCommand("reload-config", ...)
-
-    // Set signal handlers. When the SIGHUP is received by the process
-    // the server reconfiguration will be triggered. When SIGTERM or
-    // SIGINT will be received, the server will start shutting down.
-    signal_set_.reset(new isc::util::SignalSet(SIGINT, SIGHUP, SIGTERM));
-    // Set the pointer to the handler function.
-    signal_handler_ = signalHandler;
-}
-
-void ControlledDhcpv6Srv::cleanup() {
-    // Nothing to do here. No need to disconnect from anything.
-}
-
-};
-};

+ 101 - 69
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc

@@ -371,71 +371,13 @@ TEST_F(CtrlChannelDhcpv6SrvTest, 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 "config-reload" command.  This should cause the libraries
-    // to unload and to reload.
-
-    // Use empty parameters list
-    // Prepare configuration file.
-    string config_txt = "{ \"Dhcp6\": { \"interfaces-config\": {"
-        "  \"interfaces\": [ \"*\" ]"
-        "},"
-        "\"preferred-lifetime\": 3000,"
-        "\"rebind-timer\": 2000, "
-        "\"renew-timer\": 1000, "
-        "\"subnet6\": [ { "
-        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
-        "    \"subnet\": \"2001:db8:1::/64\" "
-        " },"
-        " {"
-        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
-        "    \"subnet\": \"2001:db8:2::/64\", "
-        "    \"id\": 0"
-        " },"
-        " {"
-        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
-        "    \"subnet\": \"2001:db8:3::/64\" "
-        " } ],"
-        "\"valid-lifetime\": 4000 }}";
-
-    ConstElementPtr config;
-    ASSERT_NO_THROW(config = parseJSON(config_txt));
-
-    // Make sure there are no subnets configured.
-    CfgMgr::instance().clear();
-
-    // 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().getCurrentCfg()->getCfgSubnets6()->getAll();
-    EXPECT_EQ(3, subnets->size());
-
-    // Clean up after the test.
-    CfgMgr::instance().clear();
-}
-
-// Check that the "set-config" command will replace current configuration
+// Check that the "config-set" command will replace current configuration
 TEST_F(CtrlChannelDhcpv6SrvTest, configSet) {
     createUnixChannelServer();
 
     // Define strings to permutate the config arguments
     // (Note the line feeds makes errors easy to find)
-    string set_config_txt = "{ \"command\": \"set-config\" \n";
+    string set_config_txt = "{ \"command\": \"config-set\" \n";
     string args_txt = " \"arguments\": { \n";
     string dhcp6_cfg_txt =
         "    \"Dhcp6\": { \n"
@@ -501,7 +443,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configSet) {
         << logger_txt
         << "}}";
 
-    // Send the set-config command
+    // Send the config-set command
     std::string response;
     sendUnixCommand(os.str(), response);
 
@@ -527,7 +469,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configSet) {
         << "}\n"                      // close dhcp6
         "}}";
 
-    // Send the set-config command
+    // Send the config-set command
     sendUnixCommand(os.str(), response);
 
     // Should fail with a syntax error
@@ -555,7 +497,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configSet) {
     // Verify the control channel socket exists.
     ASSERT_TRUE(fileExists(socket_path_));
 
-    // Send the set-config command.
+    // Send the config-set command.
     sendUnixCommand(os.str(), response);
 
     // Verify the control channel socket no longer exists.
@@ -579,7 +521,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configTest) {
 
     // Define strings to permutate the config arguments
     // (Note the line feeds makes errors easy to find)
-    string set_config_txt = "{ \"command\": \"set-config\" \n";
+    string set_config_txt = "{ \"command\": \"config-set\" \n";
     string config_test_txt = "{ \"command\": \"config-test\" \n";
     string args_txt = " \"arguments\": { \n";
     string dhcp6_cfg_txt =
@@ -646,7 +588,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configTest) {
         << logger_txt
         << "}}";
 
-    // Send the set-config command
+    // Send the config-set command
     std::string response;
     sendUnixCommand(os.str(), response);
 
@@ -708,8 +650,8 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configTest) {
 
     // Verify the configuration was successful.
     EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration seems sane. "
-	      "Control-socket, hook-libraries, and D2 configuration were "
-	      "sanity checked, but not applied.\" }",
+              "Control-socket, hook-libraries, and D2 configuration were "
+              "sanity checked, but not applied.\" }",
               response);
 
     // Check that the config was not applied.
@@ -753,7 +695,7 @@ TEST_F(CtrlDhcpv6SrvTest, commandsRegistration) {
     EXPECT_TRUE(command_list.find("\"config-write\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"leases-reclaim\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"libreload\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"set-config\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"config-set\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"shutdown\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"statistic-get\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"statistic-get-all\"") != string::npos);
@@ -982,12 +924,12 @@ TEST_F(CtrlChannelDhcpv6SrvTest, commandsList) {
     // We expect the server to report at least the following commands:
     checkListCommands(rsp, "build-report");
     checkListCommands(rsp, "config-get");
+    checkListCommands(rsp, "config-set");
     checkListCommands(rsp, "config-test");
     checkListCommands(rsp, "config-write");
     checkListCommands(rsp, "list-commands");
     checkListCommands(rsp, "leases-reclaim");
     checkListCommands(rsp, "libreload");
-    checkListCommands(rsp, "set-config");
     checkListCommands(rsp, "version-get");
     checkListCommands(rsp, "shutdown");
     checkListCommands(rsp, "statistic-get");
@@ -1085,4 +1027,94 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configWriteInvalidEscape) {
                      "Using \\ in filename is not allowed.");
 }
 
+// Tests if config-reload attempts to reload a file and reports that the
+// file is missing.
+TEST_F(CtrlChannelDhcpv6SrvTest, configReloadMissingFile) {
+    createUnixChannelServer();
+    std::string response;
+
+    // This is normally set to whatever value is passed to -c when the server is
+    // started, but we're not starting it that way, so need to set it by hand.
+    server_->setConfigFile("test6.json");
+
+    // Tell the server to reload its configuration. It should attempt to load
+    // test6.json (and fail, because the file is not there).
+    sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+    // Verify the reload was rejected.
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"Config reload failed:"
+              "configuration error using file 'test6.json': Unable to open file "
+              "test6.json\" }",
+              response);
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is not a valid JSON.
+TEST_F(CtrlChannelDhcpv6SrvTest, configReloadBrokenFile) {
+    createUnixChannelServer();
+    std::string response;
+
+    // This is normally set to whatever value is passed to -c when the server is
+    // started, but we're not starting it that way, so need to set it by hand.
+    server_->setConfigFile("test7.json");
+
+    // Although Kea is smart, its AI routines are not smart enough to handle
+    // this one... at least not yet.
+    ofstream f("test7.json", ios::trunc);
+    f << "gimme some addr, bro!";
+    f.close();
+
+    // Now tell Kea to reload its config.
+    sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+    // Verify the reload will fail.
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"Config reload failed:"
+              "configuration error using file 'test7.json': "
+              "test7.json:1.1: Invalid character: g\" }",
+              response);
+
+    ::remove("test7.json");
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is loaded correctly.
+TEST_F(CtrlChannelDhcpv6SrvTest, configReloadValid) {
+    createUnixChannelServer();
+    std::string response;
+
+    // This is normally set to whatever value is passed to -c when the server is
+    // started, but we're not starting it that way, so need to set it by hand.
+    server_->setConfigFile("test8.json");
+
+    // Ok, enough fooling around. Let's create a valid config.
+    const std::string cfg_txt =
+        "{ \"Dhcp6\": {"
+        "    \"interfaces-config\": {"
+        "        \"interfaces\": [ \"*\" ]"
+        "    },"
+        "    \"subnet6\": ["
+        "        { \"subnet\": \"2001:db8:1::/64\" },"
+        "        { \"subnet\": \"2001:db8:2::/64\" }"
+        "     ],"
+        "    \"lease-database\": {"
+        "       \"type\": \"memfile\", \"persist\": false }"
+        "} }";
+    ofstream f("test8.json", ios::trunc);
+    f << cfg_txt;
+    f.close();
+
+    // This command should reload test8.json config.
+    sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+    // Verify the configuration was successful.
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+              response);
+
+    // Check that the config was indeed applied.
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(2, subnets->size());
+
+    ::remove("test8.json");
+}
+
 } // End of anonymous namespace