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 {