Browse Source

[master] Merge branch 'trac3406'

Marcin Siodelski 11 years ago
parent
commit
3be60fa6ac

+ 1 - 0
doc/devel/mainpage.dox

@@ -64,6 +64,7 @@
  *   - @subpage dhcpv6OptionsParse
  *   - @subpage dhcpv6Classifier
  *   - @subpage dhcpv6ConfigBackend
+ *   - @subpage dhcpv6SignalBasedReconfiguration
  *   - @subpage dhcpv6Other
  * - @subpage d2
  *   - @subpage d2CPL

+ 4 - 1
src/bin/dhcp6/bundy_controller.cc

@@ -133,7 +133,10 @@ bundyConfigHandler(ConstElementPtr new_config) {
 }
 
 void
-ControlledDhcpv6Srv::init(const std::string& /* config_file*/) {
+ControlledDhcpv6Srv::init(const std::string& config_file) {
+    // Call base class's init.
+    Daemon::init(config_file);
+
     // This is Bundy configuration backed. It established control session
     // that is used to connect to Bundy framework.
     //

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

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -262,6 +262,38 @@ for ISC-DHCP. Is at least possible that similar will happen for Kea. Finally, if
 extend the isc::dhcp::CfgMgr with configuration export, this approach could be used as
 a migration tool.
 
+@section dhcpv6SignalBasedReconfiguration Reconfiguring DHCPv6 server with SIGHUP signal
+
+Online reconfiguration (reconfiguration without a need to restart the server) is an
+important feature which is supported by all modern DHCP servers. When using the JSON
+configuration backend, a configuration file name is specified with a command line
+option of the DHCP server binary. The configuration file is used to configure the
+server at startup. If the initial configuration fails, the server will fail to start.
+If the server starts and configures successfully it will use the initial configuration
+until it is reconfigured.
+
+The reconfiguration request can be triggered externally (from other process) by editing
+a configuration file and sending a SIGHUP signal to DHCP server process. After receiving
+the SIGHUP signal, the server will re-read the configuration file specified at startup.
+If the reconfiguration fails, the server will continue to run and use the last good
+configuration.
+
+The SIGHUP signal handler is defined in the kea_controller.cc. The handler calls the
+same function to reconfigure the server which is called to configure it at startup.
+The signal handler catches exceptions emitted during reconfiguration so as the
+uncaught exceptions don't cause the process to exit.
+
+Signal handlers are static and therefore they must call static functions. The
+@c ControlledDhcpv6Srv::processCommand which performs the actual server
+reconfiguration is static, so it can be called from the signal handler. In order
+for the signal handler to know the location of the configuration file (specified
+at process startup), the location of this file needs to be stored in a static
+variable so as it may be directly accessed by the signal handler. This static
+variable is stored in the @c dhcp::Daemon class and all Kea processes can use
+it (all processes derive from this class). The configuration file location is
+initialized when the @c Daemon::init method is called. Therefore, derived
+classes should call it in their implementations of the @c init method.
+
  @section dhcpv6Other Other DHCPv6 topics
 
  For hooks API support in DHCPv6, see @ref dhcpv6Hooks.

+ 8 - 0
src/bin/dhcp6/dhcp6_messages.mes

@@ -115,6 +115,14 @@ This message is printed when DHCPv6 server disables an interface from being
 used to receive DHCPv6 traffic. Sockets on this interface will not be opened
 by the Interface Manager until interface is enabled.
 
+% DHCP6_DYNAMIC_RECONFIGURATION initate server reconfiguration using file: %1, after receiving SIGHUP signal
+This is the info message logged when the DHCPv6 server starts reconfiguration
+as a result of receiving SIGHUP signal.
+
+% DHCP6_DYNAMIC_RECONFIGURATION_FAIL dynamic server reconfiguration failed with file: %1
+This is an error message logged when the dynamic reconfiguration of the
+DHCP server failed.
+
 % DHCP6_EXTEND_LEASE_SUBNET_SELECTED the %1 subnet was selected for client extending its lease
 This is a debug message informing that a given subnet was selected. It will
 be used for extending lifetime of the lease. This is one of the early steps

+ 70 - 11
src/bin/dhcp6/kea_controller.cc

@@ -33,6 +33,7 @@
 
 #include <cassert>
 #include <iostream>
+#include <signal.h>
 #include <string>
 #include <vector>
 
@@ -45,11 +46,18 @@ using namespace isc::log;
 using namespace isc::util;
 using namespace std;
 
-namespace isc {
-namespace dhcp {
-
-void
-ControlledDhcpv6Srv::init(const std::string& file_name) {
+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.
 
@@ -61,7 +69,7 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
     try {
         if (file_name.empty()) {
             // Basic sanity check: file name must not be empty.
-            isc_throw(BadValue, "JSON configuration file not specified. Please "
+            isc_throw(isc::BadValue, "JSON configuration file not specified. Please "
                       "use -c command line option.");
         }
 
@@ -71,7 +79,7 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
         if (!json) {
             LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
                 .arg("Config file " + file_name + " missing or empty.");
-            isc_throw(BadValue, "Unable to process JSON configuration file:"
+            isc_throw(isc::BadValue, "Unable to process JSON configuration file:"
                       + file_name);
         }
 
@@ -81,16 +89,16 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
         if (!dhcp6) {
             LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
                 .arg("Config file " + file_name + " does not include 'Dhcp6' entry.");
-            isc_throw(BadValue, "Unable to process JSON configuration file:"
+            isc_throw(isc::BadValue, "Unable to process JSON configuration file:"
                       + file_name);
         }
 
         // Use parsed JSON structures to configure the server
-        result = processCommand("config-reload", dhcp6);
+        result = ControlledDhcpv6Srv::processCommand("config-reload", dhcp6);
 
     }  catch (const std::exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
-        isc_throw(BadValue, "Unable to process JSON configuration file:"
+        isc_throw(isc::BadValue, "Unable to process JSON configuration file:"
                   + file_name);
     }
 
@@ -114,12 +122,63 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
             reason = string(" (") + comment->stringValue() + string(")");
         }
         LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(reason);
-        isc_throw(BadValue, "Failed to apply configuration:" << reason);
+        isc_throw(isc::BadValue, "Failed to apply configuration:" << reason);
     }
+}
+
+/// @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)) {
+        ElementPtr params(new isc::data::MapElement());
+        ControlledDhcpv6Srv::processCommand("shutdown", params);
+    }
+}
+
+}
+
+namespace isc {
+namespace dhcp {
+
+void
+ControlledDhcpv6Srv::init(const std::string& file_name) {
+    // Call parent class's init to initialize file name.
+    Daemon::init(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(SIGHUP, signalHandler);
+    signal(SIGTERM, signalHandler);
+    signal(SIGINT, signalHandler);
 }
 
 void ControlledDhcpv6Srv::cleanup() {

+ 16 - 1
src/bin/dhcp6/tests/Makefile.am

@@ -1,6 +1,14 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = dhcp6_test.py
-EXTRA_DIST = $(PYTESTS)
+SHTESTS =
+# The test of dynamic reconfiguration based on signals will work only
+# if we are using file based configuration approach.
+if CONFIG_BACKEND_JSON
+SHTESTS += dhcp6_reconfigure_test.sh
+SHTESTS += dhcp6_sigterm_test.sh
+SHTESTS += dhcp6_sigint_test.sh
+endif
+EXTRA_DIST = $(PYTESTS) $(SHTESTS)
 
 # Explicitly specify paths to dynamic libraries required by loadable python
 # modules. That is required on Mac OS systems. Otherwise we will get exception
@@ -20,6 +28,12 @@ check-local:
 		$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 
+	for shtest in $(SHTESTS) ; do \
+	echo Running test: $$shtest ; \
+	export B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir); \
+	$(abs_srcdir)/$$shtest || exit ; \
+	done
+
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
@@ -30,6 +44,7 @@ AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
 CLEANFILES  = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
 CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
+CLEANFILES += *.json *.log
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 if USE_CLANGPP

+ 164 - 0
src/bin/dhcp6/tests/dhcp6_reconfigure_test.sh

@@ -0,0 +1,164 @@
+# 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.
+
+# Test name
+TEST_NAME="DynamicReconfiguration"
+# Path to the temporary configuration file.
+CFG_FILE="test_config.json"
+# Path to the Kea log file.
+LOG_FILE="test.log"
+# Kea configuration to be stored in the configuration file.
+CONFIG="{
+    \"Dhcp6\":
+    {
+        \"interfaces\": [ ],
+        \"preferred-lifetime\": 3000,
+        \"valid-lifetime\": 4000,
+        \"renew-timer\": 1000,
+        \"rebind-timer\": 2000,
+        \"lease-database\":
+        {
+            \"type\": \"memfile\",
+            \"persist\": false
+        },
+        \"subnet6\": [
+        {
+            \"subnet\": \"2001:db8:1::/64\",
+            \"pool\": [ \"2001:db8:1::10-2001:db8:1::100\" ]
+        } ]
+    }
+}"
+# Invalid configuration (negative preferred-lifetime) to check that Kea
+# gracefully handles reconfiguration errors.
+CONFIG_INVALID="{
+    \"Dhcp6\":
+    {
+        \"interfaces\": [ ],
+        \"preferred-lifetime\": -3,
+        \"valid-lifetime\": 4000,
+        \"renew-timer\": 1000,
+        \"rebind-timer\": 2000,
+        \"lease-database\":
+        {
+            \"type\": \"memfile\",
+            \"persist\": false
+        },
+        \"subnet6\": [
+        {
+            \"subnet\": \"2001:db8:1::/64\",
+            \"pool\": [ \"2001:db8:1::10-2001:db8:1::100\" ]
+        } ]
+    }
+}"
+
+# Set the location of the executable.
+BIN="b10-dhcp6"
+BIN_PATH=".."
+
+# Import common test library.
+. $(dirname $0)/../../../lib/testutils/dhcp_test_lib.sh
+
+# Log the start of the test and print test name.
+test_start
+# Remove dangling Kea instances and remove log files.
+cleanup
+# Create new configuration file.
+create_config "${CONFIG}"
+# Instruct Kea to log to the specific file.
+set_logger
+# Start Kea.
+start_kea
+# Wait up to 20s for Kea to start.
+wait_for_kea 20
+if [ ${_WAIT_FOR_KEA} -eq 0 ]; then
+    printf "ERROR: timeout waiting for Kea to start.\n"
+    clean_exit 1
+fi
+
+# Check if it is still running. It could have terminated (e.g. as a result
+# of configuration failure).
+get_pids
+if [ ${_GET_PIDS_NUM} -ne 1 ]; then
+    printf "ERROR: expected one Kea process to be started. Found %d processes started.\n" ${_GET_PIDS_NUM}
+    clean_exit 1
+fi
+
+# Check in the log file, how many times server has been configured. It should
+# be just once on startup.
+get_reconfigs
+if [ ${_GET_RECONFIGS} -ne 1 ]; then
+    printf "ERROR: server hasn't been configured.\n"
+    clean_exit 1
+else
+    printf "Server successfully configured.\n"
+fi
+
+# Now use invalid configuration.
+create_config "${CONFIG_INVALID}"
+
+# Try to reconfigure by sending SIGHUP
+send_signal 1
+
+# The configuration should fail and the error message should be there.
+wait_for_message 10 "DHCP6_CONFIG_LOAD_FAIL" 1
+
+# After receiving SIGHUP the server should try to reconfigure itself.
+# The configuration provided is invalid so it should result in
+# reconfiguration failure but the server should still be running.
+get_reconfigs
+if [ ${_GET_RECONFIGS} -ne 1 ]; then
+    printf "ERROR: server has been reconfigured despite bogus configuration.\n"
+    clean_exit 1
+elif [ ${_GET_RECONFIG_ERRORS} -ne 1 ]; then
+    printf "ERROR: server did not report reconfiguration error despite attempt" \
+        " to configure it with invalid configuration.\n"
+    clean_exit 1
+fi
+
+# Make sure the server is still operational.
+get_pids
+if [ ${_GET_PIDS_NUM} -ne 1 ]; then
+    printf "ERROR: Kea process was killed when attempting reconfiguration.\n"
+    clean_exit 1
+fi
+
+# Restore the good configuration.
+create_config "${CONFIG}"
+
+# Reconfigure the server with SIGHUP.
+send_signal 1
+
+# There should be two occurrences of the DHCP6_CONFIG_COMPLETE messages.
+# Wait for it up to 10s.
+wait_for_message 10 "DHCP6_CONFIG_COMPLETE" 2
+
+# After receiving SIGHUP the server should get reconfigured and the
+# reconfiguration should be noted in the log file. We should now
+# have two configurations logged in the log file.
+if [ ${_WAIT_FOR_MESSAGE} -eq 0 ]; then
+    printf "ERROR: server hasn't been reconfigured.\n"
+    clean_exit 1
+else
+    printf "Server successfully reconfigured.\n"
+fi
+
+# Make sure the server is still operational.
+get_pids
+if [ ${_GET_PIDS_NUM} -ne 1 ]; then
+    printf "ERROR: Kea process was killed when attempting reconfiguration.\n"
+    clean_exit 1
+fi
+
+# All ok. Shut down Kea and exit.
+clean_exit 0

+ 110 - 0
src/bin/dhcp6/tests/dhcp6_shutdown_test.sh

@@ -0,0 +1,110 @@
+# 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.
+
+if [ $# -ne 2 ]; then
+    printf "USAGE: dhcp6_shutdown_test.sh <test_name> <signal_num>\n"
+    exit 1
+fi
+
+# Test name
+TEST_NAME=$1
+# Signal number to be used for this test.
+SIG_NUM=$2
+# Path to the temporary configuration file.
+CFG_FILE="test_config.json"
+# Path to the Kea log file.
+LOG_FILE="test.log"
+# Kea configuration to be stored in the configuration file.
+CONFIG="{
+    \"Dhcp6\":
+    {
+        \"interfaces\": [ ],
+        \"preferred-lifetime\": 3000,
+        \"valid-lifetime\": 4000,
+        \"renew-timer\": 1000,
+        \"rebind-timer\": 2000,
+        \"lease-database\":
+        {
+            \"type\": \"memfile\",
+            \"persist\": false
+        },
+        \"subnet6\": [
+        {
+            \"subnet\": \"2001:db8:1::/64\",
+            \"pool\": [ \"2001:db8:1::10-2001:db8:1::100\" ]
+        } ]
+    }
+}"
+
+# Set the location of the executable.
+BIN="b10-dhcp6"
+BIN_PATH=".."
+
+# Import common test library.
+. $(dirname $0)/../../../lib/testutils/dhcp_test_lib.sh
+
+# Log the start of the test and print test name.
+test_start
+# Remove dangling Kea instances and remove log files.
+cleanup
+# Create new configuration file.
+create_config "${CONFIG}"
+# Instruct Kea to log to the specific file.
+set_logger
+# Start Kea.
+start_kea
+# Wait up to 20s for Kea to start.
+wait_for_kea 20
+if [ ${_WAIT_FOR_KEA} -eq 0 ]; then
+    printf "ERROR: timeout waiting for Kea to start.\n"
+    clean_exit 1
+fi
+
+# Check if it is still running. It could have terminated (e.g. as a result
+# of configuration failure).
+get_pids
+if [ ${_GET_PIDS_NUM} -ne 1 ]; then
+    printf "ERROR: expected one Kea process to be started. Found %d processes started.\n" ${_GET_PIDS_NUM}
+    clean_exit 1
+fi
+
+# Check in the log file, how many times server has been configured. It should
+# be just once on startup.
+get_reconfigs
+if [ ${_GET_RECONFIGS} -ne 1 ]; then
+    printf "ERROR: server hasn't been configured.\n"
+    clean_exit 1
+else
+    printf "Server successfully configured.\n"
+fi
+
+# Send signal to Kea (SIGTERM, SIGINT etc.)
+send_signal ${SIG_NUM}
+
+# Wait up to 10s for the server's graceful shutdown. The graceful shut down
+# should be recorded in the log file with the appropriate message.
+wait_for_message 10 "DHCP6_SHUTDOWN" 1
+if [ ${_WAIT_FOR_MESSAGE} -eq 0 ]; then
+    printf "ERROR: Server did not record shutdown in the log.\n"
+    clean_exit 1
+fi
+
+# Server should have shut down.
+get_pids
+if [ ${_GET_PIDS_NUM} -ne 0 ]; then
+    printf "ERROR: Kea did not shut down after receiving signal.\n" ${_GET_PIDS_NUM}
+    clean_exit 1
+fi
+
+clean_exit 0

+ 16 - 0
src/bin/dhcp6/tests/dhcp6_sigint_test.sh

@@ -0,0 +1,16 @@
+# 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.
+
+# Run a test that sends SIGINT to Kea and checks if it shuts down gracefully.
+$(dirname $0)/dhcp6_shutdown_test.sh "Sigint" 2

+ 16 - 0
src/bin/dhcp6/tests/dhcp6_sigterm_test.sh

@@ -0,0 +1,16 @@
+# 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.
+
+# Run a test that sends SIGTERM to Kea and checks if it shuts down gracefully.
+$(dirname $0)/dhcp6_shutdown_test.sh "Sigterm" 15

+ 2 - 2
src/lib/cc/data.cc

@@ -736,8 +736,8 @@ Element::fromJSONFile(const std::string& file_name,
     if (!infile.is_open())
     {
         const char* error = strerror(errno);
-        isc_throw(InvalidOperation, "Failed to read file " << file_name
-                  << ",error:" << error);
+        isc_throw(InvalidOperation, "Failed to read file '" << file_name
+                  << "', error:" << error);
     }
 
     return (fromJSON(infile, file_name, preproc));

+ 5 - 1
src/lib/dhcpsrv/daemon.cc

@@ -24,10 +24,14 @@
 namespace isc {
 namespace dhcp {
 
+// This is an initial config file location.
+std::string Daemon::config_file_ = "";
+
 Daemon::Daemon() {
 }
 
-void Daemon::init(const std::string&) {
+void Daemon::init(const std::string& config_file) {
+    config_file_ = config_file;
 }
 
 void Daemon::cleanup() {

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

@@ -34,6 +34,16 @@ namespace dhcp {
 /// Dhcpv6Srv) in tests, without going through the hassles of implemeting stub
 /// methods.
 ///
+/// This class comprises a static object holding a location of the configuration
+/// file. The object must be static because it is instantiated by the signal
+/// handler functions, which are static by their nature. The signal handlers
+/// are used to reconfigure a running server and they need access to the
+/// configuration file location. They get this access by calling
+/// @c Daemon::getConfigFile function.
+///
+/// By default, the configuration file location is empty and its actual value
+/// is assigned to the static object in @c Daemon::init function.
+///
 /// @note Only one instance of this class is instantiated as it encompasses
 ///       the whole operation of the server.  Nothing, however, enforces the
 ///       singleton status of the object.
@@ -44,13 +54,11 @@ public:
     ///
     /// Currently it does nothing.
     Daemon();
-    
+
     /// @brief Initializes the server.
     ///
     /// Depending on the configuration backend, it establishes msgq session,
-    /// or reads the 
-    /// Creates session that will be used to receive commands and updated
-    /// configuration from cfgmgr (or indirectly from user via bindctl).
+    /// or reads the configuration file.
     ///
     /// Note: This function may throw to report enountered problems. It may
     /// also return false if the initialization was skipped. That may seem
@@ -62,6 +70,8 @@ public:
     /// decide that it is not needed and will shut down.
     ///
     /// @note this method may throw
+    ///
+    /// @param config_file Config file name (may be empty if unused).
     virtual void init(const std::string& config_file);
 
     /// @brief Performs final deconfiguration.
@@ -83,10 +93,20 @@ public:
     /// virtual destructor as well.
     virtual ~Daemon();
 
-    /// Initializez logger
+    /// @brief Returns config file name.
+    static std::string getConfigFile() {
+        return (config_file_);
+    }
+
+    /// Initializes logger
     ///
-    /// This method initializes logger. I
+    /// This method initializes logger.
     static void loggerInit(const char* log_name, bool verbose, bool stand_alone);
+
+private:
+
+    /// @brief Config file name or empty if config file not used.
+    static std::string config_file_;
 };
 
 }; // end of isc::dhcp namespace

+ 4 - 1
src/lib/testutils/Makefile.am

@@ -15,4 +15,7 @@ libkea_testutils_la_LIBADD  = $(top_builddir)/src/lib/asiolink/libkea-asiolink.l
 libkea_testutils_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
 endif
 
-EXTRA_DIST = portconfig.h socket_request.h
+# Include common libraries being used by shell-based tests.
+SHLIBS = dhcp_test_lib.sh
+
+EXTRA_DIST = portconfig.h socket_request.h $(SHLIBS)

+ 191 - 0
src/lib/testutils/dhcp_test_lib.sh

@@ -0,0 +1,191 @@
+# 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.
+
+# The following two parameters must to be specified in a script
+# including this library.
+# - BIN - Name of the Kea executable (excluding a path), e.g. b10-dhcp6
+# - BIN_PATH - Path to the Kea executable (excluding an executable name),
+#              e.g. ../
+
+# Begins a test by prining its name.
+# It requires the ${TEST_NAME} variable to hold the test name.
+test_start() {
+    printf "\nSTART TEST ${TEST_NAME}\n"
+}
+
+# Stores the configuration specified as a parameter in the configuration
+# file which name has been set in the ${CFG_FILE} variable.
+create_config() {
+    printf "Creating Kea configuration file: %s.\n" ${CFG_FILE}
+    printf "%b" ${1} > ${CFG_FILE}
+}
+
+# Sets Kea logger to write to the file specified by the global value
+# ${LOG_FILE}.
+set_logger() {
+    printf "Kea log will be stored in %s.\n" ${LOG_FILE}
+    export B10_LOGGER_DESTINATION=${LOG_FILE}
+}
+
+# Returns the number of running process pids and the list of pids.
+_GET_PIDS=     # Return value: holds space separated list of DHCPv6 pids.
+_GET_PIDS_NUM= # Return value: holds the number of DHCPv6 server pids.
+get_pids() {
+    _GET_PIDS=`ps axwwo pid,command | grep ${BIN} | grep -v grep | awk '{print $1}'`
+    _GET_PIDS_NUM=`printf "%s" "${_GET_PIDS}" | wc -w | awk '{print $1}'`
+}
+
+# Returns the number of occurrences of the Kea log message in the
+# log file.
+_GET_LOG_MESSAGES= # Holds the number of log message occurrences.
+get_log_messages() {
+    # Grep log file for the logger message occurrences.
+    _GET_LOG_MESSAGES=`grep -o ${1} ${LOG_FILE} | wc -w`
+    # Remove whitespaces.
+    ${_GET_LOG_MESSAGES##*[! ]}
+}
+
+# Returns the number of server configurations performed so far. Also
+# returns the number of configuration errors.
+_GET_RECONFIGS=        # Return value: number of configurations so far.
+_GET_RECONFIG_ERRORS=  # Return value: number of configuration errors.
+get_reconfigs() {
+    # Grep log file for DHCP6_CONFIG_COMPLETE occurences. There should
+    # be one occurence per (re)configuration.
+    _GET_RECONFIGS=`grep -o DHCP6_CONFIG_COMPLETE ${LOG_FILE} | wc -w`
+    # Grep log file for DHCP6_CONFIG_LOAD_FAIL to check for configuration
+    # failures.
+    _GET_RECONFIG_ERRORS=`grep -o DHCP6_CONFIG_LOAD_FAIL ${LOG_FILE} | wc -w`
+    # Remove whitespaces
+    ${_GET_RECONFIGS##*[! ]}
+    ${_GET_RECONFIG_ERRORS##*[! ]}
+}
+
+# Performs cleanup for a test.
+# It shuts down running Kea processes and removes temporary files.
+# The location of the log file and the configuration file should be set
+# in the ${LOG_FILE} and ${CFG_FILE} variables recpectively, prior to
+# calling this function.
+cleanup() {
+    get_pids
+    # Shut down running Kea processes.
+    for pid in ${_GET_PIDS}
+    do
+        printf "Shutting down Kea proccess having pid %d.\n" ${pid}
+        kill -9 ${pid}
+    done
+    # Remove temporary files.
+    rm -rf ${LOG_FILE}
+    rm -rf ${CFG_FILE}
+}
+
+# Exists the test in the clean way.
+# It peformes the cleanup and prints whether the test has passed or failed.
+# If a test fails, the Kea log is dumped.
+clean_exit() {
+    exit_code=${1}  # Exit code to be returned by the exit function.
+    if [ ${exit_code} -eq 0 ]; then
+        cleanup
+        printf "PASSED ${TEST_NAME}\n\n"
+    else
+        # Dump log file if exists for debugging purposes.
+        if [ -s ${LOG_FILE} ]; then
+            printf "Log file dump:\n"
+            cat ${LOG_FILE}
+        fi
+        cleanup
+        printf "FAILED ${TEST_NAME}\n\n"
+    fi
+    exit ${exit_code}
+}
+
+# Starts Kea process in background using a configuration file specified
+# in the global variable ${CFG_FILE}
+start_kea() {
+    printf "Running command %s.\n" "\"${BIN_PATH}/${BIN} -c ${CFG_FILE}\""
+    ${BIN_PATH}/$BIN -c ${CFG_FILE} &
+}
+
+# Waits for Kea to startup with timeout.
+# This function repeatedly checs if the Kea log file has been created
+# and is non-empty. If it is, the function assumes that Kea has started.
+# It doesn't check the contents of the log file though.
+# If the log file doesn't exist the function sleeps for a second and
+# checks again. This is repeated until timeout is reached or non-empty
+# log file is found. If timeout is reached, the function reports an
+# error.
+_WAIT_FOR_KEA=0  # Return value: Holds 0 if Kea hasn't started, 1 otherwise
+wait_for_kea() {
+    timeout=${1} # Desired timeout in seconds.
+    loops=0 # Loops counter
+    _WAIT_FOR_KEA=0
+    while [ ! -s ${LOG_FILE} ] && [ ${loops} -le ${timeout} ]; do
+        printf "."
+        sleep 1
+        loops=`expr $loops + 1`
+    done
+    printf "\n"
+    if [ ${loops} -le ${timeout} ]; then
+        _WAIT_FOR_KEA=1
+    fi
+}
+
+# Waits for a specific message to occur in the Kea log file.
+# This function is called when the test expects specific message
+# to show up in the log file as a result of some action that has
+# been taken. Typically, the test expects that the message
+# is logged when the SIGHUP or SIGTERM signal has been sent to the
+# Kea process.
+# This function waits a specified number of seconds for the number
+# of message occurrences to show up. If the expected number of
+# message doesn't occur, the error status is returned.
+_WAIT_FOR_MESSAGE=0  # Return value: holds 0 if the message hasn't occured,
+                     # 1 otherwise.
+wait_for_message() {
+    timeout=${1}     # Expecte timeout value in seconds.
+    message=${2}     # Expected message id.
+    occurrences=${3} # Number of expected occurrences.
+    loops=0          # Number of loops performed so far.
+    _WAIT_FOR_MESSAGE=0
+    # Check if log file exists and if we reached timeout.
+    while [ ! -s {LOG_FILE} ] && [ ${loops} -le ${timeout} ]; do
+        printf "."
+        # Check if the message has been logged.
+        get_log_messages ${message}
+        if [ ${_GET_LOG_MESSAGES} -eq ${occurrences} ]; then
+            printf "\n"
+            _WAIT_FOR_MESSAGE=1
+            return
+        fi
+        # Message not recorded. Keep going.
+        sleep 1
+        loops=`expr ${loops} + 1`
+    done
+    printf "\n"
+    # Timeout.
+}
+
+# Sends specified signal to the Kea process.
+send_signal() {
+    sig=${1}  # Signal number.
+    # Get Kea pid.
+    get_pids
+    if [ ${_GET_PIDS_NUM} -ne 1 ]; then
+        printf "ERROR: expected one Kea process to be started. Found %d processes started.\n" ${_GET_PIDS_NUM}
+        clean_exit 1
+    fi
+    printf "Sending signal ${sig} to Kea process (pid=%s).\n" ${_GET_PIDS}
+    # Actually send a signal.
+    kill -${sig} ${_GET_PIDS}
+}