From 27cf2211cd44af9d37c8e5532e817d1d96e25236 Mon Sep 17 00:00:00 2001 From: Tomek Mrugalski Date: Fri, 14 Apr 2017 13:51:51 +0200 Subject: [PATCH] [5213] config-reload implemented, set-config renamed to config-set --- src/bin/dhcp4/ctrl_dhcp4_srv.cc | 1 + .../dhcp4/tests/ctrl_dhcp4_srv_unittest.cc | 8 +- src/bin/dhcp6/Makefile.am | 1 - src/bin/dhcp6/ctrl_dhcp6_srv.cc | 160 ++++++++++++++++- src/bin/dhcp6/ctrl_dhcp6_srv.h | 28 ++- src/bin/dhcp6/kea_controller.cc | 162 ----------------- .../dhcp6/tests/ctrl_dhcp6_srv_unittest.cc | 166 +++++++++++------- 7 files changed, 277 insertions(+), 249 deletions(-) delete mode 100644 src/bin/dhcp6/kea_controller.cc diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc index 731c40dc89..bcef1d4ecc 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc @@ -18,6 +18,7 @@ #include #include #include +#include using namespace isc::data; using namespace isc::hooks; diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc index c06211d53e..34ab358d44 100644 --- a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc @@ -1014,7 +1014,8 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configReloadMissingFile) { // Tell the server to reload its configuration. It should attempt to load // test6.json (and fail, because the file is not there). sendUnixCommand("{ \"command\": \"config-reload\" }", response); - // Verify the configuration was successful. + + // Verify the reload was rejected. EXPECT_EQ("{ \"result\": 1, \"text\": \"Config reload failed:" "configuration error using file 'test6.json': Unable to open file " "test6.json\" }", @@ -1034,15 +1035,16 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configReloadBrokenFile) { // Although Kea is smart, its AI routines are not smart enough to handle // this one... at least not yet. ofstream f("test7.json", ios::trunc); - f << "can u give me some addr?"; + f << "gimme some addrs, bro!"; f.close(); // Now tell Kea to reload its config. sendUnixCommand("{ \"command\": \"config-reload\" }", response); + // Verify the reload will fail. EXPECT_EQ("{ \"result\": 1, \"text\": \"Config reload failed:" "configuration error using file 'test7.json': " - "test7.json:1.1: Invalid character: c\" }", + "test7.json:1.1: Invalid character: g\" }", response); ::remove("test7.json"); diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am index 4835f8bf4b..dcd9cd1cb0 100644 --- a/src/bin/dhcp6/Makefile.am +++ b/src/bin/dhcp6/Makefile.am @@ -67,7 +67,6 @@ libdhcp6_la_SOURCES += dhcp6to4_ipc.cc dhcp6to4_ipc.h libdhcp6_la_SOURCES += dhcp6_lexer.ll location.hh position.hh stack.hh libdhcp6_la_SOURCES += dhcp6_parser.cc dhcp6_parser.h libdhcp6_la_SOURCES += parser_context.cc parser_context.h parser_context_decl.h -libdhcp6_la_SOURCES += kea_controller.cc libdhcp6_la_SOURCES += simple_parser6.cc simple_parser6.h nodist_libdhcp6_la_SOURCES = dhcp6_messages.h dhcp6_messages.cc diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc index fb8d2b59a2..2596bfab15 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc @@ -15,9 +15,11 @@ #include #include #include +#include #include #include #include +#include using namespace isc::config; using namespace isc::data; @@ -30,6 +32,26 @@ namespace { // Name of the file holding server identifier. static const char* SERVER_DUID_FILE = "kea-dhcp6-serverid"; +/// @brief Signals handler for DHCPv6 server. +/// +/// This signal handler handles the following signals received by the DHCPv6 +/// server process: +/// - SIGHUP - triggers server's dynamic reconfiguration. +/// - SIGTERM - triggers server's shut down. +/// - SIGINT - triggers server's shut down. +/// +/// @param signo Signal number received. +void signalHandler(int signo) { + // SIGHUP signals a request to reconfigure the server. + if (signo == SIGHUP) { + isc::dhcp::ControlledDhcpv6Srv::processCommand("config-reload", + ConstElementPtr()); + } else if ((signo == SIGTERM) || (signo == SIGINT)) { + isc::dhcp::ControlledDhcpv6Srv::processCommand("shutdown", + ConstElementPtr()); + } +} + } namespace isc { @@ -37,6 +59,115 @@ namespace dhcp { ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL; +/// @brief Configure DHCPv6 server using the configuration file specified. +/// +/// This function is used to both configure the DHCP server on its startup +/// and dynamically reconfigure the server when SIGHUP signal is received. +/// +/// It fetches DHCPv6 server's configuration from the 'Dhcp6' section of +/// the JSON configuration file. +/// +/// @param file_name Configuration file location. +ConstElementPtr +ControlledDhcpv6Srv::loadConfigFile(const std::string& file_name) { + // This is a configuration backend implementation that reads the + // configuration from a JSON file. + + isc::data::ConstElementPtr json; + isc::data::ConstElementPtr dhcp6; + isc::data::ConstElementPtr logger; + isc::data::ConstElementPtr result; + + // Basic sanity check: file name must not be empty. + try { + if (file_name.empty()) { + // Basic sanity check: file name must not be empty. + isc_throw(isc::BadValue, "JSON configuration file not specified. Please " + "use -c command line option."); + } + + // Read contents of the file and parse it as JSON + Parser6Context parser; + json = parser.parseFile(file_name, Parser6Context::PARSER_DHCP6); + if (!json) { + isc_throw(isc::BadValue, "no configuration found"); + } + + // Let's do sanity check before we call json->get() which + // works only for map. + if (json->getType() != isc::data::Element::map) { + isc_throw(isc::BadValue, "Configuration file is expected to be " + "a map, i.e., start with { and end with } and contain " + "at least an entry called 'Dhcp6' that itself is a map. " + << file_name + << " is a valid JSON, but its top element is not a map." + " Did you forget to add { } around your configuration?"); + } + + // Use parsed JSON structures to configure the server + result = ControlledDhcpv6Srv::processCommand("config-set", 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-set\", json)"); + } + + // Now check is the returned result is successful (rcode=0) or not + // (see @ref isc::config::parseAnswer). + int rcode; + isc::data::ConstElementPtr comment = + isc::config::parseAnswer(rcode, result); + if (rcode != 0) { + string reason = comment ? comment->stringValue() : + "no details available"; + isc_throw(isc::BadValue, reason); + } + } catch (const std::exception& ex) { + // If configuration failed at any stage, we drop the staging + // configuration and continue to use the previous one. + CfgMgr::instance().rollback(); + + LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL) + .arg(file_name).arg(ex.what()); + isc_throw(isc::BadValue, "configuration error using file '" + << file_name << "': " << ex.what()); + } + + return (result); +} + + +void +ControlledDhcpv6Srv::init(const std::string& file_name) { + // Configure the server using JSON file. + ConstElementPtr result = loadConfigFile(file_name); + int rcode; + ConstElementPtr comment = isc::config::parseAnswer(rcode, result); + if (rcode != 0) { + string reason = comment ? comment->stringValue() : + "no details available"; + isc_throw(isc::BadValue, reason); + } + + // We don't need to call openActiveSockets() or startD2() as these + // methods are called in processConfig() which is called by + // processCommand("reload-config", ...) + + // Set signal handlers. When the SIGHUP is received by the process + // the server reconfiguration will be triggered. When SIGTERM or + // SIGINT will be received, the server will start shutting down. + signal_set_.reset(new isc::util::SignalSet(SIGINT, SIGHUP, SIGTERM)); + // Set the pointer to the handler function. + signal_handler_ = signalHandler; +} + +void ControlledDhcpv6Srv::cleanup() { + // Nothing to do here. No need to disconnect from anything. +} + + ConstElementPtr ControlledDhcpv6Srv::commandShutdownHandler(const string&, ConstElementPtr) { if (ControlledDhcpv6Srv::server_) { @@ -68,9 +199,22 @@ ControlledDhcpv6Srv::commandLibReloadHandler(const string&, ConstElementPtr) { } ConstElementPtr -ControlledDhcpv6Srv::commandConfigReloadHandler(const string&, ConstElementPtr args) { - // Use set-config as it handles logging and server config - return (commandSetConfigHandler("set-config", args)); +ControlledDhcpv6Srv::commandConfigReloadHandler(const string&, + ConstElementPtr /*args*/) { + // Get configuration file name. + std::string file = ControlledDhcpv6Srv::getInstance()->getConfigFile(); + try { + LOG_INFO(dhcp6_logger, DHCP6_DYNAMIC_RECONFIGURATION).arg(file); + return (loadConfigFile(file)); + } catch (const std::exception& ex) { + // Log the unsuccessful reconfiguration. The reason for failure + // should be already logged. Don't rethrow an exception so as + // the server keeps working. + LOG_ERROR(dhcp6_logger, DHCP6_DYNAMIC_RECONFIGURATION_FAIL) + .arg(file); + return (createAnswer(CONTROL_RESULT_ERROR, + "Config reload failed:" + string(ex.what()))); + } } ConstElementPtr @@ -319,7 +463,7 @@ ControlledDhcpv6Srv::processCommand(const std::string& command, } else if (command == "config-reload") { return (srv->commandConfigReloadHandler(command, args)); - } else if (command == "set-config") { + } else if (command == "config-set") { return (srv->commandSetConfigHandler(command, args)); } else if (command == "config-get") { @@ -517,7 +661,8 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port) CommandMgr::instance().registerCommand("config-get", boost::bind(&ControlledDhcpv6Srv::commandConfigGetHandler, this, _1, _2)); - /// @todo: register config-reload (see CtrlDhcpv6Srv::commandConfigReloadHandler) + CommandMgr::instance().registerCommand("config-reload", + boost::bind(&ControlledDhcpv6Srv::commandConfigReloadHandler, this, _1, _2)); CommandMgr::instance().registerCommand("config-test", boost::bind(&ControlledDhcpv6Srv::commandConfigTestHandler, this, _1, _2)); @@ -531,7 +676,7 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port) CommandMgr::instance().registerCommand("libreload", boost::bind(&ControlledDhcpv6Srv::commandLibReloadHandler, this, _1, _2)); - CommandMgr::instance().registerCommand("set-config", + CommandMgr::instance().registerCommand("config-set", boost::bind(&ControlledDhcpv6Srv::commandSetConfigHandler, this, _1, _2)); CommandMgr::instance().registerCommand("shutdown", @@ -580,11 +725,12 @@ ControlledDhcpv6Srv::~ControlledDhcpv6Srv() { // Deregister any registered commands (please keep in alphabetic order) CommandMgr::instance().deregisterCommand("build-report"); CommandMgr::instance().deregisterCommand("config-get"); + CommandMgr::instance().deregisterCommand("config-set"); + CommandMgr::instance().deregisterCommand("config-reload"); CommandMgr::instance().deregisterCommand("config-test"); CommandMgr::instance().deregisterCommand("config-write"); CommandMgr::instance().deregisterCommand("leases-reclaim"); CommandMgr::instance().deregisterCommand("libreload"); - CommandMgr::instance().deregisterCommand("set-config"); CommandMgr::instance().deregisterCommand("shutdown"); CommandMgr::instance().deregisterCommand("statistic-get"); CommandMgr::instance().deregisterCommand("statistic-get-all"); diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h index 881efc1e68..0395121a38 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.h +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.h @@ -34,15 +34,25 @@ public: /// @brief Initializes the server. /// - /// Depending on the configuration backend, it establishes msgq session, - /// reads the JSON file from disk or may perform any other setup - /// operation. For specific details, see actual implementation in - /// *_backend.cc + /// It reads the JSON file from disk or may perform any other setup + /// operation. In particular, it also install signal handlers. /// - /// This method may throw if initialization fails. Exception types may be - /// specific to used configuration backend. + /// This method may throw if initialization fails. void init(const std::string& config_file); + /// @brief loads specific configuration file + /// + /// This utility method is called whenever we know a filename of the config + /// and need to load it. It calls config-set command once the content of + /// the file has been loaded and verified to be a sane JSON configuration. + /// config-set handler will process the config file (apply it as current + /// configuration). + /// + /// @param file_name name of the file to be loaded + /// @return status of the file loading and outcome of config-set + isc::data::ConstElementPtr + loadConfigFile(const std::string& file_name); + /// @brief Performs cleanup, immediately before termination /// /// This method performs final clean up, just before the Dhcpv6Srv object @@ -63,7 +73,7 @@ public: /// - config-reload /// - config-test /// - leases-reclaim - /// - libreload + /// - libreload /// - shutdown /// ... /// @@ -218,7 +228,7 @@ private: /// This handler processes version-get command, which returns /// over the control channel the -v and -V command line arguments. /// @param command (parameter ignored) - /// @param args (parameter ignored) + /// @param args (parameter ignored) /// /// @return status of the command with the version in text and /// the extended version in arguments. @@ -231,7 +241,7 @@ private: /// This handler processes build-report command, which returns /// over the control channel the -W command line argument. /// @param command (parameter ignored) - /// @param args (parameter ignored) + /// @param args (parameter ignored) /// /// @return status of the command with the config report isc::data::ConstElementPtr diff --git a/src/bin/dhcp6/kea_controller.cc b/src/bin/dhcp6/kea_controller.cc deleted file mode 100644 index ae3466d3c9..0000000000 --- a/src/bin/dhcp6/kea_controller.cc +++ /dev/null @@ -1,162 +0,0 @@ -// 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 -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -using namespace isc::asiolink; -using namespace isc::dhcp; -using namespace std; - -namespace { - -/// @brief Configure DHCPv6 server using the configuration file specified. -/// -/// This function is used to both configure the DHCP server on its startup -/// and dynamically reconfigure the server when SIGHUP signal is received. -/// -/// It fetches DHCPv6 server's configuration from the 'Dhcp6' section of -/// the JSON configuration file. -/// -/// @param file_name Configuration file location. -void configure(const std::string& file_name) { - // This is a configuration backend implementation that reads the - // configuration from a JSON file. - - isc::data::ConstElementPtr json; - isc::data::ConstElementPtr dhcp6; - isc::data::ConstElementPtr logger; - isc::data::ConstElementPtr result; - - // Basic sanity check: file name must not be empty. - try { - if (file_name.empty()) { - // Basic sanity check: file name must not be empty. - isc_throw(isc::BadValue, "JSON configuration file not specified. Please " - "use -c command line option."); - } - - // Read contents of the file and parse it as JSON - Parser6Context parser; - json = parser.parseFile(file_name, Parser6Context::PARSER_DHCP6); - if (!json) { - isc_throw(isc::BadValue, "no configuration found"); - } - - // Let's do sanity check before we call json->get() which - // works only for map. - if (json->getType() != isc::data::Element::map) { - isc_throw(isc::BadValue, "Configuration file is expected to be " - "a map, i.e., start with { and end with } and contain " - "at least an entry called 'Dhcp6' that itself is a map. " - << file_name - << " is a valid JSON, but its top element is not a map." - " Did you forget to add { } around your configuration?"); - } - - // Use parsed JSON structures to configure the server - 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(\"set-config\", json)"); - } - - // Now check is the returned result is successful (rcode=0) or not - // (see @ref isc::config::parseAnswer). - int rcode; - isc::data::ConstElementPtr comment = - isc::config::parseAnswer(rcode, result); - if (rcode != 0) { - string reason = comment ? comment->stringValue() : - "no details available"; - isc_throw(isc::BadValue, reason); - } - } catch (const std::exception& ex) { - // If configuration failed at any stage, we drop the staging - // configuration and continue to use the previous one. - CfgMgr::instance().rollback(); - - LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL) - .arg(file_name).arg(ex.what()); - isc_throw(isc::BadValue, "configuration error using file '" - << file_name << "': " << ex.what()); - } -} - -/// @brief Signals handler for DHCPv6 server. -/// -/// This signal handler handles the following signals received by the DHCPv6 -/// server process: -/// - SIGHUP - triggers server's dynamic reconfiguration. -/// - SIGTERM - triggers server's shut down. -/// - SIGINT - triggers server's shut down. -/// -/// @param signo Signal number received. -void signalHandler(int signo) { - // SIGHUP signals a request to reconfigure the server. - if (signo == SIGHUP) { - // Get configuration file name. - std::string file = ControlledDhcpv6Srv::getInstance()->getConfigFile(); - try { - LOG_INFO(dhcp6_logger, DHCP6_DYNAMIC_RECONFIGURATION).arg(file); - configure(file); - } catch (const std::exception& ex) { - // Log the unsuccessful reconfiguration. The reason for failure - // should be already logged. Don't rethrow an exception so as - // the server keeps working. - LOG_ERROR(dhcp6_logger, DHCP6_DYNAMIC_RECONFIGURATION_FAIL) - .arg(file); - } - } else if ((signo == SIGTERM) || (signo == SIGINT)) { - isc::data::ElementPtr params(new isc::data::MapElement()); - ControlledDhcpv6Srv::processCommand("shutdown", params); - } -} - -} - -namespace isc { -namespace dhcp { - -void -ControlledDhcpv6Srv::init(const std::string& file_name) { - // Configure the server using JSON file. - configure(file_name); - - // We don't need to call openActiveSockets() or startD2() as these - // methods are called in processConfig() which is called by - // processCommand("reload-config", ...) - - // Set signal handlers. When the SIGHUP is received by the process - // the server reconfiguration will be triggered. When SIGTERM or - // SIGINT will be received, the server will start shutting down. - signal_set_.reset(new isc::util::SignalSet(SIGINT, SIGHUP, SIGTERM)); - // Set the pointer to the handler function. - signal_handler_ = signalHandler; -} - -void ControlledDhcpv6Srv::cleanup() { - // Nothing to do here. No need to disconnect from anything. -} - -}; -}; diff --git a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc index cd445c18f4..27b95e42d9 100644 --- a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc @@ -371,71 +371,13 @@ TEST_F(CtrlChannelDhcpv6SrvTest, libreload) { EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "1212")); } -// Check that the "configReload" command will reload libraries -TEST_F(CtrlDhcpv6SrvTest, configReload) { - - // Sending commands for processing now requires a server that can process - // them. - boost::scoped_ptr srv; - ASSERT_NO_THROW( - srv.reset(new ControlledDhcpv6Srv(0)) - ); - - // Now execute the "config-reload" command. This should cause the libraries - // to unload and to reload. - - // Use empty parameters list - // Prepare configuration file. - string config_txt = "{ \"Dhcp6\": { \"interfaces-config\": {" - " \"interfaces\": [ \"*\" ]" - "}," - "\"preferred-lifetime\": 3000," - "\"rebind-timer\": 2000, " - "\"renew-timer\": 1000, " - "\"subnet6\": [ { " - " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ]," - " \"subnet\": \"2001:db8:1::/64\" " - " }," - " {" - " \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ]," - " \"subnet\": \"2001:db8:2::/64\", " - " \"id\": 0" - " }," - " {" - " \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ]," - " \"subnet\": \"2001:db8:3::/64\" " - " } ]," - "\"valid-lifetime\": 4000 }}"; - - ConstElementPtr config; - ASSERT_NO_THROW(config = parseJSON(config_txt)); - - // Make sure there are no subnets configured. - CfgMgr::instance().clear(); - - // Now send the command - int rcode = -1; - ConstElementPtr result = - ControlledDhcpv6Srv::processCommand("config-reload", config); - ConstElementPtr comment = isc::config::parseAnswer(rcode, result); - EXPECT_EQ(0, rcode); // Expect success - - // Check that the config was indeed applied. - const Subnet6Collection* subnets = - 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 +// Check that the "config-set" command will replace current configuration TEST_F(CtrlChannelDhcpv6SrvTest, configSet) { 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 set_config_txt = "{ \"command\": \"config-set\" \n"; string args_txt = " \"arguments\": { \n"; string dhcp6_cfg_txt = " \"Dhcp6\": { \n" @@ -501,7 +443,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configSet) { << logger_txt << "}}"; - // Send the set-config command + // Send the config-set command std::string response; sendUnixCommand(os.str(), response); @@ -527,7 +469,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configSet) { << "}\n" // close dhcp6 "}}"; - // Send the set-config command + // Send the config-set command sendUnixCommand(os.str(), response); // Should fail with a syntax error @@ -555,7 +497,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configSet) { // Verify the control channel socket exists. ASSERT_TRUE(fileExists(socket_path_)); - // Send the set-config command. + // Send the config-set command. sendUnixCommand(os.str(), response); // Verify the control channel socket no longer exists. @@ -579,7 +521,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configTest) { // 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 set_config_txt = "{ \"command\": \"config-set\" \n"; string config_test_txt = "{ \"command\": \"config-test\" \n"; string args_txt = " \"arguments\": { \n"; string dhcp6_cfg_txt = @@ -646,7 +588,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configTest) { << logger_txt << "}}"; - // Send the set-config command + // Send the config-set command std::string response; sendUnixCommand(os.str(), response); @@ -753,7 +695,7 @@ TEST_F(CtrlDhcpv6SrvTest, commandsRegistration) { EXPECT_TRUE(command_list.find("\"config-write\"") != string::npos); EXPECT_TRUE(command_list.find("\"leases-reclaim\"") != string::npos); EXPECT_TRUE(command_list.find("\"libreload\"") != string::npos); - EXPECT_TRUE(command_list.find("\"set-config\"") != string::npos); + EXPECT_TRUE(command_list.find("\"config-set\"") != string::npos); EXPECT_TRUE(command_list.find("\"shutdown\"") != string::npos); EXPECT_TRUE(command_list.find("\"statistic-get\"") != string::npos); EXPECT_TRUE(command_list.find("\"statistic-get-all\"") != string::npos); @@ -982,12 +924,12 @@ TEST_F(CtrlChannelDhcpv6SrvTest, commandsList) { // We expect the server to report at least the following commands: checkListCommands(rsp, "build-report"); checkListCommands(rsp, "config-get"); + checkListCommands(rsp, "config-set"); checkListCommands(rsp, "config-test"); checkListCommands(rsp, "config-write"); checkListCommands(rsp, "list-commands"); checkListCommands(rsp, "leases-reclaim"); checkListCommands(rsp, "libreload"); - checkListCommands(rsp, "set-config"); checkListCommands(rsp, "version-get"); checkListCommands(rsp, "shutdown"); checkListCommands(rsp, "statistic-get"); @@ -1085,4 +1027,94 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configWriteInvalidEscape) { "Using \\ in filename is not allowed."); } +// Tests if config-reload attempts to reload a file and reports that the +// file is missing. +TEST_F(CtrlChannelDhcpv6SrvTest, configReloadMissingFile) { + createUnixChannelServer(); + std::string response; + + // This is normally set to whatever value is passed to -c when the server is + // started, but we're not starting it that way, so need to set it by hand. + server_->setConfigFile("test6.json"); + + // Tell the server to reload its configuration. It should attempt to load + // test6.json (and fail, because the file is not there). + sendUnixCommand("{ \"command\": \"config-reload\" }", response); + + // Verify the reload was rejected. + EXPECT_EQ("{ \"result\": 1, \"text\": \"Config reload failed:" + "configuration error using file 'test6.json': Unable to open file " + "test6.json\" }", + response); +} + +// Tests if config-reload attempts to reload a file and reports that the +// file is not a valid JSON. +TEST_F(CtrlChannelDhcpv6SrvTest, configReloadBrokenFile) { + createUnixChannelServer(); + std::string response; + + // This is normally set to whatever value is passed to -c when the server is + // started, but we're not starting it that way, so need to set it by hand. + server_->setConfigFile("test7.json"); + + // Although Kea is smart, its AI routines are not smart enough to handle + // this one... at least not yet. + ofstream f("test7.json", ios::trunc); + f << "gimme some addr, bro!"; + f.close(); + + // Now tell Kea to reload its config. + sendUnixCommand("{ \"command\": \"config-reload\" }", response); + + // Verify the reload will fail. + EXPECT_EQ("{ \"result\": 1, \"text\": \"Config reload failed:" + "configuration error using file 'test7.json': " + "test7.json:1.1: Invalid character: g\" }", + response); + + ::remove("test7.json"); +} + +// Tests if config-reload attempts to reload a file and reports that the +// file is loaded correctly. +TEST_F(CtrlChannelDhcpv6SrvTest, configReloadValid) { + createUnixChannelServer(); + std::string response; + + // This is normally set to whatever value is passed to -c when the server is + // started, but we're not starting it that way, so need to set it by hand. + server_->setConfigFile("test8.json"); + + // Ok, enough fooling around. Let's create a valid config. + const std::string cfg_txt = + "{ \"Dhcp6\": {" + " \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + " }," + " \"subnet6\": [" + " { \"subnet\": \"2001:db8:1::/64\" }," + " { \"subnet\": \"2001:db8:2::/64\" }" + " ]," + " \"lease-database\": {" + " \"type\": \"memfile\", \"persist\": false }" + "} }"; + ofstream f("test8.json", ios::trunc); + f << cfg_txt; + f.close(); + + // This command should reload test8.json config. + sendUnixCommand("{ \"command\": \"config-reload\" }", 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(2, subnets->size()); + + ::remove("test8.json"); +} + } // End of anonymous namespace -- 2.47.3