From: Tomek Mrugalski Date: Wed, 21 May 2014 14:16:18 +0000 (+0200) Subject: [3399] Bundy controller implemented. X-Git-Tag: trac3434_base~15^2~13 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=d8ebb8039b9a0c6136d3fb79c664e7316ac2c834;p=thirdparty%2Fkea.git [3399] Bundy controller implemented. --- diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am index 735f9f022a..c7f7d5c7ed 100644 --- a/src/bin/dhcp4/Makefile.am +++ b/src/bin/dhcp4/Makefile.am @@ -51,10 +51,18 @@ pkglibexec_PROGRAMS = b10-dhcp4 b10_dhcp4_SOURCES = main.cc b10_dhcp4_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h -b10_dhcp4_SOURCES += config_parser.cc config_parser.h +b10_dhcp4_SOURCES += json_config_parser.cc json_config_parser.h b10_dhcp4_SOURCES += dhcp4_log.cc dhcp4_log.h b10_dhcp4_SOURCES += dhcp4_srv.cc dhcp4_srv.h +if CONFIG_BACKEND_BUNDY +b10_dhcp4_SOURCES += bundy_controller.cc +endif + +if CONFIG_BACKEND_JSON +b10_dhcp4_SOURCES += kea_controller.cc +endif + nodist_b10_dhcp4_SOURCES = dhcp4_messages.h dhcp4_messages.cc EXTRA_DIST += dhcp4_messages.mes diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/bundy_controller.cc similarity index 58% rename from src/bin/dhcp4/ctrl_dhcp4_srv.cc rename to src/bin/dhcp4/bundy_controller.cc index 8c522569c8..dec9689843 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/bundy_controller.cc @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include #include @@ -48,10 +48,33 @@ using namespace std; namespace isc { namespace dhcp { -ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL; - +/// @brief Helper session object that represents raw connection to msgq. +isc::cc::Session* cc_session_ = NULL; + +/// @brief Session that receives configuration and commands +isc::config::ModuleCCSession* config_session_ = NULL; + +/// @brief A dummy configuration handler that always returns success. +/// +/// This configuration handler does not perform configuration +/// parsing and always returns success. A dummy handler should +/// be installed using \ref isc::config::ModuleCCSession ctor +/// to get the initial configuration. This initial configuration +/// comprises values for only those elements that were modified +/// the previous session. The \ref dhcp4ConfigHandler can't be +/// used to parse the initial configuration because it needs the +/// full configuration to satisfy dependencies between the +/// various configuration values. Installing the dummy handler +/// that guarantees to return success causes initial configuration +/// to be stored for the session being created and that it can +/// be later accessed with +/// \ref isc::config::ConfigData::getFullConfig(). +/// +/// @param new_config new configuration. +/// +/// @return success configuration status. ConstElementPtr -ControlledDhcpv4Srv::dhcp4StubConfigHandler(ConstElementPtr) { +dhcp4StubConfigHandler(ConstElementPtr) { // This configuration handler is intended to be used only // when the initial configuration comes in. To receive this // configuration a pointer to this handler must be passed @@ -66,8 +89,8 @@ ControlledDhcpv4Srv::dhcp4StubConfigHandler(ConstElementPtr) { } ConstElementPtr -ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) { - if (!server_ || !server_->config_session_) { +bundyConfigHandler(ConstElementPtr new_config) { + if (!ControlledDhcpv4Srv::getInstance() || !config_session_) { // That should never happen as we install config_handler // after we instantiate the server. ConstElementPtr answer = @@ -91,7 +114,7 @@ ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) { // Let's create a new object that will hold the merged configuration. boost::shared_ptr merged_config(new MapElement()); // Let's get the existing configuration. - ConstElementPtr full_config = server_->config_session_->getFullConfig(); + ConstElementPtr full_config = config_session_->getFullConfig(); // The full_config and merged_config should be always non-NULL // but to provide some level of exception safety we check that they // really are (in case we go out of memory). @@ -105,88 +128,13 @@ ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) { } // Configure the server. - ConstElementPtr answer = configureDhcp4Server(*server_, merged_config); - - // Check that configuration was successful. If not, do not reopen sockets. - int rcode = 0; - parseAnswer(rcode, answer); - if (rcode != 0) { - return (answer); - } - - // Server will start DDNS communications if its enabled. - try { - server_->startD2(); - } catch (const std::exception& ex) { - std::ostringstream err; - err << "error starting DHCP_DDNS client " - " after server reconfiguration: " << ex.what(); - return (isc::config::createAnswer(1, err.str())); - } - - // Configuration may change active interfaces. Therefore, we have to reopen - // sockets according to new configuration. This operation is not exception - // safe and we really don't want to emit exceptions to the callback caller. - // Instead, catch an exception and create appropriate answer. - try { - server_->openActiveSockets(server_->getPort(), server_->useBroadcast()); - } catch (std::exception& ex) { - std::ostringstream err; - err << "failed to open sockets after server reconfiguration: " << ex.what(); - answer = isc::config::createAnswer(1, err.str()); - } - return (answer); + return (ControlledDhcpv4Srv::processConfig(merged_config)); } -ConstElementPtr -ControlledDhcpv4Srv::dhcp4CommandHandler(const string& command, ConstElementPtr args) { - LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED) - .arg(command).arg(args->str()); - - if (command == "shutdown") { - if (ControlledDhcpv4Srv::server_) { - ControlledDhcpv4Srv::server_->shutdown(); - } else { - LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING); - ConstElementPtr answer = isc::config::createAnswer(1, - "Shutdown failure."); - return (answer); - } - ConstElementPtr answer = isc::config::createAnswer(0, - "Shutting down."); - return (answer); - - } else if (command == "libreload") { - // TODO delete any stored CalloutHandles referring to the old libraries - // Get list of currently loaded libraries and reload them. - vector loaded = HooksManager::getLibraryNames(); - bool status = HooksManager::loadLibraries(loaded); - if (!status) { - LOG_ERROR(dhcp4_logger, DHCP4_HOOKS_LIBS_RELOAD_FAIL); - ConstElementPtr answer = isc::config::createAnswer(1, - "Failed to reload hooks libraries."); - return (answer); - } - ConstElementPtr answer = isc::config::createAnswer(0, - "Hooks libraries successfully reloaded."); - return (answer); - } - - ConstElementPtr answer = isc::config::createAnswer(1, - "Unrecognized command."); - return (answer); -} -void ControlledDhcpv4Srv::sessionReader(void) { - // Process one asio event. If there are more events, iface_mgr will call - // this callback more than once. - if (server_) { - server_->io_service_.run_one(); - } -} -void ControlledDhcpv4Srv::establishSession() { +void ControlledDhcpv4Srv::init(const std::string& /*config_file*/) { string specfile; if (getenv("B10_FROM_BUILD")) { @@ -210,14 +158,14 @@ void ControlledDhcpv4Srv::establishSession() { // been lost. config_session_ = new ModuleCCSession(specfile, *cc_session_, dhcp4StubConfigHandler, - dhcp4CommandHandler, false); + processCommand, false); config_session_->start(); // We initially create ModuleCCSession() without configHandler, as // the session module is too eager to send partial configuration. // We want to get the full configuration, so we explicitly call // getFullConfig() and then pass it to our configHandler. - config_session_->setConfigHandler(dhcp4ConfigHandler); + config_session_->setConfigHandler(bundyConfigHandler); try { configureDhcp4Server(*this, config_session_->getFullConfig()); @@ -243,7 +191,8 @@ void ControlledDhcpv4Srv::establishSession() { IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader); } -void ControlledDhcpv4Srv::disconnectSession() { + +void ControlledDhcpv4Srv::cleanup() { if (config_session_) { delete config_session_; config_session_ = NULL; @@ -259,32 +208,11 @@ void ControlledDhcpv4Srv::disconnectSession() { } } -ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/) - :Dhcpv4Srv(port), cc_session_(NULL), config_session_(NULL) { - server_ = this; // remember this instance for use in callback -} - -void ControlledDhcpv4Srv::shutdown() { - io_service_.stop(); // Stop ASIO transmissions - Dhcpv4Srv::shutdown(); // Initiate DHCPv4 shutdown procedure. -} - -ControlledDhcpv4Srv::~ControlledDhcpv4Srv() { - disconnectSession(); - - server_ = NULL; // forget this instance. There should be no callback anymore - // at this stage anyway. -} - -isc::data::ConstElementPtr -ControlledDhcpv4Srv::execDhcpv4ServerCommand(const std::string& command_id, - isc::data::ConstElementPtr args) { - try { - return (dhcp4CommandHandler(command_id, args)); - } catch (const Exception& ex) { - ConstElementPtr answer = isc::config::createAnswer(1, ex.what()); - return (answer); - } +void +Daemon::loggerInit(const char* log_name, bool verbose, bool stand_alone) { + isc::log::initLogger(log_name, + (verbose ? isc::log::DEBUG : isc::log::INFO), + isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone); } }; diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h index 59dde870ee..25db7ac08f 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.h +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC") // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -46,44 +46,51 @@ public: /// @brief Destructor. ~ControlledDhcpv4Srv(); - /// @brief Establishes msgq session. + /// @brief Initializes the server. /// - /// Creates session that will be used to receive commands and updated - /// configuration from cfgmgr (or indirectly from user via bindctl). - void establishSession(); + /// 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 + /// + /// @return true if initialization was successful, false if it failed + void init(const std::string& config_file); - /// @brief Terminates existing msgq session. + /// @brief Performs cleanup, immediately before termination /// - /// This method terminates existing session with msgq. After calling + /// This method performs final clean up, just before the Dhcpv6Srv object + /// is destroyed. The actual behavior is backend dependent. For Bundy + /// backend, it terminates existing session with msgq. After calling /// it, no further messages over msgq (commands or configuration updates) - /// may be received. + /// may be received. For JSON backend, it is no-op. /// - /// It is ok to call this method when session is disconnected already. - void disconnectSession(); + /// For specific details, see actual implementation in *_backend.cc + void cleanup(); /// @brief Initiates shutdown procedure for the whole DHCPv4 server. void shutdown(); - /// @brief Session callback, processes received commands. + /// @brief command processor + /// + /// This method is uniform for all config backends. It processes received + /// command (as a string + JSON arguments). Internally, it's just a + /// wrapper that calls process*Command() methods and catches exceptions + /// in them. + /// + /// @note It never throws. /// /// @param command Text represenation of the command (e.g. "shutdown") /// @param args Optional parameters - /// @param command text represenation of the command (e.g. "shutdown") - /// @param args optional parameters /// /// @return status of the command static isc::data::ConstElementPtr - execDhcpv4ServerCommand(const std::string& command, - isc::data::ConstElementPtr args); + processCommand(const std::string& command, isc::data::ConstElementPtr args); -protected: - /// @brief Static pointer to the sole instance of the DHCP server. + /// @brief configuration processor /// - /// This is required for config and command handlers to gain access to - /// the server - static ControlledDhcpv4Srv* server_; - - /// @brief A callback for handling incoming configuration updates. + /// This is a callback for handling incoming configuration updates. + /// This method should be called by all configuration backends when the + /// server is starting up or when configuration has changed. /// /// As pointer to this method is used a callback in ASIO used in /// ModuleCCSession, it has to be static. @@ -92,54 +99,72 @@ protected: /// /// @return status of the config update static isc::data::ConstElementPtr - dhcp4ConfigHandler(isc::data::ConstElementPtr new_config); - - /// @brief A dummy configuration handler that always returns success. - /// - /// This configuration handler does not perform configuration - /// parsing and always returns success. A dummy handler should - /// be installed using \ref isc::config::ModuleCCSession ctor - /// to get the initial configuration. This initial configuration - /// comprises values for only those elements that were modified - /// the previous session. The \ref dhcp4ConfigHandler can't be - /// used to parse the initial configuration because it needs the - /// full configuration to satisfy dependencies between the - /// various configuration values. Installing the dummy handler - /// that guarantees to return success causes initial configuration - /// to be stored for the session being created and that it can - /// be later accessed with - /// \ref isc::config::ConfigData::getFullConfig(). - /// - /// @param new_config new configuration. - /// - /// @return success configuration status. - static isc::data::ConstElementPtr - dhcp4StubConfigHandler(isc::data::ConstElementPtr new_config); + processConfig(isc::data::ConstElementPtr new_config); - /// @brief A callback for handling incoming commands. + /// @brief returns pointer to the sole instance of Dhcpv4Srv /// - /// @param command textual representation of the command - /// @param args parameters of the command + /// @note may return NULL, if called before server is spawned + static ControlledDhcpv4Srv* getInstance() { + return (server_); + } + + +protected: + /// @brief Static pointer to the sole instance of the DHCP server. /// - /// @return status of the processed command - static isc::data::ConstElementPtr - dhcp4CommandHandler(const std::string& command, isc::data::ConstElementPtr args); + /// This is required for config and command handlers to gain access to + /// the server + static ControlledDhcpv4Srv* server_; - /// @brief Callback that will be called from iface_mgr when command/config arrives. + /// @brief Callback that will be called from iface_mgr when data + /// is received over control socket. /// - /// This static callback method is called from IfaceMgr::receive4() method, - /// when there is a new command or configuration sent over msgq. + /// This static callback method is called from IfaceMgr::receive6() method, + /// when there is a new command or configuration sent over control socket + /// (that was sent from msgq if backend is Bundy, or some yet unspecified + /// sender if the backend is JSON file). static void sessionReader(void); - /// @brief IOService object, used for all ASIO operations. isc::asiolink::IOService io_service_; - /// @brief Helper session object that represents raw connection to msgq. - isc::cc::Session* cc_session_; + /// @brief handler for processing 'shutdown' command + /// + /// This handler processes shutdown command, which initializes shutdown + /// procedure. + /// @param command (parameter ignored) + /// @param args (parameter ignored) + /// + /// @return status of the command + isc::data::ConstElementPtr + commandShutdownHandler(const std::string& command, + isc::data::ConstElementPtr args); + + /// @brief handler for processing 'libreload' command + /// + /// This handler processes libreload command, which unloads all hook + /// libraries and reloads them. + /// + /// @param command (parameter ignored) + /// @param args (parameter ignored) + /// + /// @return status of the command + isc::data::ConstElementPtr + commandLibReloadHandler(const std::string& command, + isc::data::ConstElementPtr args); - /// @brief Session that receives configuration and commands - isc::config::ModuleCCSession* config_session_; + /// @brief handler for processing 'config-reload' command + /// + /// This handler processes config-reload command, which processes + /// configuration specified in args parameter. + /// + /// @param command (parameter ignored) + /// @param args configuration to be processed + /// + /// @return status of the command + isc::data::ConstElementPtr + commandConfigReloadHandler(const std::string& command, + isc::data::ConstElementPtr args); }; }; // namespace isc::dhcp diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h index d041258394..0a0441aac5 100644 --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC") // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -57,7 +58,7 @@ public: /// /// For detailed explanation or relations between main(), ControlledDhcpv4Srv, /// Dhcpv4Srv and other classes, see \ref dhcpv4Session. -class Dhcpv4Srv : public boost::noncopyable { +class Dhcpv4Srv : public Daemon { public: diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/json_config_parser.cc similarity index 99% rename from src/bin/dhcp4/config_parser.cc rename to src/bin/dhcp4/json_config_parser.cc index 0e86217309..e6606312ec 100644 --- a/src/bin/dhcp4/config_parser.cc +++ b/src/bin/dhcp4/json_config_parser.cc @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/bin/dhcp4/config_parser.h b/src/bin/dhcp4/json_config_parser.h similarity index 100% rename from src/bin/dhcp4/config_parser.h rename to src/bin/dhcp4/json_config_parser.h diff --git a/src/bin/dhcp4/main.cc b/src/bin/dhcp4/main.cc index 45f1de1612..1508995d4e 100644 --- a/src/bin/dhcp4/main.cc +++ b/src/bin/dhcp4/main.cc @@ -39,6 +39,8 @@ namespace { const char* const DHCP4_NAME = "b10-dhcp4"; +const char* const DHCP4_LOGGER_NAME = "kea"; + void usage() { cerr << "Usage: " << DHCP4_NAME << " [-v] [-s] [-p number]" << endl; @@ -46,6 +48,7 @@ usage() { cerr << " -s: stand-alone mode (don't connect to BIND10)" << endl; cerr << " -p number: specify non-standard port number 1-65535 " << "(useful for testing only)" << endl; + cerr << " -c file: specify configuration file" << endl; exit(EXIT_FAILURE); } } // end of anonymous namespace @@ -58,7 +61,10 @@ main(int argc, char* argv[]) { bool stand_alone = false; // Should be connect to BIND10 msgq? bool verbose_mode = false; // Should server be verbose? - while ((ch = getopt(argc, argv, "vsp:")) != -1) { + // The standard config file + std::string config_file(""); + + while ((ch = getopt(argc, argv, "vsp:c:")) != -1) { switch (ch) { case 'v': verbose_mode = true; @@ -83,6 +89,10 @@ main(int argc, char* argv[]) { } break; + case 'c': // config file + config_file = optarg; + break; + default: usage(); } @@ -93,31 +103,35 @@ main(int argc, char* argv[]) { usage(); } - // Initialize logging. If verbose, we'll use maximum verbosity. - // If standalone is enabled, do not buffer initial log messages - isc::log::initLogger(DHCP4_NAME, - (verbose_mode ? isc::log::DEBUG : isc::log::INFO), - isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone); - LOG_INFO(dhcp4_logger, DHCP4_STARTING); - LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO) - .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no") - .arg(stand_alone ? "yes" : "no" ); - - int ret = EXIT_SUCCESS; + try { + // Initialize logging. If verbose, we'll use maximum verbosity. + // If standalone is enabled, do not buffer initial log messages + Daemon::loggerInit(DHCP4_LOGGER_NAME, verbose_mode, stand_alone); + LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO) + .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no") + .arg(stand_alone ? "yes" : "no" ); + + LOG_INFO(dhcp4_logger, DHCP4_STARTING); + ControlledDhcpv4Srv server(port_number); + if (!stand_alone) { + try { - server.establishSession(); + server.init(config_file); } catch (const std::exception& ex) { LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what()); - // Let's continue. It is useful to have the ability to run - // DHCP server in stand-alone mode, e.g. for testing - // We do need to make sure logging is no longer buffered - // since then it would not print until dhcp6 is stopped + + // We should not continue if were told to configure (either read + // config file or establish Bundy control session). + isc::log::LoggerManager log_manager; log_manager.process(); + + cerr << "Failed to initialize server: " << ex.what() << endl; + return (EXIT_FAILURE); } } else { LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_STANDALONE); diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am index 1961883706..95c7921a39 100644 --- a/src/bin/dhcp4/tests/Makefile.am +++ b/src/bin/dhcp4/tests/Makefile.am @@ -77,7 +77,7 @@ TESTS += dhcp4_unittests dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc -dhcp4_unittests_SOURCES += ../config_parser.cc ../config_parser.h +dhcp4_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h dhcp4_unittests_SOURCES += d2_unittest.h d2_unittest.cc dhcp4_unittests_SOURCES += dhcp4_test_utils.h dhcp4_unittests_SOURCES += dhcp4_unittests.cc @@ -89,6 +89,19 @@ dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc dhcp4_unittests_SOURCES += config_parser_unittest.cc dhcp4_unittests_SOURCES += fqdn_unittest.cc dhcp4_unittests_SOURCES += marker_file.cc + +if CONFIG_BACKEND_BUNDY +# For Bundy backend, we only need to run the usual tests. There are no +# Bundy-specific tests yet. +dhcp4_unittests_SOURCES += ../bundy_controller.cc +dhcp4_unittests_SOURCES += bundy_controller_unittest.cc +endif + +if CONFIG_BACKEND_JSON +dhcp4_unittests_SOURCES += ../kea_controller.cc +dhcp4_unittests_SOURCES += kea_controller_unittest.cc +endif + nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc nodist_dhcp4_unittests_SOURCES += marker_file.h test_libraries.h diff --git a/src/bin/dhcp4/tests/bundy_controller_unittest.cc b/src/bin/dhcp4/tests/bundy_controller_unittest.cc new file mode 100644 index 0000000000..f556560a14 --- /dev/null +++ b/src/bin/dhcp4/tests/bundy_controller_unittest.cc @@ -0,0 +1,26 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include + +#include + +namespace { + +// Bundy framework specific tests should be added here. +TEST(BundyBackendTest, dummy) { + +} + +} // End of anonymous namespace diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc index 2afee03f59..ab33dc00b7 100644 --- a/src/bin/dhcp4/tests/config_parser_unittest.cc +++ b/src/bin/dhcp4/tests/config_parser_unittest.cc @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include #include diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc index e6b500b663..5f111b4fb9 100644 --- a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc @@ -85,12 +85,12 @@ TEST_F(CtrlDhcpv4SrvTest, commands) { int rcode = -1; // Case 1: send bogus command - ConstElementPtr result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("blah", params); + ConstElementPtr result = ControlledDhcpv4Srv::processCommand("blah", params); ConstElementPtr comment = parseAnswer(rcode, result); EXPECT_EQ(1, rcode); // expect failure (no such command as blah) // Case 2: send shutdown command without any parameters - result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params); + result = ControlledDhcpv4Srv::processCommand("shutdown", params); comment = parseAnswer(rcode, result); EXPECT_EQ(0, rcode); // expect success @@ -99,7 +99,7 @@ TEST_F(CtrlDhcpv4SrvTest, commands) { params->set("pid", x); // Case 3: send shutdown command with 1 parameter: pid - result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params); + result = ControlledDhcpv4Srv::processCommand("shutdown", params); comment = parseAnswer(rcode, result); EXPECT_EQ(0, rcode); // expect success } @@ -107,6 +107,14 @@ TEST_F(CtrlDhcpv4SrvTest, commands) { // Check that the "libreload" command will reload libraries TEST_F(CtrlDhcpv4SrvTest, libreload) { + + // Sending commands for processing now requires a server that can process + // them. + boost::scoped_ptr srv; + ASSERT_NO_THROW( + srv.reset(new ControlledDhcpv4Srv(0)) + ); + // Ensure no marker files to start with. ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE)); ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE)); @@ -137,7 +145,7 @@ TEST_F(CtrlDhcpv4SrvTest, libreload) { int rcode = -1; ConstElementPtr result = - ControlledDhcpv4Srv::execDhcpv4ServerCommand("libreload", params); + ControlledDhcpv4Srv::processCommand("libreload", params); ConstElementPtr comment = parseAnswer(rcode, result); EXPECT_EQ(0, rcode); // Expect success diff --git a/src/bin/dhcp4/tests/d2_unittest.cc b/src/bin/dhcp4/tests/d2_unittest.cc index baac41fdb8..d5e5bc3c09 100644 --- a/src/bin/dhcp4/tests/d2_unittest.cc +++ b/src/bin/dhcp4/tests/d2_unittest.cc @@ -13,7 +13,7 @@ // PERFORMANCE OF THIS SOFTWARE. #include -#include +#include #include #include diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc index a332cbb5eb..4a5b0236d7 100644 --- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.cc b/src/bin/dhcp4/tests/dhcp4_test_utils.cc index ab1afc642f..57348b703c 100644 --- a/src/bin/dhcp4/tests/dhcp4_test_utils.cc +++ b/src/bin/dhcp4/tests/dhcp4_test_utils.cc @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/bin/dhcp4/tests/direct_client_unittest.cc b/src/bin/dhcp4/tests/direct_client_unittest.cc index aacfdc79a1..ae650a44e8 100644 --- a/src/bin/dhcp4/tests/direct_client_unittest.cc +++ b/src/bin/dhcp4/tests/direct_client_unittest.cc @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/bin/dhcp4/tests/kea_controller_unittest.cc b/src/bin/dhcp4/tests/kea_controller_unittest.cc new file mode 100644 index 0000000000..aa5e5e8566 --- /dev/null +++ b/src/bin/dhcp4/tests/kea_controller_unittest.cc @@ -0,0 +1,236 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::config; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::hooks; + +namespace { + +class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv { + // "Naked" DHCPv4 server, exposes internal fields +public: + NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(0) { } +}; + + +class JSONFileBackendTest : public ::testing::Test { +public: + JSONFileBackendTest() { + } + + ~JSONFileBackendTest() { + static_cast(unlink(TEST_FILE)); + }; + + void writeFile(const std::string& file_name, const std::string& content) { + static_cast(unlink(file_name.c_str())); + + ofstream out(file_name.c_str(), ios::trunc); + EXPECT_TRUE(out.is_open()); + out << content; + out.close(); + } + + static const char* TEST_FILE; +}; + +const char* JSONFileBackendTest::TEST_FILE = "test-config.json"; + +// This test checks if configuration can be read from a JSON file. +TEST_F(JSONFileBackendTest, jsonFile) { + + // Prepare configuration file. + string config = "{ \"interfaces\": [ \"*\" ]," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet4\": [ { " + " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ]," + " \"subnet\": \"192.0.2.0/24\" " + " }," + " {" + " \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ]," + " \"subnet\": \"192.0.3.0/24\", " + " \"id\": 0 " + " }," + " {" + " \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ]," + " \"subnet\": \"192.0.4.0/24\" " + " } ]," + "\"valid-lifetime\": 4000 }"; + + writeFile(TEST_FILE, config); + + // Now initialize the server + boost::scoped_ptr srv; + ASSERT_NO_THROW( + srv.reset(new ControlledDhcpv4Srv(0)) + ); + + // And configure it using the config file. + EXPECT_NO_THROW(srv->init(TEST_FILE)); + + // Now check if the configuration has been applied correctly. + const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4(); + ASSERT_TRUE(subnets); + ASSERT_EQ(3, subnets->size()); // We expect 3 subnets. + + + // Check subnet 1. + EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText()); + EXPECT_EQ(24, subnets->at(0)->get().second); + + // Check pools in the first subnet. + const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_V4); + ASSERT_EQ(1, pools1.size()); + EXPECT_EQ("192.0.2.1", pools1.at(0)->getFirstAddress().toText()); + EXPECT_EQ("192.0.2.100", pools1.at(0)->getLastAddress().toText()); + EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType()); + + // Check subnet 2. + EXPECT_EQ("192.0.3.0", subnets->at(1)->get().first.toText()); + EXPECT_EQ(24, subnets->at(1)->get().second); + + // Check pools in the second subnet. + const PoolCollection& pools2 = subnets->at(1)->getPools(Lease::TYPE_V4); + ASSERT_EQ(1, pools2.size()); + EXPECT_EQ("192.0.3.101", pools2.at(0)->getFirstAddress().toText()); + EXPECT_EQ("192.0.3.150", pools2.at(0)->getLastAddress().toText()); + EXPECT_EQ(Lease::TYPE_V4, pools2.at(0)->getType()); + + // And finally check subnet 3. + EXPECT_EQ("192.0.4.0", subnets->at(2)->get().first.toText()); + EXPECT_EQ(24, subnets->at(2)->get().second); + + // ... and it's only pool. + const PoolCollection& pools3 = subnets->at(2)->getPools(Lease::TYPE_V4); + EXPECT_EQ("192.0.4.101", pools3.at(0)->getFirstAddress().toText()); + EXPECT_EQ("192.0.4.150", pools3.at(0)->getLastAddress().toText()); + EXPECT_EQ(Lease::TYPE_NA, pools3.at(0)->getType()); +} + +// This test checks if configuration can be read from a JSON file. +TEST_F(JSONFileBackendTest, comments) { + + string config_hash_comments = "# This is a comment. It should be \n" + "#ignored. Real config starts in line below\n" + "{ \"Dhcp4\": { \"interfaces\": [ \"*\" ]," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, \n" + "# comments in the middle should be ignored, too\n" + "\"subnet4\": [ { " + " \"pool\": [ \"192.0.2.0/24\" ]," + " \"subnet\": \"192.0.2.0/22\" " + " } ]," + "\"valid-lifetime\": 4000 }" + "}"; + + /// @todo: Implement C++-style (// ...) comments + /// @todo: Implement C-style (/* ... */) comments + + writeFile(TEST_FILE, config_hash_comments); + + // Now initialize the server + boost::scoped_ptr srv; + ASSERT_NO_THROW( + srv.reset(new ControlledDhcpv4Srv(0)) + ); + + // And configure it using config without + EXPECT_NO_THROW(srv->init(TEST_FILE)); + + // Now check if the configuration has been applied correctly. + const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4(); + ASSERT_TRUE(subnets); + ASSERT_EQ(1, subnets->size()); + + // Check subnet 1. + EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText()); + EXPECT_EQ(22, subnets->at(0)->get().second); + + // Check pools in the first subnet. + const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA); + ASSERT_EQ(1, pools1.size()); + EXPECT_EQ("192.0.2.0", pools1.at(0)->getFirstAddress().toText()); + EXPECT_EQ("192.0.2.255", pools1.at(0)->getLastAddress().toText()); + EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType()); +} + +// This test checks if configuration detects failure when trying: +// - empty file +// - empty filename +// - no Dhcp4 element +// - Config file that contains Dhcp4 but has a content error +TEST_F(JSONFileBackendTest, configBroken) { + + // Empty config is not allowed, because Dhcp4 element is missing + string config_empty = ""; + + // This config does not have mandatory Dhcp4 element + string config_v4 = "{ \"Dhcp6\": { \"interfaces\": [ \"*\" ]," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet4\": [ { " + " \"pool\": [ \"2001:db8::/80\" ]," + " \"subnet\": \"2001:db8::/64\" " + " } ]}"; + + // This has Dhcp4 element, but it's utter nonsense + string config_nonsense = "{ \"Dhcp4\": { \"reviews\": \"are so much fun\" } }"; + + // Now initialize the server + boost::scoped_ptr srv; + ASSERT_NO_THROW( + srv.reset(new ControlledDhcpv4Srv(0)) + ); + + // Try to configure without filename. Should fail. + EXPECT_THROW(srv->init(""), BadValue); + + // Try to configure it using empty file. Should fail. + writeFile(TEST_FILE, config_empty); + EXPECT_THROW(srv->init(TEST_FILE), BadValue); + + // Now try to load a config that does not have Dhcp4 component. + writeFile(TEST_FILE, config_v4); + EXPECT_THROW(srv->init(TEST_FILE), BadValue); + + // Now try to load a config with Dhcp4 full of nonsense. + writeFile(TEST_FILE, config_nonsense); + EXPECT_THROW(srv->init(TEST_FILE), BadValue); +} + +} // End of anonymous namespace