]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1115] shutdown command exit-value arg, exit w/failure on db loss
authorThomas Markwalder <tmark@isc.org>
Fri, 28 Feb 2020 21:04:28 +0000 (16:04 -0500)
committerThomas Markwalder <tmark@isc.org>
Wed, 4 Mar 2020 11:59:52 +0000 (06:59 -0500)
Added exit-value argument to shutdown command.
kea-dhcpX servers now exit with EXIT_FAILURE status on db loss

src/lib/process/daemon.*
    Daemon::exit_value_,  new member with getter/setter

src/lib/process/d_controller.*
    DControllerBase::launch() - now returns getExitValue()
    DControllerBase::shutdownHandler() - uses exit-value argument to set exit
    value

src/lib/process/tests/daemon_unittest.cc
    TEST_F(DaemonTest, exitValue)  - new test

src/bin/agent/main.cc
    Use launch() return value for exit value.

src/bin/agent/tests/ca_controller_unittests.cc
    TEST_F(CtrlAgentControllerTest, shutdownExitValue)  - new test

src/bin/d2/main.cc
    Use launch() return value for exit value.

src/bin/d2/tests/d2_command_unittest.cc
    TEST_F(CtrlChannelD2Test, shutdownExitValue)  - new test

src/bin/dhcp4/ctrl_dhcp4_srv.*
    ControlledDhcpv4Srv::
        commandShutdownHandler() - handle exit-value argument
        shutdown(int exit_value) - added exit_value parameter
        dbReconnect() - call shutdown(EXIT_FAILURE)
        dbLostCallback() - call shutdown(EXIT_FAILURE)

src/bin/dhcp4/dhcp4_srv.*
    Dhcp4Srv::run() - returns int Daemon::exit_value instead of bool

src/bin/dhcp4/main.cc
    Use run() return value for exit value.

src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
    TEST_F(CtrlChannelDhcpv4SrvTest, commands) - revamped test

src/bin/dhcp6/ctrl_dhcp6_srv.*
    ControlledDhcpv6Srv::
        commandShutdownHandler() - use exit-value argument to set exit value
        shutdown(int exit_value) - added exit_value parameter
        dbReconnect() - call shutdown(EXIT_FAILURE)
        dbLostCallback() - call shutdown(EXIT_FAILURE)

src/bin/dhcp6/dhcp6_srv.*
    Dhcp6Srv::run() - returns int Daemon::exit_value instead of bool

src/bin/dhcp6/main.cc
    Use run() return value for exit value.

src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
    TEST_F(CtrlDhcpv6SrvTest, commands) - revamped test

21 files changed:
src/bin/agent/main.cc
src/bin/agent/tests/ca_controller_unittests.cc
src/bin/d2/main.cc
src/bin/d2/tests/d2_command_unittest.cc
src/bin/dhcp4/ctrl_dhcp4_srv.cc
src/bin/dhcp4/ctrl_dhcp4_srv.h
src/bin/dhcp4/dhcp4_srv.cc
src/bin/dhcp4/dhcp4_srv.h
src/bin/dhcp4/main.cc
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
src/bin/dhcp6/ctrl_dhcp6_srv.cc
src/bin/dhcp6/ctrl_dhcp6_srv.h
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/dhcp6_srv.h
src/bin/dhcp6/main.cc
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
src/lib/process/d_controller.cc
src/lib/process/d_controller.h
src/lib/process/daemon.cc
src/lib/process/daemon.h
src/lib/process/tests/daemon_unittest.cc

index be43065b2914200f1dd434276aa233b43b453e24..42117e5c3a3e82ae10ba8d38ef98bdd8cd145e78 100644 (file)
@@ -23,7 +23,7 @@ int main(int argc, char* argv[]) {
         DControllerBasePtr& controller = CtrlAgentController::instance();
 
         // 'false' value disables test mode.
-        controller->launch(argc, argv, false);
+        ret = controller->launch(argc, argv, false);
     } catch (const VersionMessage& ex) {
         std::string msg(ex.what());
         if (!msg.empty()) {
index dcda359c9793b85f983dfb827194c6cb3eb62d67..0c084daf9a8858da6e9b3ed17f2b737568bc49e1 100644 (file)
@@ -697,4 +697,56 @@ TEST_F(CtrlAgentControllerTest, statusGet) {
     EXPECT_GE(found_reload->intValue(), 0);
 }
 
+TEST_F(CtrlAgentControllerTest, shutdownExitValue) {
+    ASSERT_NO_THROW(initProcess());
+    EXPECT_TRUE(checkProcess());
+
+    // The framework available makes it very difficult to test the actual
+    // code as CtrlAgentController is not initialized the same way it is
+    // in production code. In particular, the way CtrlAgentController
+    // is initialized in tests does not call registerCommands().
+    // This is a crude workaround for this problem. Proper solution should
+    // be developed sooner rather than later.
+    const DControllerBasePtr& base = getController();
+    const CtrlAgentControllerPtr& ctrl
+        = boost::dynamic_pointer_cast<CtrlAgentController>(base);
+    ASSERT_TRUE(ctrl);
+    // Now clean up after ourselves.
+    ctrl->registerCommands();
+
+    // 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.
+    getController()->setConfigFile("testvalid.json");
+
+    // Ok, enough fooling around. Let's create a valid config.
+    ofstream f("testvalid.json", ios::trunc);
+    f << "{ \"Control-agent\": "
+      << string(valid_agent_config)
+      << " }" << endl;
+    f.close();
+
+    // Build and execute the command.
+
+    ConstElementPtr cmd = Element::fromJSON("{ \"command\": \"shutdown\"}");
+    ConstElementPtr params = Element::fromJSON("{ \"exit-value\": 77 }");
+    ConstElementPtr answer;
+    answer = CtrlAgentCommandMgr::instance().handleCommand("shutdown",
+                                                           params, cmd);
+
+    // Verify the reload was successful.
+    string expected = "[ { \"result\": 0, \"text\": "
+                      "\"Control Agent is shutting down\" } ]";
+
+    EXPECT_EQ(expected, answer->str());
+
+    int exit_value = ctrl->getExitValue();
+    EXPECT_EQ(77, exit_value);
+
+    // Remove the file.
+    ::remove("testvalid.json");
+
+    // Now clean up after ourselves.
+    ctrl->deregisterCommands();
+}
+
 }
index 7eb704467b4d0e72c7f5bb2a8726aaa3eb33429f..1f896fcb835141718cab113611bcfe12a8bb9627 100644 (file)
@@ -32,7 +32,7 @@ int main(int argc, char* argv[]) {
         DControllerBasePtr& controller = D2Controller::instance();
 
         // 'false' value disables test mode.
-        controller->launch(argc, argv, false);
+        ret = controller->launch(argc, argv, false);
     } catch (const VersionMessage& ex) {
         std::string msg(ex.what());
         if (!msg.empty()) {
index c6690d75e24602d1f3e02c16a4e9bf166f382937..e0eceafacbd3569f1cd9dd89ff5182905726f455 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2020 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
@@ -565,7 +565,7 @@ TEST_F(CtrlChannelD2Test, invalid) {
               response);
 }
 
-// Tests that the server properly responds to shtudown command.
+// Tests that the server properly responds to shutdown command.
 TEST_F(CtrlChannelD2Test, shutdown) {
     EXPECT_NO_THROW(createUnixChannelServer());
     string response;
@@ -573,6 +573,23 @@ TEST_F(CtrlChannelD2Test, shutdown) {
     sendUnixCommand("{ \"command\": \"shutdown\" }", response);
     EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutdown initiated, type is: normal\" }",
               response);
+    EXPECT_EQ(EXIT_SUCCESS, server_->getExitValue());
+}
+
+// Tests that the server sets exit value supplied as argument
+// to shutdown command.
+TEST_F(CtrlChannelD2Test, shutdownExitValue) {
+    EXPECT_NO_THROW(createUnixChannelServer());
+    string response;
+
+    sendUnixCommand("{ \"command\": \"shutdown\", "
+                    "\"arguments\": { \"exit-value\": 77 }}"
+                    , response);
+
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutdown initiated, type is: normal\" }",
+              response);
+
+    EXPECT_EQ(77, server_->getExitValue());
 }
 
 // This test verifies that the DHCP server handles version-get commands.
index 2b298dfbf6ccfb2ba3ac2d048e0b001c6e268b2c..8e00344e7ae539666f032adf97b9681635639ff5 100644 (file)
@@ -202,17 +202,32 @@ ControlledDhcpv4Srv::loadConfigFile(const std::string& file_name) {
 }
 
 ConstElementPtr
-ControlledDhcpv4Srv::commandShutdownHandler(const string&, ConstElementPtr) {
-    if (ControlledDhcpv4Srv::getInstance()) {
-        ControlledDhcpv4Srv::getInstance()->shutdown();
-    } else {
+ControlledDhcpv4Srv::commandShutdownHandler(const string&, ConstElementPtr args) {
+    if (!ControlledDhcpv4Srv::getInstance()) {
         LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING);
-        ConstElementPtr answer = isc::config::createAnswer(1,
-                                              "Shutdown failure.");
-        return (answer);
+        return(createAnswer(CONTROL_RESULT_ERROR, "Shutdown failure."));
     }
-    ConstElementPtr answer = isc::config::createAnswer(0, "Shutting down.");
-    return (answer);
+
+    int exit_value = 0;
+    if (args) {
+        // @todo Should we go ahead and shutdown even if the args are invalid?
+        if (args->getType() != Element::map) {
+            return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
+        }
+
+        ConstElementPtr param = args->get("exit-value");
+        if (param)  {
+            if (param->getType() != Element::integer) {
+                return (createAnswer(CONTROL_RESULT_ERROR,
+                                     "parameter 'exit-value' is not an integer"));
+            }
+
+            exit_value = param->intValue();
+        }
+    }
+
+    ControlledDhcpv4Srv::getInstance()->shutdown(exit_value);
+    return(createAnswer(CONTROL_RESULT_SUCCESS, "Shutting down."));
 }
 
 ConstElementPtr
@@ -957,9 +972,10 @@ ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_P
         boost::bind(&StatsMgr::statisticSetMaxSampleCountAllHandler, _1, _2));
 }
 
-void ControlledDhcpv4Srv::shutdown() {
-    io_service_.stop(); // Stop ASIO transmissions
-    Dhcpv4Srv::shutdown(); // Initiate DHCPv4 shutdown procedure.
+void ControlledDhcpv4Srv::shutdown(int exit_value) {
+    setExitValue(exit_value);
+    io_service_.stop();       // Stop ASIO transmissions
+    Dhcpv4Srv::shutdown();    // Initiate DHCPv4 shutdown procedure.
 }
 
 ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
@@ -1065,9 +1081,10 @@ ControlledDhcpv4Srv::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
         db_reconnect_ctl.reset();
     } else {
         if (!db_reconnect_ctl->checkRetries()) {
+            // We're out of retries, log it and initiate shutdown.
             LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_RETRIES_EXHAUSTED)
             .arg(db_reconnect_ctl->maxRetries());
-            shutdown();
+            shutdown(EXIT_FAILURE);
             return;
         }
 
@@ -1099,14 +1116,14 @@ ControlledDhcpv4Srv::dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
         return (false);
     }
 
-    // If reconnect isn't enabled or we're out of retries,
-    // log it, schedule a shutdown,  and return false
+    // If reconnect isn't enabled log it,
+    // initiate a shutdown and return false.
     if (!db_reconnect_ctl->retriesLeft() ||
         !db_reconnect_ctl->retryInterval()) {
         LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_DISABLED)
             .arg(db_reconnect_ctl->retriesLeft())
             .arg(db_reconnect_ctl->retryInterval());
-        ControlledDhcpv4Srv::processCommand("shutdown", ConstElementPtr());
+        shutdown(EXIT_FAILURE);
         return(false);
     }
 
index b50479681a72e8607340271ac298fe998f00ec03..d1f8107751d09de7a05eb6eae173a9eb3807566a 100644 (file)
@@ -63,7 +63,8 @@ public:
     void cleanup();
 
     /// @brief Initiates shutdown procedure for the whole DHCPv4 server.
-    void shutdown();
+    /// @param exit_value integer value to the process should exit with.
+    void shutdown(int exit_value);
 
     /// @brief Command processor
     ///
index d5b2c34846b6448b357cb887e942527fb5b9ef6f..6d202136319a379561a468e4307dd6efa03abfad 100644 (file)
@@ -771,7 +771,7 @@ Dhcpv4Srv::sendPacket(const Pkt4Ptr& packet) {
     IfaceMgr::instance().send(packet);
 }
 
-bool
+int
 Dhcpv4Srv::run() {
 #ifdef ENABLE_AFL
     // Set up structures needed for fuzzing.
@@ -805,7 +805,7 @@ Dhcpv4Srv::run() {
     // destroying the thread pool
     MultiThreadingMgr::instance().apply(false, 0);
 
-    return (true);
+    return (getExitValue());
 }
 
 void
index 3af4f97eb5f48252242bd77817df725c2edd7ee3..4ade9666919f95bcf8b478be05af5722ce95be52 100644 (file)
@@ -272,8 +272,8 @@ public:
     /// Main server processing loop. Call the processing step routine
     /// until shut down.
     ///
-    /// @return true, if being shut down gracefully, never fail.
-    bool run();
+    /// @return The value returned by @c Daemon::getExitValue().
+    int run();
 
     /// @brief Main server processing step.
     ///
@@ -319,7 +319,6 @@ public:
     void processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp,
                        bool allow_packet_park = true);
 
-
     /// @brief Instructs the server to shut down.
     void shutdown();
 
index b8cd9a52052f5817dcc20611ed4cdbbcb194575b..9d72cebaab318d25a992462dd564c87b0e4fc73d 100644 (file)
@@ -269,7 +269,7 @@ main(int argc, char* argv[]) {
         LOG_INFO(dhcp4_logger, DHCP4_STARTED).arg(VERSION);
 
         // And run the main loop of the server.
-        server.run();
+        ret = server.run();
 
         LOG_INFO(dhcp4_logger, DHCP4_SHUTDOWN);
 
index 8930bc2aec799912cd7f8a34e574e86ce34f71b1..b3533d99c92d52ff28abd0d32ff0d82d3684fb82 100644 (file)
@@ -401,15 +401,19 @@ TEST_F(CtrlChannelDhcpv4SrvTest, commands) {
     result = ControlledDhcpv4Srv::processCommand("shutdown", params);
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
+    // Exit value should default to 0.
+    EXPECT_EQ(0, server_->getExitValue());
 
-    const pid_t pid(getpid());
-    ConstElementPtr x(new isc::data::IntElement(pid));
-    params->set("pid", x);
+    // Case 3: send shutdown command with exit-value parameter.
+    ConstElementPtr x(new isc::data::IntElement(77));
+    params->set("exit-value", x);
 
-    // Case 3: send shutdown command with 1 parameter: pid
     result = ControlledDhcpv4Srv::processCommand("shutdown", params);
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
+
+    // Exit value should match.
+    EXPECT_EQ(77, server_->getExitValue());
 }
 
 // Check that the "libreload" command will reload libraries
index 69cce5d4f06e667a195c4f17d9f04e6dcfc64697..e8bb15af05f0bf0fdc2d64f3f5377c0bd0285480 100644 (file)
@@ -205,16 +205,33 @@ void ControlledDhcpv6Srv::cleanup() {
 }
 
 ConstElementPtr
-ControlledDhcpv6Srv::commandShutdownHandler(const string&, ConstElementPtr) {
-    if (ControlledDhcpv6Srv::getInstance()) {
-        ControlledDhcpv6Srv::getInstance()->shutdown();
-    } else {
+ControlledDhcpv6Srv::commandShutdownHandler(const string&, ConstElementPtr args) {
+
+    if (!ControlledDhcpv6Srv::getInstance()) {
         LOG_WARN(dhcp6_logger, DHCP6_NOT_RUNNING);
-        ConstElementPtr answer = isc::config::createAnswer(1, "Shutdown failure.");
-        return (answer);
+        return(createAnswer(CONTROL_RESULT_ERROR, "Shutdown failure."));
     }
-    ConstElementPtr answer = isc::config::createAnswer(0, "Shutting down.");
-    return (answer);
+
+    int exit_value = 0;
+    if (args) {
+        // @todo Should we go ahead and shutdown even if the args are invalid?
+        if (args->getType() != Element::map) {
+            return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
+        }
+
+        ConstElementPtr param = args->get("exit-value");
+        if (param)  {
+            if (param->getType() != Element::integer) {
+                return (createAnswer(CONTROL_RESULT_ERROR,
+                                     "parameter 'exit-value' is not an integer"));
+            }
+
+            exit_value = param->intValue();
+        }
+    }
+
+    ControlledDhcpv6Srv::getInstance()->shutdown(exit_value);
+    return(createAnswer(CONTROL_RESULT_SUCCESS, "Shutting down."));
 }
 
 ConstElementPtr
@@ -977,8 +994,9 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t server_port,
         boost::bind(&StatsMgr::statisticSetMaxSampleCountAllHandler, _1, _2));
 }
 
-void ControlledDhcpv6Srv::shutdown() {
-    io_service_.stop(); // Stop ASIO transmissions
+void ControlledDhcpv6Srv::shutdown(int exit_value) {
+    setExitValue(exit_value);
+    io_service_.stop();    // Stop ASIO transmissions
     Dhcpv6Srv::shutdown(); // Initiate DHCPv6 shutdown procedure.
 }
 
@@ -1085,9 +1103,10 @@ ControlledDhcpv6Srv::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
         db_reconnect_ctl.reset();
     } else {
         if (!db_reconnect_ctl->checkRetries()) {
+            // We're out of retries, log it and initiate shutdown.
             LOG_ERROR(dhcp6_logger, DHCP6_DB_RECONNECT_RETRIES_EXHAUSTED)
             .arg(db_reconnect_ctl->maxRetries());
-            shutdown();
+            shutdown(EXIT_FAILURE);
             return;
         }
 
@@ -1119,14 +1138,13 @@ ControlledDhcpv6Srv::dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
         return (false);
     }
 
-    // If reconnect isn't enabled or we're out of retries,
-    // log it, schedule a shutdown,  and return false
+    // If reconnect isn't enabled log it and initiate a shutdown.
     if (!db_reconnect_ctl->retriesLeft() ||
         !db_reconnect_ctl->retryInterval()) {
         LOG_INFO(dhcp6_logger, DHCP6_DB_RECONNECT_DISABLED)
             .arg(db_reconnect_ctl->retriesLeft())
             .arg(db_reconnect_ctl->retryInterval());
-        ControlledDhcpv6Srv::processCommand("shutdown", ConstElementPtr());
+        shutdown(EXIT_FAILURE);
         return(false);
     }
 
index 6061630b334d36d1c6b904cd5667bc671249f348..911eb01908998a2946988316cfc67f88d20f09ee 100644 (file)
@@ -63,7 +63,8 @@ public:
     void cleanup();
 
     /// @brief Initiates shutdown procedure for the whole DHCPv6 server.
-    void shutdown();
+    /// @param exit_value integer value to the process should exit with.
+    virtual void shutdown(int exit_value);
 
     /// @brief Command processor
     ///
index 1f28dd4a12a6e40a1ce54239da5a8c467c503f1e..5aa4321e39e8e33b079ad1738fe38685c8f5a7c2 100644 (file)
@@ -442,7 +442,7 @@ Dhcpv6Srv::initContext(const Pkt6Ptr& pkt,
     evaluateClasses(pkt, true);
 }
 
-bool Dhcpv6Srv::run() {
+int Dhcpv6Srv::run() {
 #ifdef ENABLE_AFL
     // Set up structures needed for fuzzing.
     Fuzz fuzzer(6, server_port_);
@@ -475,7 +475,7 @@ bool Dhcpv6Srv::run() {
     // destroying the thread pool
     MultiThreadingMgr::instance().apply(false, 0);
 
-    return (true);
+    return (getExitValue());
 }
 
 void Dhcpv6Srv::run_one() {
index 59fa57f13dda79ce16c6f1b5d895f10e77fe7257..b1716340ace164a7c919b447c37c2dc814049429 100644 (file)
@@ -139,8 +139,8 @@ public:
     /// Main server processing loop. Call the processing step routine
     /// until shut down.
     ///
-    /// @return true, if being shut down gracefully, never fail.
-    bool run();
+    /// @return The value returned by @c Daemon::getExitValue().
+    int run();
 
     /// @brief Main server processing step.
     ///
index 893107d84f18a728a58d661b01c4bc6694e06989..b4fde4cbd4b5cbba7489aa721288ddbf7eee7755 100644 (file)
@@ -269,7 +269,7 @@ main(int argc, char* argv[]) {
         LOG_INFO(dhcp6_logger, DHCP6_STARTED).arg(VERSION);
 
         // And run the main loop of the server.
-        server.run();
+        ret = server.run();
 
         LOG_INFO(dhcp6_logger, DHCP6_SHUTDOWN);
 
index e81065716fe5f2e6f721edec47ed01ba4324133c..3a9259e4a54a1811596968404d59573f481603c6 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2020 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
@@ -419,14 +419,16 @@ TEST_F(CtrlDhcpv6SrvTest, commands) {
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
 
-    const pid_t pid(getpid());
-    ConstElementPtr x(new isc::data::IntElement(pid));
-    params->set("pid", x);
+    // Case 3: send shutdown command with exit-value parameter.
+    ConstElementPtr x(new isc::data::IntElement(77));
+    params->set("exit-value", x);
 
-    // Case 3: send shutdown command with 1 parameter: pid
     result = ControlledDhcpv6Srv::processCommand("shutdown", params);
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
+
+    // Exit value should match.
+    EXPECT_EQ(77, srv->getExitValue());
 }
 
 // Check that the "libreload" command will reload libraries
index 83db472b94f5e9afb6366e4a0e0806c01279d749..bbd5a3c717edb372f89da2d8065a7d82cf6a95d7 100644 (file)
@@ -53,7 +53,7 @@ DControllerBase::parseFile(const std::string&) {
     return (elements);
 }
 
-void
+int
 DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
 
     // Step 1 is to parse the command line arguments.
@@ -69,7 +69,7 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
 
     if (isCheckOnly()) {
         checkConfigOnly();
-        return;
+        return(EXIT_SUCCESS);
     }
 
     // It is important that we set a default logger name because this name
@@ -154,6 +154,8 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
     // All done, so bail out.
     LOG_INFO(dctl_logger, DCTL_SHUTDOWN)
         .arg(app_name_).arg(getpid()).arg(VERSION);
+
+    return(getExitValue());
 }
 
 void
@@ -682,6 +684,26 @@ DControllerBase::shutdownHandler(const std::string&, ConstElementPtr args) {
     // a custom command supported by the derivation.  If that
     // doesn't pan out either, than send to it the application
     // as it may be supported there.
+
+    int exit_value = EXIT_SUCCESS;
+    if (args) {
+        // @todo Should we go ahead and shutdown even if the args are invalid?
+        if (args->getType() != Element::map) {
+            return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
+        }
+
+        ConstElementPtr param = args->get("exit-value");
+        if (param)  {
+            if (param->getType() != Element::integer) {
+                return (createAnswer(CONTROL_RESULT_ERROR,
+                                     "parameter 'exit-value' is not an integer"));
+            }
+
+            exit_value = param->intValue();
+        }
+    }
+
+    setExitValue(exit_value);
     return (shutdownProcess(args));
 }
 
index 2b9849a87dad69daaca5ce36c80f8e8e3fc6fd6a..072963890ae96b8d883499808fa774ff1aac6ff2 100644 (file)
@@ -149,7 +149,7 @@ public:
     /// process object.
     /// ProcessRunError - A fatal error occurred while in the application
     /// process event loop.
-    virtual void launch(int argc, char* argv[], const bool test_mode);
+    virtual int launch(int argc, char* argv[], const bool test_mode);
 
     /// @brief Instance method invoked by the configuration event handler and
     /// which processes the actual configuration update.  Provides behavioral
index 4ca5d0d4168ae102347e3c206f8999b4093a0891..dce6ee80868b9c9bcee533ca348a1c3b8fa0e10a 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2020 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
@@ -37,7 +37,8 @@ std::string Daemon::default_logger_name_("kea");
 
 Daemon::Daemon()
     : signal_set_(), signal_handler_(), config_file_(""),
-      pid_file_dir_(DATA_DIR), pid_file_(), am_file_author_(false) {
+      pid_file_dir_(DATA_DIR), pid_file_(), am_file_author_(false),
+      exit_value_(EXIT_SUCCESS) {
 
     // The pid_file_dir can be overridden via environment variable
     // This is primarily intended to simplify testing
@@ -58,7 +59,6 @@ void Daemon::cleanup() {
 }
 
 void Daemon::shutdown() {
-
 }
 
 void Daemon::handleSignal() {
index d8786ff6e61728010a2bae192ba0ee21493fab78..0585c1b881b681d8a6718a28e734cec3edf6f2a6 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2020 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
@@ -72,8 +72,8 @@ public:
     /// configuration updates as it is in final stages of shutdown.
     virtual void cleanup();
 
-    /// @brief Initiates shutdown procedure for the whole DHCPv6 server.
-    virtual void shutdown();
+    /// @brief Initiates shutdown procedure for the whole server.
+    void shutdown();
 
     /// @brief Initializes logger
     ///
@@ -230,6 +230,18 @@ public:
         default_logger_name_ = logger;
     }
 
+    /// @brief Fetches the exit value.
+    int getExitValue() {
+        return (exit_value_);
+    }
+
+    /// @brief Sets the exit value.
+    ///
+    /// @param value new exit value.
+    void setExitValue(int value) {
+        exit_value_ = value;
+    }
+
 protected:
 
     /// @brief Invokes handler for the next received signal.
@@ -286,6 +298,10 @@ private:
 
     /// @brief Flag indicating if this instance created the file
     bool am_file_author_;
+
+    /// @brief Exit value the process should use.  Typically set
+    /// by the application during a controlled shutdown.
+    int exit_value_;
 };
 
 }; // end of isc::dhcp namespace
index 666cedaddab2108718c5b6372ec4f20af97d8f0a..773cb75b35ada62244c34843dab86f6e451bb495 100644 (file)
@@ -362,6 +362,13 @@ TEST_F(DaemonTest, parsingConsoleOutput) {
     EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[0].output_);
 }
 
+TEST_F(DaemonTest, exitValue) {
+    DaemonImpl instance;
+
+    EXPECT_EQ(EXIT_SUCCESS, instance.getExitValue());
+    instance.setExitValue(77);
+    EXPECT_EQ(77, instance.getExitValue());
+}
 
 
 // More tests will appear here as we develop Daemon class.