Browse Source

[3406] Implemented dynamic reconfiguration when SIGHUP is received.

Marcin Siodelski 11 years ago
parent
commit
ba9bed21a4

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

@@ -133,7 +133,10 @@ bundyConfigHandler(ConstElementPtr new_config) {
 }
 }
 
 
 void
 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
     // This is Bundy configuration backed. It established control session
     // that is used to connect to Bundy framework.
     // that is used to connect to Bundy framework.
     //
     //

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

@@ -33,6 +33,7 @@
 
 
 #include <cassert>
 #include <cassert>
 #include <iostream>
 #include <iostream>
+#include <signal.h>
 #include <string>
 #include <string>
 #include <vector>
 #include <vector>
 
 
@@ -45,11 +46,18 @@ using namespace isc::log;
 using namespace isc::util;
 using namespace isc::util;
 using namespace std;
 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
     // This is a configuration backend implementation that reads the
     // configuration from a JSON file.
     // configuration from a JSON file.
 
 
@@ -61,7 +69,7 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
     try {
     try {
         if (file_name.empty()) {
         if (file_name.empty()) {
             // Basic sanity check: file name must not be 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.");
                       "use -c command line option.");
         }
         }
 
 
@@ -71,7 +79,7 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
         if (!json) {
         if (!json) {
             LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
             LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
                 .arg("Config file " + file_name + " missing or empty.");
                 .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);
                       + file_name);
         }
         }
 
 
@@ -81,16 +89,16 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
         if (!dhcp6) {
         if (!dhcp6) {
             LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
             LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
                 .arg("Config file " + file_name + " does not include 'Dhcp6' entry.");
                 .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);
                       + file_name);
         }
         }
 
 
         // Use parsed JSON structures to configure the server
         // Use parsed JSON structures to configure the server
-        result = processCommand("config-reload", dhcp6);
+        result = ControlledDhcpv6Srv::processCommand("config-reload", dhcp6);
 
 
     }  catch (const std::exception& ex) {
     }  catch (const std::exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
         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);
                   + file_name);
     }
     }
 
 
@@ -114,12 +122,42 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
             reason = string(" (") + comment->stringValue() + string(")");
             reason = string(" (") + comment->stringValue() + string(")");
         }
         }
         LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(reason);
         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.
+///
+/// Currently, this function handles SIGHUP signal only. When this signal
+/// is received it triggeres DHCSP server reconfiguration.
+///
+/// @param signo Signal number received.
+void signalHandler(int signo) {
+    if (signo == SIGHUP) {
+        configure(ControlledDhcpv6Srv::getInstance()->getConfigFile());
+    }
+}
+
+}
+
+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
     // We don't need to call openActiveSockets() or startD2() as these
     // methods are called in processConfig() which is called by
     // methods are called in processConfig() which is called by
     // processCommand("reload-config", ...)
     // processCommand("reload-config", ...)
+
+    // Set signal handler function for SIGHUP. When the SIGHUP is received
+    // by the process the server reconfiguration will be triggered.
+    signal(SIGHUP, signalHandler);
 }
 }
 
 
 void ControlledDhcpv6Srv::cleanup() {
 void ControlledDhcpv6Srv::cleanup() {

+ 6 - 2
src/bin/dhcp6/tests/Makefile.am

@@ -1,6 +1,11 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = dhcp6_test.py
 PYTESTS = dhcp6_test.py
-SHTESTS = dhcp6_reconfigure_test.sh
+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
+endif
 EXTRA_DIST = $(PYTESTS) $(SHTESTS)
 EXTRA_DIST = $(PYTESTS) $(SHTESTS)
 
 
 # Explicitly specify paths to dynamic libraries required by loadable python
 # Explicitly specify paths to dynamic libraries required by loadable python
@@ -49,7 +54,6 @@ TESTS_ENVIRONMENT = \
         $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
         $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
 
 
 TESTS =
 TESTS =
-TEST_EXTENSIONS = .sh
 if HAVE_GTEST
 if HAVE_GTEST
 # Build shared libraries for testing. The libtool way to create a shared library
 # Build shared libraries for testing. The libtool way to create a shared library
 # is to specify "-avoid-version -export-dynamic -module" in the library LDFLAGS
 # is to specify "-avoid-version -export-dynamic -module" in the library LDFLAGS

+ 45 - 2
src/bin/dhcp6/tests/dhcp6_reconfigure_test.sh

@@ -12,6 +12,8 @@
 # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 # PERFORMANCE OF THIS SOFTWARE.
 # PERFORMANCE OF THIS SOFTWARE.
 
 
+# Test name
+TEST_NAME="DynamicReconfiguration"
 # Name of the Kea executable.
 # Name of the Kea executable.
 BIN="../b10-dhcp6"
 BIN="../b10-dhcp6"
 # Path to the temporary configuration file.
 # Path to the temporary configuration file.
@@ -54,13 +56,33 @@ cleanup() {
         printf "Shutting down Kea proccess having pid %d.\n" ${pid}
         printf "Shutting down Kea proccess having pid %d.\n" ${pid}
         kill -9 ${pid}
         kill -9 ${pid}
     done
     done
+    rm -rf ${LOG_FILE}
 }
 }
 
 
 cleanexit() {
 cleanexit() {
-    cleanup
+    if [ $1 -eq 0 ]; then
+        cleanup
+        printf "PASSED ${TEST_NAME}\n\n"
+    else
+        printf "Log file dump:\n"
+        cat ${LOG_FILE}
+        cleanup
+        printf "FAILED ${TEST_NAME}\n\n"
+    fi
     exit $1
     exit $1
 }
 }
 
 
+_GETRECONFIGS=
+getreconfigs() {
+    # Grep log file for DHCP6_CONFIG_COMPLETE occurences. There should
+    # be one occurence per (re)configuration.
+    _GETRECONFIGS=`grep -o DHCP6_CONFIG_COMPLETE ${LOG_FILE} | wc -w`
+    # Remove whitespace
+    ${_GETRECONFIGS##*[! ]}
+}
+
+printf "\nSTART TEST ${TEST_NAME}\n"
+
 printf "Creating Kea configuration file: %s.\n" ${CFG_FILE}
 printf "Creating Kea configuration file: %s.\n" ${CFG_FILE}
 printf "%b" ${CONFIG} > ${CFG_FILE}
 printf "%b" ${CONFIG} > ${CFG_FILE}
 
 
@@ -84,6 +106,16 @@ if [ ${_GETPIDS2} -ne 1 ]; then
     cleanexit 1
     cleanexit 1
 fi
 fi
 
 
+# Check in the log file, how many times server has been configured. It should
+# be just once on startup.
+getreconfigs
+if [ ${_GETRECONFIGS} -ne 1 ]; then
+    printf "ERROR: server hasn't been configured.\n"
+    cleanexit 1
+else
+    printf "Server successfully configured\n"
+fi
+
 # Reconfigure the server with SIGUP.
 # Reconfigure the server with SIGUP.
 printf "Sending SIGUP to Kea process (pid=%s) to reconfigure the server.\n" ${_GETPIDS1}
 printf "Sending SIGUP to Kea process (pid=%s) to reconfigure the server.\n" ${_GETPIDS1}
 kill -1 ${_GETPIDS1}
 kill -1 ${_GETPIDS1}
@@ -92,7 +124,18 @@ kill -1 ${_GETPIDS1}
 # didn't work.
 # didn't work.
 sleep 1
 sleep 1
 
 
-# Make sure it is still operational.
+# 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.
+getreconfigs
+if [ ${_GETRECONFIGS} -ne 2 ]; then
+    printf "ERROR: server hasn't been reconfigured.\n"
+    cleanexit 1
+else
+    printf "Server successfully configured\n"
+fi
+
+# Make sure the server is still operational.
 getpids
 getpids
 if [ ${_GETPIDS2} -ne 1 ]; then
 if [ ${_GETPIDS2} -ne 1 ]; then
     printf "ERROR: Kea process was killed when attempting reconfiguration.\n"
     printf "ERROR: Kea process was killed when attempting reconfiguration.\n"

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

@@ -736,8 +736,8 @@ Element::fromJSONFile(const std::string& file_name,
     if (!infile.is_open())
     if (!infile.is_open())
     {
     {
         const char* error = strerror(errno);
         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));
     return (fromJSON(infile, file_name, preproc));

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

@@ -24,10 +24,14 @@
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
+// This is an initial config file location.
+std::string Daemon::config_file_ = "";
+
 Daemon::Daemon() {
 Daemon::Daemon() {
 }
 }
 
 
-void Daemon::init(const std::string&) {
+void Daemon::init(const std::string& config_file) {
+    config_file_ = config_file;
 }
 }
 
 
 void Daemon::cleanup() {
 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
 /// Dhcpv6Srv) in tests, without going through the hassles of implemeting stub
 /// methods.
 /// 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
 /// @note Only one instance of this class is instantiated as it encompasses
 ///       the whole operation of the server.  Nothing, however, enforces the
 ///       the whole operation of the server.  Nothing, however, enforces the
 ///       singleton status of the object.
 ///       singleton status of the object.
@@ -44,13 +54,11 @@ public:
     ///
     ///
     /// Currently it does nothing.
     /// Currently it does nothing.
     Daemon();
     Daemon();
-    
+
     /// @brief Initializes the server.
     /// @brief Initializes the server.
     ///
     ///
     /// Depending on the configuration backend, it establishes msgq session,
     /// 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
     /// Note: This function may throw to report enountered problems. It may
     /// also return false if the initialization was skipped. That may seem
     /// 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.
     /// decide that it is not needed and will shut down.
     ///
     ///
     /// @note this method may throw
     /// @note this method may throw
+    ///
+    /// @param config_file Config file name (may be empty if unused).
     virtual void init(const std::string& config_file);
     virtual void init(const std::string& config_file);
 
 
     /// @brief Performs final deconfiguration.
     /// @brief Performs final deconfiguration.
@@ -83,10 +93,20 @@ public:
     /// virtual destructor as well.
     /// virtual destructor as well.
     virtual ~Daemon();
     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);
     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
 }; // end of isc::dhcp namespace