Browse Source

[3405] Install signal handlers in DHCPv4 server.

Marcin Siodelski 11 years ago
parent
commit
db9b5842fc
3 changed files with 100 additions and 20 deletions
  1. 8 0
      src/bin/dhcp4/dhcp4_messages.mes
  2. 12 0
      src/bin/dhcp4/dhcp4_srv.cc
  3. 80 20
      src/bin/dhcp4/kea_controller.cc

+ 8 - 0
src/bin/dhcp4/dhcp4_messages.mes

@@ -97,6 +97,14 @@ This error message is logged when the attempt to compute DHCID for a specified
 lease has failed. The lease details and reason for failure is logged in the
 message.
 
+% DHCP4_DYNAMIC_RECONFIGURATION initate server reconfiguration using file: %1, after receiving SIGHUP signal
+This is the info message logged when the DHCPv4 server starts reconfiguration
+as a result of receiving SIGHUP signal.
+
+% DHCP4_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.
+
 % DHCP4_EMPTY_HOSTNAME received empty hostname from the client, skipping processing of this option
 This debug message is issued when the server received an empty Hostname option
 from a client. Server does not process empty Hostname options and therefore

+ 12 - 0
src/bin/dhcp4/dhcp4_srv.cc

@@ -175,6 +175,18 @@ Dhcpv4Srv::run() {
             LOG_ERROR(dhcp4_logger, DHCP4_PACKET_RECEIVE_FAIL).arg(e.what());
         }
 
+        // Handle next signal received by the process. It must be called after
+        // an attempt to receive a packet to properly handle server shut down.
+        // The SIGTERM or SIGINT will be received prior to, or during execution
+        // of select() (select is invoked by recivePacket()). When that happens,
+        // select will be interrupted. The signal handler will be invoked
+        // immediately after select(). The handler will set the shutdown flag
+        // and cause the process to terminate before the next select() function
+        // is called. If the function was called before receivePacket the
+        // process could wait up to the duration of timeout of select() to
+        // terminate.
+        handleSignal();
+
         // Timeout may be reached or signal received, which breaks select()
         // with no reception ocurred
         if (!query) {

+ 80 - 20
src/bin/dhcp4/kea_controller.cc

@@ -25,11 +25,18 @@ using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace std;
 
-namespace isc {
-namespace dhcp {
-
-void
-ControlledDhcpv4Srv::init(const std::string& file_name) {
+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.
 
@@ -41,8 +48,8 @@ ControlledDhcpv4Srv::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 "
-                      "use -c command line option.");
+            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
@@ -51,8 +58,8 @@ ControlledDhcpv4Srv::init(const std::string& file_name) {
         if (!json) {
             LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
                 .arg("Config file " + file_name + " missing or empty.");
-            isc_throw(BadValue, "Unable to process JSON configuration file:"
-                      + file_name);
+            isc_throw(isc::BadValue, "Unable to process JSON configuration"
+                      " file: " << file_name);
         }
 
         // Get Dhcp4 component from the config
@@ -60,29 +67,30 @@ ControlledDhcpv4Srv::init(const std::string& file_name) {
 
         if (!dhcp4) {
             LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
-                .arg("Config file " + file_name + " does not include 'Dhcp4' entry.");
-            isc_throw(BadValue, "Unable to process JSON configuration file:"
-                      + file_name);
+                .arg("Config file " + file_name + " does not include 'Dhcp4'"
+                     " entry.");
+            isc_throw(isc::BadValue, "Unable to process JSON configuration"
+                      " file: " << file_name);
         }
 
         // Use parsed JSON structures to configure the server
-        result = processCommand("config-reload", dhcp4);
+        result = ControlledDhcpv4Srv::processCommand("config-reload", dhcp4);
 
     }  catch (const std::exception& ex) {
         LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
-        isc_throw(BadValue, "Unable to process JSON configuration file:"
-                  + file_name);
+        isc_throw(isc::BadValue, "Unable to process JSON configuration file: "
+                  << file_name);
     }
 
     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.
+        // but as the configureDhcp4Server returns a pointer, it is
+        // theoretically possible that it will return NULL.
         LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
             .arg("Configuration failed: Undefined result of processCommand("
                  "config-reload, " + file_name + ")");
-        isc_throw(BadValue, "Configuration failed: Undefined result of "
-                  "processCommand('config-reload', " + file_name + ")");
+        isc_throw(isc::BadValue, "Configuration failed: Undefined result of "
+                  "processCommand('config-reload', " << file_name << ")");
     }
 
     // Now check is the returned result is successful (rcode=0) or not
@@ -95,12 +103,64 @@ ControlledDhcpv4Srv::init(const std::string& file_name) {
             reason = string(" (") + comment->stringValue() + string(")");
         }
         LOG_ERROR(dhcp4_logger, DHCP4_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 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) {
+    // 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_set_.reset(new isc::util::io::SignalSet(SIGINT, SIGHUP, SIGTERM));
+    // Set the pointer to the handler function.
+    signal_handler_ = signalHandler;
+
 }
 
 void ControlledDhcpv4Srv::cleanup() {