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
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()) {
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();
+}
+
}
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()) {
-// 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
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;
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.
}
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
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() {
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;
}
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);
}
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
///
IfaceMgr::instance().send(packet);
}
-bool
+int
Dhcpv4Srv::run() {
#ifdef ENABLE_AFL
// Set up structures needed for fuzzing.
// destroying the thread pool
MultiThreadingMgr::instance().apply(false, 0);
- return (true);
+ return (getExitValue());
}
void
/// 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.
///
void processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp,
bool allow_packet_park = true);
-
/// @brief Instructs the server to shut down.
void shutdown();
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);
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
}
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
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.
}
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;
}
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);
}
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
///
evaluateClasses(pkt, true);
}
-bool Dhcpv6Srv::run() {
+int Dhcpv6Srv::run() {
#ifdef ENABLE_AFL
// Set up structures needed for fuzzing.
Fuzz fuzzer(6, server_port_);
// destroying the thread pool
MultiThreadingMgr::instance().apply(false, 0);
- return (true);
+ return (getExitValue());
}
void Dhcpv6Srv::run_one() {
/// 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.
///
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);
-// 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
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
return (elements);
}
-void
+int
DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
// Step 1 is to parse the command line arguments.
if (isCheckOnly()) {
checkConfigOnly();
- return;
+ return(EXIT_SUCCESS);
}
// It is important that we set a default logger name because this name
// All done, so bail out.
LOG_INFO(dctl_logger, DCTL_SHUTDOWN)
.arg(app_name_).arg(getpid()).arg(VERSION);
+
+ return(getExitValue());
}
void
// 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));
}
/// 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
-// 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
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
}
void Daemon::shutdown() {
-
}
void Daemon::handleSignal() {
-// 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
/// 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
///
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.
/// @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
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.