diff --git a/doc/guide/ctrl-channel.xml b/doc/guide/ctrl-channel.xml
index b70b7b4965..e03a047458 100644
--- a/doc/guide/ctrl-channel.xml
+++ b/doc/guide/ctrl-channel.xml
@@ -184,6 +184,65 @@ will be sent to Kea and the responses received from Kea printed to standard outp
+
+ set-config
+
+
+ The set-config command instructs the server to replace
+ its current configuration with the new configuration supplied in the
+ command's arguments. The supplied configuration is expected to be the full
+ configuration for the target server along with an optional Logger
+ configuration. While optional, the Logger configuration is highly
+ recommended as without it the server will revert to its default logging
+ configuration. The structure of the command is as follows:
+
+
+{
+ "command": "set-config",
+ "arguments": {
+ "<server>": {
+ },
+ "Logging": {
+ }
+ }
+}
+
+
+ where <server> is the configuration element name for a given server
+ such as "Dhcp4" or "Dhcp6". For example:
+
+
+{
+ "command": "set-config",
+ "arguments": {
+ "Dhcp6": {
+ :
+ },
+ "Logging": {
+ :
+ }
+ }
+}
+
+
+ If the new configuration proves to be invalid the server will retain
+ its current configuration. Please note that the new configuration is
+ retained in memory only. If the server is restarted or a configuration
+ reload is triggered via a signal, the server will use the configuration
+ stored in its configuration file.
+
+ The server's response will contain a numeric code, "result" (0 for success,
+ non-zero on failure), and a string, "text", describing the outcome:
+
+ {"result": 0, "text": "Configuration successful." }
+
+ or
+
+ {"result": 1, "text": "unsupported parameter: BOGUS (<string>:16:26)" }
+
+
+
+
shutdown
@@ -205,6 +264,8 @@ will be sent to Kea and the responses received from Kea printed to standard outp
+
+
diff --git a/doc/guide/dhcp4-srv.xml b/doc/guide/dhcp4-srv.xml
index 9dcf8ba894..c2423b1266 100644
--- a/doc/guide/dhcp4-srv.xml
+++ b/doc/guide/dhcp4-srv.xml
@@ -3649,17 +3649,30 @@ src/lib/dhcpsrv/cfg_host_operations.cc -->
Communication over control channel is conducted using JSON structures.
- See the Control Channel section in the Kea Developer's Guide for more details.
+ See the Control Channel section in the Kea Developer's Guide for more
+ details.
+
+
+ The DHCPv4 server supports the following operational commands:
+
+ leases-reclaim
+ list-commands
+ set-config
+ shutdown
+
+ as described in . In addition,
+ it supports the following statistics related commands:
+
+ statistic-get
+ statistic-reset
+ statistic-remove
+ statistic-get-all
+ statistic-reset-all
+ statistic-remove-all
+
+ as described here .
- The DHCPv4 server supports statistic-get,
- statistic-reset, statistic-remove,
- statistic-get-all, statistic-reset-all
- and statistic-remove-all, specified in
- . It also supports
- list-commands and shutdown,
- specified in and
- , respectively.
diff --git a/doc/guide/dhcp6-srv.xml b/doc/guide/dhcp6-srv.xml
index 1c5153ebae..27fe542a90 100644
--- a/doc/guide/dhcp6-srv.xml
+++ b/doc/guide/dhcp6-srv.xml
@@ -4062,16 +4062,27 @@ If not specified, the default value is:
See the Control Channel section in the Kea Developer's Guide for more details.
- The DHCPv6 server supports statistic-get,
- statistic-reset, statistic-remove,
- statistic-get-all, statistic-reset-all
- and statistic-remove-all, specified in
- . It also supports
- list-commands and shutdown,
- specified in and
- , respectively.
-
+ The DHCPv6 server supports the following operational commands:
+
+ leases-reclaim
+ list-commands
+ set-config
+ shutdown
+
+ as described in . In addition,
+ it supports the following statistics related commands:
+
+ statistic-get
+ statistic-reset
+ statistic-remove
+ statistic-get-all
+ statistic-reset-all
+ statistic-remove-all
+
+ as described here .
+
+
User context in IPv6 pools
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index e95ffa3de4..4eac126187 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 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
@@ -62,7 +62,69 @@ ControlledDhcpv4Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
ConstElementPtr
ControlledDhcpv4Srv::commandConfigReloadHandler(const string&,
ConstElementPtr args) {
- return (processConfig(args));
+ // Use set-config as it handles logging and server config
+ return (commandSetConfigHandler("set-config", args));
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandSetConfigHandler(const string&,
+ ConstElementPtr args) {
+ const int status_code = 1; // 1 indicates an error
+ ConstElementPtr dhcp4;
+ string message;
+
+ // Command arguments are expected to be:
+ // { "Dhcp4": { ... }, "Logging": { ... } }
+ // The Logging component is technically optional. If it's not supplied
+ // logging will revert to default logging.
+ if (!args) {
+ message = "Missing mandatory 'arguments' parameter.";
+ } else {
+ dhcp4 = args->get("Dhcp4");
+ if (!dhcp4) {
+ message = "Missing mandatory 'Dhcp4' parameter.";
+ } else if (dhcp4->getType() != Element::map) {
+ message = "'Dhcp4' parameter expected to be a map.";
+ }
+ }
+
+ if (!message.empty()) {
+ // Something is amiss with arguments, return a failure response.
+ ConstElementPtr result = isc::config::createAnswer(status_code,
+ message);
+ return (result);
+ }
+
+ // We are starting the configuration process so we should remove any
+ // staging configuration that has been created during previous
+ // configuration attempts.
+ CfgMgr::instance().rollback();
+
+ // Logging is a sibling element and must be parsed explicitly.
+ // The call to configureLogger parses the given Logging element if
+ // not null, into the staging config. Note this does not alter the
+ // current loggers, they remain in effect until we apply the
+ // logging config below. If no logging is supplied logging will
+ // revert to default logging.
+ Daemon::configureLogger(args->get("Logging"),
+ CfgMgr::instance().getStagingCfg());
+
+ // Now we configure the server proper.
+ ConstElementPtr result = processConfig(dhcp4);
+
+ // If the configuration parsed successfully, apply the new logger
+ // configuration and the commit the new configuration. We apply
+ // the logging first in case there's a configuration failure.
+ int rcode = 0;
+ isc::config::parseAnswer(rcode, result);
+ if (rcode == 0) {
+ CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
+
+ // Use new configuration.
+ CfgMgr::instance().commit();
+ }
+
+ return (result);
}
ConstElementPtr
@@ -116,6 +178,9 @@ ControlledDhcpv4Srv::processCommand(const string& command,
} else if (command == "config-reload") {
return (srv->commandConfigReloadHandler(command, args));
+ } else if (command == "set-config") {
+ return (srv->commandSetConfigHandler(command, args));
+
} else if (command == "leases-reclaim") {
return (srv->commandLeasesReclaimHandler(command, args));
}
@@ -235,8 +300,6 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
}
}
-
-
return (answer);
}
@@ -257,6 +320,9 @@ ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)
CommandMgr::instance().registerCommand("libreload",
boost::bind(&ControlledDhcpv4Srv::commandLibReloadHandler, this, _1, _2));
+ CommandMgr::instance().registerCommand("set-config",
+ boost::bind(&ControlledDhcpv4Srv::commandSetConfigHandler, this, _1, _2));
+
CommandMgr::instance().registerCommand("leases-reclaim",
boost::bind(&ControlledDhcpv4Srv::commandLeasesReclaimHandler, this, _1, _2));
@@ -300,6 +366,7 @@ ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
// Deregister any registered commands
CommandMgr::instance().deregisterCommand("shutdown");
CommandMgr::instance().deregisterCommand("libreload");
+ CommandMgr::instance().deregisterCommand("set-config");
CommandMgr::instance().deregisterCommand("leases-reclaim");
CommandMgr::instance().deregisterCommand("statistic-get");
CommandMgr::instance().deregisterCommand("statistic-reset");
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h
index 57883e5fd8..2d96b34427 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.h
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 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
@@ -143,6 +143,19 @@ private:
commandConfigReloadHandler(const std::string& command,
isc::data::ConstElementPtr args);
+ /// @brief handler for processing 'set-config' command
+ ///
+ /// This handler processes set-config command, which processes
+ /// configuration specified in args parameter.
+ /// @param command (parameter ignored)
+ /// @param args configuration to be processed. Expected format:
+ /// map containing Dhcp4 map that contains DHCPv4 server configuration.
+ /// May also contain Logging map that specifies logging configuration.
+ ///
+ /// @return status of the command
+ isc::data::ConstElementPtr
+ commandSetConfigHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
/// @brief Handler for processing 'leases-reclaim' command
///
diff --git a/src/bin/dhcp4/json_config_parser.cc b/src/bin/dhcp4/json_config_parser.cc
index 427ecf4fc6..d291e1444a 100644
--- a/src/bin/dhcp4/json_config_parser.cc
+++ b/src/bin/dhcp4/json_config_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 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
@@ -497,6 +497,47 @@ void setGlobalParameters4() {
}
}
+/// @brief Initialize the command channel based on the staging configuration
+///
+/// Only close the current channel, if the new channel configuration is
+/// different. This avoids disconnecting a client and hence not sending them
+/// a command result, unless they specifically alter the channel configuration.
+/// In that case the user simply has to accept they'll be disconnected.
+///
+void configureCommandChannel() {
+ // Get new socket configuration.
+ ConstElementPtr sock_cfg =
+ CfgMgr::instance().getStagingCfg()->getControlSocketInfo();
+
+ // Get current socket configuration.
+ ConstElementPtr current_sock_cfg =
+ CfgMgr::instance().getCurrentCfg()->getControlSocketInfo();
+
+ // Determine if the socket configuration has changed. It has if
+ // both old and new configuration is specified but respective
+ // data elements are't equal.
+ bool sock_changed = (sock_cfg && current_sock_cfg &&
+ !sock_cfg->equals(*current_sock_cfg));
+
+ // If the previous or new socket configuration doesn't exist or
+ // the new configuration differs from the old configuration we
+ // close the exisitng socket and open a new socket as appropriate.
+ // Note that closing an existing socket means the clien will not
+ // receive the configuration result.
+ if (!sock_cfg || !current_sock_cfg || sock_changed) {
+ // Close the existing socket (if any).
+ isc::config::CommandMgr::instance().closeCommandSocket();
+
+ if (sock_cfg) {
+ // This will create a control socket and install the external
+ // socket in IfaceMgr. That socket will be monitored when
+ // Dhcp4Srv::receivePacket() calls IfaceMgr::receive4() and
+ // callback in CommandMgr will be called, if necessary.
+ isc::config::CommandMgr::instance().openCommandSocket(sock_cfg);
+ }
+ }
+}
+
isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
if (!config_set) {
@@ -646,25 +687,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
subnet_parser->build(subnet_config->second);
}
- // Get command socket configuration from the config file.
- // This code expects the following structure:
- // {
- // "socket-type": "unix",
- // "socket-name": "/tmp/kea4.sock"
- // }
- ConstElementPtr sock_cfg =
- CfgMgr::instance().getStagingCfg()->getControlSocketInfo();
-
- // Close existing socket (if any).
- isc::config::CommandMgr::instance().closeCommandSocket();
- if (sock_cfg) {
- // This will create a control socket and will install external socket
- // in IfaceMgr. That socket will be monitored when Dhcp4Srv::receivePacket()
- // calls IfaceMgr::receive4() and callback in CommandMgr will be called,
- // if necessary. If there were previously open command socket, it will
- // be closed.
- isc::config::CommandMgr::instance().openCommandSocket(sock_cfg);
- }
+ // Setup the command channel.
+ configureCommandChannel();
// the leases database parser is the last to be run.
std::map::const_iterator leases_config =
diff --git a/src/bin/dhcp4/kea_controller.cc b/src/bin/dhcp4/kea_controller.cc
index 60a289bb02..43499f91a6 100644
--- a/src/bin/dhcp4/kea_controller.cc
+++ b/src/bin/dhcp4/kea_controller.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 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
@@ -34,11 +34,6 @@ void configure(const std::string& file_name) {
// This is a configuration backend implementation that reads the
// configuration from a JSON file.
- // We are starting the configuration process so we should remove any
- // staging configuration that has been created during previous
- // configuration attempts.
- CfgMgr::instance().rollback();
-
isc::data::ConstElementPtr json;
isc::data::ConstElementPtr dhcp4;
isc::data::ConstElementPtr logger;
@@ -70,26 +65,14 @@ void configure(const std::string& file_name) {
" Did you forget to add { } around your configuration?");
}
- // If there's no logging element, we'll just pass NULL pointer,
- // which will be handled by configureLogger().
- Daemon::configureLogger(json->get("Logging"),
- CfgMgr::instance().getStagingCfg());
-
- // Get Dhcp4 component from the config
- dhcp4 = json->get("Dhcp4");
- if (!dhcp4) {
- isc_throw(isc::BadValue, "no mandatory 'Dhcp4' entry in"
- " the configuration");
- }
-
// Use parsed JSON structures to configure the server
- result = ControlledDhcpv4Srv::processCommand("config-reload", dhcp4);
+ result = ControlledDhcpv4Srv::processCommand("set-config", json);
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.
isc_throw(isc::BadValue, "undefined result of "
- "processCommand(\"config-reload\", dhcp4)");
+ "processCommand(\"set-config\", json)");
}
// Now check is the returned result is successful (rcode=0) or not
@@ -102,17 +85,6 @@ void configure(const std::string& file_name) {
"no details available";
isc_throw(isc::BadValue, reason);
}
-
- // If configuration was parsed successfully, apply the new logger
- // configuration to log4cplus. It is done before commit in case
- // something goes wrong.
- CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
-
- // Use new configuration.
- /// @todo: This commit should be moved to
- /// CtrlDhcp4Srv::commandConfigReloadHandler.
- CfgMgr::instance().commit();
-
} catch (const std::exception& ex) {
// If configuration failed at any stage, we drop the staging
// configuration and continue to use the previous one.
@@ -167,7 +139,7 @@ ControlledDhcpv4Srv::init(const std::string& 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", ...)
+ // processCommand("set-config", ...)
// Set signal handlers. When the SIGHUP is received by the process
// the server reconfiguration will be triggered. When SIGTERM or
diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
index 536b9169a7..50fcbf55a9 100644
--- a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 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
@@ -15,7 +15,9 @@
#include
#include
#include
+#include
#include
+#include
#include
#include "marker_file.h"
@@ -121,6 +123,13 @@ public:
ConstElementPtr config;
ASSERT_NO_THROW(config = parseDHCP4(config_txt));
ConstElementPtr answer = server_->processConfig(config);
+
+ // Commit the configuration so any subsequent reconfigurations
+ // will only close the command channel if its configuration has
+ // changed.
+ CfgMgr::instance().commit();
+
+
ASSERT_TRUE(answer);
int status = 0;
@@ -472,4 +481,147 @@ TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelStats) {
response);
}
+// Check that the "set-config" command will replace current configuration
+TEST_F(CtrlChannelDhcpv4SrvTest, set_config) {
+ createUnixChannelServer();
+
+ // Define strings to permutate the config arguments
+ // (Note the line feeds makes errors easy to find)
+ string set_config_txt = "{ \"command\": \"set-config\" \n";
+ string args_txt = " \"arguments\": { \n";
+ string dhcp4_cfg_txt =
+ " \"Dhcp4\": { \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\"] \n"
+ " }, \n"
+ " \"valid-lifetime\": 4000, \n"
+ " \"renew-timer\": 1000, \n"
+ " \"rebind-timer\": 2000, \n"
+ " \"lease-database\": { \n"
+ " \"type\": \"memfile\", \n"
+ " \"persist\":false, \n"
+ " \"lfc-interval\": 0 \n"
+ " }, \n"
+ " \"expired-leases-processing\": { \n"
+ " \"reclaim-timer-wait-time\": 0, \n"
+ " \"hold-reclaimed-time\": 0, \n"
+ " \"flush-reclaimed-timer-wait-time\": 0 \n"
+ " },"
+ " \"subnet4\": [ \n";
+ string subnet1 =
+ " {\"subnet\": \"192.2.0.0/24\", \n"
+ " \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
+ string subnet2 =
+ " {\"subnet\": \"192.2.1.0/24\", \n"
+ " \"pools\": [{ \"pool\": \"192.2.1.1-192.2.1.50\" }]}\n";
+ string bad_subnet =
+ " {\"BOGUS\": \"192.2.2.0/24\", \n"
+ " \"pools\": [{ \"pool\": \"192.2.2.1-192.2.2.50\" }]}\n";
+ string subnet_footer =
+ " ] \n";
+ string control_socket_header =
+ " ,\"control-socket\": { \n"
+ " \"socket-type\": \"unix\", \n"
+ " \"socket-name\": \"";
+ string control_socket_footer =
+ "\" \n} \n";
+ string logger_txt =
+ " \"Logging\": { \n"
+ " \"loggers\": [ { \n"
+ " \"name\": \"kea\", \n"
+ " \"severity\": \"FATAL\", \n"
+ " \"output_options\": [{ \n"
+ " \"output\": \"/dev/null\" \n"
+ " }] \n"
+ " }] \n"
+ " } \n";
+
+ std::ostringstream os;
+
+ // Create a valid config with all the parts should parse
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp4_cfg_txt
+ << subnet1
+ << subnet_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close dhcp4
+ << ","
+ << logger_txt
+ << "}}";
+
+ // Send the set-config command
+ std::string response;
+ sendUnixCommand(os.str(), response);
+
+ // Verify the configuration was successful.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+ response);
+
+ // Check that the config was indeed applied.
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ // Create a config with malformed subnet that should fail to parse.
+ os.str("");
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp4_cfg_txt
+ << bad_subnet
+ << subnet_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close dhcp4
+ "}}";
+
+ // Send the set-config command
+ sendUnixCommand(os.str(), response);
+
+ // Should fail with a syntax error
+ EXPECT_EQ("{ \"result\": 1, "
+ "\"text\": \"unsupported parameter: BOGUS (:20:26)\" }",
+ response);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ // Create a valid config with two subnets and no command channel.
+ // It should succeed, client should still receive the response
+ os.str("");
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp4_cfg_txt
+ << subnet1
+ << ",\n"
+ << subnet2
+ << subnet_footer
+ << "}\n" // close dhcp4
+ << "}}";
+
+ /* Verify the control channel socket exists */
+ ASSERT_TRUE(fileExists(socket_path_));
+
+ // Send the set-config command
+ sendUnixCommand(os.str(), response);
+
+ /* Verify the control channel socket no longer exists */
+ EXPECT_FALSE(fileExists(socket_path_));
+
+ // With no command channel, should still receive the response.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+ response);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ EXPECT_EQ(2, subnets->size());
+
+ // Clean up after the test.
+ CfgMgr::instance().clear();
+}
+
} // End of anonymous namespace
diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.cc b/src/bin/dhcp4/tests/dhcp4_test_utils.cc
index ba8349c8e8..4a2a6af882 100644
--- a/src/bin/dhcp4/tests/dhcp4_test_utils.cc
+++ b/src/bin/dhcp4/tests/dhcp4_test_utils.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 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
@@ -24,6 +24,7 @@
#include
#include
#include
+#include
#include
using namespace std;
@@ -47,6 +48,9 @@ BaseServerTest::~BaseServerTest() {
// Revert to original data directory.
CfgMgr::instance().setDataDir(original_datadir_);
+
+ // Revert to unit test logging, in case the test reconfigured it.
+ isc::log::initLogger();
}
Dhcpv4SrvTest::Dhcpv4SrvTest()
diff --git a/src/bin/dhcp4/tests/kea_controller_unittest.cc b/src/bin/dhcp4/tests/kea_controller_unittest.cc
index 8cd9a617e0..44109d1852 100644
--- a/src/bin/dhcp4/tests/kea_controller_unittest.cc
+++ b/src/bin/dhcp4/tests/kea_controller_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 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
@@ -63,7 +63,6 @@ public:
~JSONFileBackendTest() {
LeaseMgrFactory::destroy();
- isc::log::setDefaultLoggingOutput();
static_cast(remove(TEST_FILE));
static_cast(remove(TEST_INCLUDE));
};
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index 0d9f7331c6..11c2122a0e 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 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
@@ -67,10 +67,72 @@ ControlledDhcpv6Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
ConstElementPtr
ControlledDhcpv6Srv::commandConfigReloadHandler(const string&, ConstElementPtr args) {
-
- return (processConfig(args));
+ // Use set-config as it handles logging and server config
+ return (commandSetConfigHandler("set-config", args));
}
+ConstElementPtr
+ControlledDhcpv6Srv::commandSetConfigHandler(const string&,
+ ConstElementPtr args) {
+ const int status_code = 1; // 1 indicates an error
+ ConstElementPtr dhcp6;
+ string message;
+
+ // Command arguments are expected to be:
+ // { "Dhcp6": { ... }, "Logging": { ... } }
+ // The Logging component is technically optional. If it's not supplied
+ // logging will revert to default logging.
+ if (!args) {
+ message = "Missing mandatory 'arguments' parameter.";
+ } else {
+ dhcp6 = args->get("Dhcp6");
+ if (!dhcp6) {
+ message = "Missing mandatory 'Dhcp6' parameter.";
+ } else if (dhcp6->getType() != Element::map) {
+ message = "'Dhcp6' parameter expected to be a map.";
+ }
+ }
+
+ if (!message.empty()) {
+ // Something is amiss with arguments, return a failure response.
+ ConstElementPtr result = isc::config::createAnswer(status_code,
+ message);
+ return (result);
+ }
+
+ // We are starting the configuration process so we should remove any
+ // staging configuration that has been created during previous
+ // configuration attempts.
+ CfgMgr::instance().rollback();
+
+ // Logging is a sibling element and must be parsed explicitly.
+ // The call to configureLogger parses the given Logging element if
+ // not null, into the staging config. Note this does not alter the
+ // current loggers, they remain in effect until we apply the
+ // logging config below. If no logging is supplied logging will
+ // revert to default logging.
+ Daemon::configureLogger(args->get("Logging"),
+ CfgMgr::instance().getStagingCfg());
+
+ // Now we configure the server proper.
+ ConstElementPtr result = processConfig(dhcp6);
+
+ // If the configuration parsed successfully, apply the new logger
+ // configuration and the commit the new configuration. We apply
+ // the logging first in case there's a configuration failure.
+ int rcode = 0;
+ isc::config::parseAnswer(rcode, result);
+ if (rcode == 0) {
+ CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
+
+ // Use new configuration.
+ CfgMgr::instance().commit();
+ }
+
+ return (result);
+}
+
+
ConstElementPtr
ControlledDhcpv6Srv::commandLeasesReclaimHandler(const string&,
ConstElementPtr args) {
@@ -116,12 +178,17 @@ ControlledDhcpv6Srv::processCommand(const std::string& command,
if (command == "shutdown") {
return (srv->commandShutdownHandler(command, args));
+ /// @todo: register config-reload (see CtrlDhcpv6Srv::commandConfigReloadHandler)
+
} else if (command == "libreload") {
return (srv->commandLibReloadHandler(command, args));
} else if (command == "config-reload") {
return (srv->commandConfigReloadHandler(command, args));
+ } else if (command == "set-config") {
+ return (srv->commandSetConfigHandler(command, args));
+
} else if (command == "leases-reclaim") {
return (srv->commandLeasesReclaimHandler(command, args));
}
@@ -279,11 +346,12 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
CommandMgr::instance().registerCommand("shutdown",
boost::bind(&ControlledDhcpv6Srv::commandShutdownHandler, this, _1, _2));
- /// @todo: register config-reload (see CtrlDhcpv4Srv::commandConfigReloadHandler)
-
CommandMgr::instance().registerCommand("libreload",
boost::bind(&ControlledDhcpv6Srv::commandLibReloadHandler, this, _1, _2));
+ CommandMgr::instance().registerCommand("set-config",
+ boost::bind(&ControlledDhcpv6Srv::commandSetConfigHandler, this, _1, _2));
+
CommandMgr::instance().registerCommand("leases-reclaim",
boost::bind(&ControlledDhcpv6Srv::commandLeasesReclaimHandler, this, _1, _2));
@@ -327,6 +395,7 @@ ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
// Deregister any registered commands
CommandMgr::instance().deregisterCommand("shutdown");
CommandMgr::instance().deregisterCommand("libreload");
+ CommandMgr::instance().deregisterCommand("set-config");
CommandMgr::instance().deregisterCommand("leases-reclaim");
CommandMgr::instance().deregisterCommand("statistic-get");
CommandMgr::instance().deregisterCommand("statistic-reset");
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h
index b1ca9587c1..23abb69632 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.h
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 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
@@ -143,6 +143,20 @@ private:
commandConfigReloadHandler(const std::string& command,
isc::data::ConstElementPtr args);
+ /// @brief handler for processing 'set-config' command
+ ///
+ /// This handler processes set-config command, which processes
+ /// configuration specified in args parameter.
+ /// @param command (parameter ignored)
+ /// @param args configuration to be processed. Expected format:
+ /// map containing Dhcp6 map that contains DHCPv6 server configuration.
+ /// May also contain Logging map that specifies logging configuration.
+ ///
+ /// @return status of the command
+ isc::data::ConstElementPtr
+ commandSetConfigHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
/// @brief Handler for processing 'leases-reclaim' command
///
/// This handler processes leases-reclaim command, which triggers
diff --git a/src/bin/dhcp6/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc
index 84b410791d..04e1e0fcf0 100644
--- a/src/bin/dhcp6/json_config_parser.cc
+++ b/src/bin/dhcp6/json_config_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 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
@@ -770,6 +770,47 @@ void setGlobalParameters6() {
}
}
+/// @brief Initialize the command channel based on the staging configuration
+///
+/// Only close the current channel, if the new channel configuration is
+/// different. This avoids disconnecting a client and hence not sending them
+/// a command result, unless they specifically alter the channel configuration.
+/// In that case the user simply has to accept they'll be disconnected.
+///
+void configureCommandChannel() {
+ // Get new socket configuration.
+ ConstElementPtr sock_cfg =
+ CfgMgr::instance().getStagingCfg()->getControlSocketInfo();
+
+ // Get current socket configuration.
+ ConstElementPtr current_sock_cfg =
+ CfgMgr::instance().getCurrentCfg()->getControlSocketInfo();
+
+ // Determine if the socket configuration has changed. It has if
+ // both old and new configuration is specified but respective
+ // data elements are't equal.
+ bool sock_changed = (sock_cfg && current_sock_cfg &&
+ !sock_cfg->equals(*current_sock_cfg));
+
+ // If the previous or new socket configuration doesn't exist or
+ // the new configuration differs from the old configuration we
+ // close the exisitng socket and open a new socket as appropriate.
+ // Note that closing an existing socket means the clien will not
+ // receive the configuration result.
+ if (!sock_cfg || !current_sock_cfg || sock_changed) {
+ // Close the existing socket (if any).
+ isc::config::CommandMgr::instance().closeCommandSocket();
+
+ if (sock_cfg) {
+ // This will create a control socket and install the external
+ // socket in IfaceMgr. That socket will be monitored when
+ // Dhcp4Srv::receivePacket() calls IfaceMgr::receive4() and
+ // callback in CommandMgr will be called, if necessary.
+ isc::config::CommandMgr::instance().openCommandSocket(sock_cfg);
+ }
+ }
+}
+
isc::data::ConstElementPtr
configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
if (!config_set) {
@@ -921,25 +962,8 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
subnet_parser->build(subnet_config->second);
}
- // Get command socket configuration from the config file.
- // This code expects the following structure:
- // {
- // "socket-type": "unix",
- // "socket-name": "/tmp/kea6.sock"
- // }
- ConstElementPtr sock_cfg =
- CfgMgr::instance().getStagingCfg()->getControlSocketInfo();
-
- // Close existing socket (if any).
- isc::config::CommandMgr::instance().closeCommandSocket();
- if (sock_cfg) {
- // This will create a control socket and will install external socket
- // in IfaceMgr. That socket will be monitored when Dhcp4Srv::receivePacket()
- // calls IfaceMgr::receive4() and callback in CommandMgr will be called,
- // if necessary. If there were previously open command socket, it will
- // be closed.
- isc::config::CommandMgr::instance().openCommandSocket(sock_cfg);
- }
+ // Setup the command channel.
+ configureCommandChannel();
// The lease database parser is the last to be run.
std::map::const_iterator leases_config =
diff --git a/src/bin/dhcp6/kea_controller.cc b/src/bin/dhcp6/kea_controller.cc
index 62be83af99..bd190796da 100644
--- a/src/bin/dhcp6/kea_controller.cc
+++ b/src/bin/dhcp6/kea_controller.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 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
@@ -38,11 +38,6 @@ void configure(const std::string& file_name) {
// This is a configuration backend implementation that reads the
// configuration from a JSON file.
- // We are starting the configuration process so we should remove any
- // staging configuration that has been created during previous
- // configuration attempts.
- CfgMgr::instance().rollback();
-
isc::data::ConstElementPtr json;
isc::data::ConstElementPtr dhcp6;
isc::data::ConstElementPtr logger;
@@ -74,29 +69,14 @@ void configure(const std::string& file_name) {
" Did you forget to add { } around your configuration?");
}
- // Let's configure logging before applying the configuration,
- // so we can log things during configuration process.
- // If there's no logging element, we'll just pass NULL pointer,
- // which will be handled by configureLogger().
- Daemon::configureLogger(json->get("Logging"),
- CfgMgr::instance().getStagingCfg());
-
- // Get Dhcp6 component from the config
- dhcp6 = json->get("Dhcp6");
-
- if (!dhcp6) {
- isc_throw(isc::BadValue, "no mandatory 'Dhcp6' entry in"
- " the configuration");
- }
-
// Use parsed JSON structures to configure the server
- result = ControlledDhcpv6Srv::processCommand("config-reload", dhcp6);
+ result = ControlledDhcpv6Srv::processCommand("set-config", json);
if (!result) {
// Undetermined status of the configuration. This should never
// happen, but as the configureDhcp6Server returns a pointer, it is
// theoretically possible that it will return NULL.
isc_throw(isc::BadValue, "undefined result of "
- "processCommand(\"config-reload\", dhcp6)");
+ "processCommand(\"set-config\", json)");
}
// Now check is the returned result is successful (rcode=0) or not
@@ -109,15 +89,6 @@ void configure(const std::string& file_name) {
"no details available";
isc_throw(isc::BadValue, reason);
}
-
- // If configuration was parsed successfully, apply the new logger
- // configuration to log4cplus. It is done before commit in case
- // something goes wrong.
- CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
-
- // Use new configuration.
- CfgMgr::instance().commit();
-
} catch (const std::exception& ex) {
// If configuration failed at any stage, we drop the staging
// configuration and continue to use the previous one.
@@ -128,7 +99,6 @@ void configure(const std::string& file_name) {
isc_throw(isc::BadValue, "configuration error using file '"
<< file_name << "': " << ex.what());
}
-
}
/// @brief Signals handler for DHCPv6 server.
diff --git a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
index efd2098f55..aa03ca333e 100644
--- a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 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
@@ -15,8 +15,10 @@
#include
#include
#include
+#include
#include
#include
+#include
#include "marker_file.h"
#include "test_libraries.h"
@@ -25,6 +27,7 @@
#include
#include
+#include
#include
#include
@@ -148,6 +151,12 @@ public:
ConstElementPtr config;
ASSERT_NO_THROW(config = parseDHCP6(config_txt));
ConstElementPtr answer = server_->processConfig(config);
+
+ // Commit the configuration so any subsequent reconfigurations
+ // will only close the command channel if its configuration has
+ // changed.
+ CfgMgr::instance().commit();
+
ASSERT_TRUE(answer);
int status = 0;
@@ -291,7 +300,7 @@ TEST_F(CtrlDhcpv6SrvTest, configReload) {
// Use empty parameters list
// Prepare configuration file.
- string config_txt = "{ \"interfaces-config\": {"
+ string config_txt = "{ \"Dhcp6\": { \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"preferred-lifetime\": 3000,"
@@ -310,10 +319,10 @@ TEST_F(CtrlDhcpv6SrvTest, configReload) {
" \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
" \"subnet\": \"2001:db8:3::/64\" "
" } ],"
- "\"valid-lifetime\": 4000 }";
+ "\"valid-lifetime\": 4000 }}";
ConstElementPtr config;
- ASSERT_NO_THROW(config = parseDHCP6(config_txt));
+ ASSERT_NO_THROW(config = parseJSON(config_txt));
// Make sure there are no subnets configured.
CfgMgr::instance().clear();
@@ -327,13 +336,158 @@ TEST_F(CtrlDhcpv6SrvTest, configReload) {
// Check that the config was indeed applied.
const Subnet6Collection* subnets =
- CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
EXPECT_EQ(3, subnets->size());
// Clean up after the test.
CfgMgr::instance().clear();
}
+// Check that the "set-config" command will replace current configuration
+TEST_F(CtrlChannelDhcpv6SrvTest, set_config) {
+ createUnixChannelServer();
+
+ // Define strings to permutate the config arguments
+ // (Note the line feeds makes errors easy to find)
+ string set_config_txt = "{ \"command\": \"set-config\" \n";
+ string args_txt = " \"arguments\": { \n";
+ string dhcp6_cfg_txt =
+ " \"Dhcp6\": { \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\"] \n"
+ " }, \n"
+ " \"preferred-lifetime\": 3000, \n"
+ " \"valid-lifetime\": 4000, \n"
+ " \"renew-timer\": 1000, \n"
+ " \"rebind-timer\": 2000, \n"
+ " \"lease-database\": { \n"
+ " \"type\": \"memfile\", \n"
+ " \"persist\":false, \n"
+ " \"lfc-interval\": 0 \n"
+ " }, \n"
+ " \"expired-leases-processing\": { \n"
+ " \"reclaim-timer-wait-time\": 0, \n"
+ " \"hold-reclaimed-time\": 0, \n"
+ " \"flush-reclaimed-timer-wait-time\": 0 \n"
+ " },"
+ " \"subnet6\": [ \n";
+ string subnet1 =
+ " {\"subnet\": \"3002::/64\", \n"
+ " \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
+ string subnet2 =
+ " {\"subnet\": \"3003::/64\", \n"
+ " \"pools\": [{ \"pool\": \"3003::100-3003::200\" }]}\n";
+ string bad_subnet =
+ " {\"BOGUS\": \"3005::/64\", \n"
+ " \"pools\": [{ \"pool\": \"3005::100-3005::200\" }]}\n";
+ string subnet_footer =
+ " ] \n";
+ string control_socket_header =
+ " ,\"control-socket\": { \n"
+ " \"socket-type\": \"unix\", \n"
+ " \"socket-name\": \"";
+ string control_socket_footer =
+ "\" \n} \n";
+ string logger_txt =
+ " \"Logging\": { \n"
+ " \"loggers\": [ { \n"
+ " \"name\": \"kea\", \n"
+ " \"severity\": \"FATAL\", \n"
+ " \"output_options\": [{ \n"
+ " \"output\": \"/dev/null\" \n"
+ " }] \n"
+ " }] \n"
+ " } \n";
+
+ std::ostringstream os;
+
+ // Create a valid config with all the parts should parse
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << subnet1
+ << subnet_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close dhcp6
+ << ","
+ << logger_txt
+ << "}}";
+
+ // Send the set-config command
+ std::string response;
+ sendUnixCommand(os.str(), response);
+
+ // Verify the configuration was successful.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+ response);
+
+ // Check that the config was indeed applied.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ // Create a config with malformed subnet that should fail to parse.
+ os.str("");
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << bad_subnet
+ << subnet_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close dhcp6
+ "}}";
+
+ // Send the set-config command
+ sendUnixCommand(os.str(), response);
+
+ // Should fail with a syntax error
+ EXPECT_EQ("{ \"result\": 1, "
+ "\"text\": \"unsupported parameter: BOGUS (:21:26)\" }",
+ response);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ // Create a valid config with two subnets and no command channel.
+ // It should succeed but client will not receive a the response
+ os.str("");
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << subnet1
+ << ",\n"
+ << subnet2
+ << subnet_footer
+ << "}\n" // close dhcp6
+ << "}}";
+
+ /* Verify the control channel socket exists */
+ ASSERT_TRUE(fileExists(socket_path_));
+
+ // Send the set-config command
+ sendUnixCommand(os.str(), response);
+
+ /* Verify the control channel socket no longer exists */
+ EXPECT_FALSE(fileExists(socket_path_));
+
+ // With no command channel, should still receive the response.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+ response);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(2, subnets->size());
+
+ // Clean up after the test.
+ CfgMgr::instance().clear();
+}
+
+
typedef std::map ElementMap;
// This test checks which commands are registered by the DHCPv4 server.
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.cc b/src/bin/dhcp6/tests/dhcp6_test_utils.cc
index 7ab67b7cdd..e97a4a7cf7 100644
--- a/src/bin/dhcp6/tests/dhcp6_test_utils.cc
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 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
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -46,6 +47,9 @@ BaseServerTest::~BaseServerTest() {
// Revert to original data directory.
CfgMgr::instance().setDataDir(original_datadir_);
+
+ // Revert to unit test logging in case the test reconfigured logging.
+ isc::log::initLogger();
}
Dhcpv6SrvTest::Dhcpv6SrvTest()
diff --git a/src/lib/config/command_mgr.cc b/src/lib/config/command_mgr.cc
index ae33188014..cf9dfb7306 100644
--- a/src/lib/config/command_mgr.cc
+++ b/src/lib/config/command_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 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
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
using namespace isc::data;
@@ -145,6 +146,19 @@ CommandMgr::commandReader(int sockfd) {
return;
}
+ // Duplicate the connection's socket in the event, the command causes the
+ // channel to close (like a reconfig). This permits us to always have
+ // a socket on which to respond. If for some reason we can't fall back
+ // to the connection socket.
+ int rsp_fd = dup(sockfd);
+ if (rsp_fd < 0 ) {
+ // Highly unlikely
+ const char* errmsg = strerror(errno);
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_DUP_WARN)
+ .arg(errmsg);
+ rsp_fd = sockfd;
+ }
+
LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_READ).arg(rval).arg(sockfd);
// Ok, we received something. Let's see if we can make any sense of it.
@@ -163,6 +177,11 @@ CommandMgr::commandReader(int sockfd) {
if (!rsp) {
LOG_WARN(command_logger, COMMAND_RESPONSE_ERROR);
+ // Only close the duped socket if it's different (should be)
+ if (rsp_fd != sockfd) {
+ close(rsp_fd);
+ }
+
return;
}
@@ -179,7 +198,8 @@ CommandMgr::commandReader(int sockfd) {
}
// Send the data back over socket.
- rval = write(sockfd, txt.c_str(), len);
+ rval = write(rsp_fd, txt.c_str(), len);
+ int saverr = errno;
LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_WRITE).arg(len).arg(sockfd);
@@ -187,7 +207,13 @@ CommandMgr::commandReader(int sockfd) {
// Response transmission failed. Since the response failed, it doesn't
// make sense to send any status codes. Let's log it and be done with
// it.
- LOG_ERROR(command_logger, COMMAND_SOCKET_WRITE_FAIL).arg(len).arg(sockfd);
+ LOG_ERROR(command_logger, COMMAND_SOCKET_WRITE_FAIL)
+ .arg(len).arg(sockfd).arg(strerror(saverr));
+ }
+
+ // Only close the duped socket if it's different (should be)
+ if (rsp_fd != sockfd) {
+ close(rsp_fd);
}
}
diff --git a/src/lib/config/config_messages.mes b/src/lib/config/config_messages.mes
index 2a4558fd85..ed5df85c8b 100644
--- a/src/lib/config/config_messages.mes
+++ b/src/lib/config/config_messages.mes
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2011-2017 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
@@ -57,6 +57,13 @@ This error message indicates that the server failed to set non-blocking mode
on just created socket. That socket was created for accepting specific
incoming connection. Additional information may be provided as third parameter.
+% COMMAND_SOCKET_DUP_WARN Failed to duplicate socket for response: %1
+This debug message indicates that the commandReader was unable to duplicate
+the connection socket prior to executing the command. This is most likely a
+system resource issue. The command should still be processed and the response
+sent, unless the command caused the command channel to be closed (e.g. a
+reconfiguration command).
+
% COMMAND_SOCKET_READ Received %1 bytes over command socket %2
This debug message indicates that specified number of bytes was received
over command socket identified by specified file descriptor.
@@ -86,6 +93,6 @@ descriptor and path specified.
This debug message indicates that the specified number of bytes was sent
over command socket identifier by the specified file descriptor.
-% COMMAND_SOCKET_WRITE_FAIL Error while writing %1 bytes to command socket %2
+% COMMAND_SOCKET_WRITE_FAIL Error while writing %1 bytes to command socket %2 : %3
This error message indicates that an error was encountered while
attempting to send a response to the command socket.
diff --git a/src/lib/testutils/io_utils.cc b/src/lib/testutils/io_utils.cc
index 54aff4cb6f..384cb68481 100644
--- a/src/lib/testutils/io_utils.cc
+++ b/src/lib/testutils/io_utils.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 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
@@ -15,10 +15,8 @@ namespace dhcp {
namespace test {
bool fileExists(const std::string& file_path) {
- std::ifstream fs(file_path.c_str());
- const bool file_exists = fs.good();
- fs.close();
- return (file_exists);
+ struct stat statbuf;
+ return(stat(file_path.c_str(), &statbuf) == 0);
}
std::string readFile(const std::string& file_path) {
diff --git a/src/lib/testutils/io_utils.h b/src/lib/testutils/io_utils.h
index 8cf1ef6e14..9155a7412e 100644
--- a/src/lib/testutils/io_utils.h
+++ b/src/lib/testutils/io_utils.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 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
@@ -8,6 +8,7 @@
#define TEST_IO_UTILS_H
#include
+#include
namespace isc {
namespace dhcp {