diff --git a/ChangeLog b/ChangeLog index d5d2168222..d2ee0fe725 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +788. [func] tomek + DHCPv4 server: New parameter added to configure.ac: --with-kea-config. + It allows selecting configuration backend and accepts one of two + values: BUNDY, which uses Bundy (former BIND10) framework as Kea + 0.8 did, or JSON, which reads configuration from a JSON file. + (Trac #3399, git 6e4dd3ae58c091ba0fd64c87fa8d7c268210f99b) + 787. [func] marcin DHCPv6 server: Implemented dynamic reconfiguration of the server, triggered when the SIGHUP signal is received by the server's @@ -31,7 +38,7 @@ (Trac #3268, git bd60252e679f19b062f61926647f661ab169f21c) 783. [func]* tomek - b10-dhcp6: New parameter added to configure: --with-kea-config. + DHCPv6 server: New parameter added to configure: --with-kea-config. It allows selecting configuration backend and accepts one of two values: BUNDY, which uses Bundy (former BIND10 framework as Kea 0.8 did, or JSON, which reads configuration from a JSON file. diff --git a/doc/devel/mainpage.dox b/doc/devel/mainpage.dox index 2c1e1c1d18..ffe9009def 100644 --- a/doc/devel/mainpage.dox +++ b/doc/devel/mainpage.dox @@ -55,6 +55,7 @@ * - @subpage dhcpv4OptionsParse * - @subpage dhcpv4DDNSIntegration * - @subpage dhcpv4Classifier + * - @subpage dhcpv4ConfigBackend * - @subpage dhcpv4Other * - @subpage dhcp6 * - @subpage dhcpv6Session diff --git a/doc/examples/kea4/several-subnets.json b/doc/examples/kea4/several-subnets.json new file mode 100644 index 0000000000..a3b04b271e --- /dev/null +++ b/doc/examples/kea4/several-subnets.json @@ -0,0 +1,38 @@ +# This is an example configuration file for DHCPv4 server in Kea. +# It's a basic scenario with three IPv4 subnets configured. In each +# subnet, there's a smaller pool of dynamic addresses. + +{ "Dhcp4": + +{ +# Kea is told to listen on eth0 interface only. + "interfaces": [ "eth0" ], + +# We need to specify lease type. As of May 2014, three backends are supported: +# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require +# any prior set up. + "lease-database": { + "type": "memfile" + }, + +# Addresses will be assigned with the valid lifetimes being 4000. +# Client is told to start renewing after 1000 seconds. If the server +# does not repond after 2000 seconds since the lease was granted, client +# is supposed to start REBIND procedure (emergency renewal that allows +# switching to a different server). + "valid-lifetime": 4000, + "renew-timer": 1000, + "rebind-timer": 2000, + +# The following list defines subnets. Each subnet consists of at +# least subnet and pool entries. + "subnet4": [ + { "pool": [ "192.0.2.1 - 192.0.2.200" ], + "subnet": "192.0.2.0/24" }, + { "pool": [ "192.0.3.100 - 192.0.3.200" ], + "subnet": "192.0.3.0/24" }, + { "pool": [ "192.0.4.1 - 192.0.4.254" ], + "subnet": "192.0.4.0/24" } ] +} + +} diff --git a/doc/examples/kea4/single-subnet.json b/doc/examples/kea4/single-subnet.json new file mode 100644 index 0000000000..b4717da393 --- /dev/null +++ b/doc/examples/kea4/single-subnet.json @@ -0,0 +1,34 @@ +# This is an example configuration file for the DHCPv4 server in Kea. +# It is a basic scenario with one IPv4 subnet configured. The subnet +# contains a single pool of dynamically allocated addresses. + +{ "Dhcp4": + +{ +# Kea is told to listen on eth0 interface only. + "interfaces": [ "eth0" ], + +# We need to specify lease type. As of May 2014, three backends are supported: +# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require +# any prior set up. + "lease-database": { + "type": "memfile" + }, + +# Addresses will be assigned with valid lifetimes being 4000. Client +# is told to start renewing after 1000 seconds. If the server does not respond +# after 2000 seconds since the lease was granted, client is supposed +# to start REBIND procedure (emergency renewal that allows switching +# to a different server). + "valid-lifetime": 4000, + "renew-timer": 1000, + "rebind-timer": 2000, + +# The following list defines subnets. We have only one subnet +# here. + "subnet4": [ + { "pool": [ "192.0.2.1 - 192.0.2.200" ], + "subnet": "192.0.2.0/24" } ] +} + +} 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/bundy_controller.cc b/src/bin/dhcp4/bundy_controller.cc new file mode 100644 index 0000000000..9e0984cee1 --- /dev/null +++ b/src/bin/dhcp4/bundy_controller.cc @@ -0,0 +1,219 @@ +// 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 +// 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 +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace isc::asiolink; +using namespace isc::cc; +using namespace isc::config; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::hooks; +using namespace isc::log; +using namespace isc::util; +using namespace std; + +namespace isc { +namespace dhcp { + +/// @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 +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 + // using ModuleCCSession constructor. This constructor will + // invoke the handler and will store the configuration for + // the configuration session when the handler returns success. + // Since this configuration is partial we just pretend to + // parse it and always return success. The function that + // initiates the session must get the configuration on its + // own using getFullConfig. + return (isc::config::createAnswer(0, "Configuration accepted.")); +} + +ConstElementPtr +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 = + isc::config::createAnswer(1, "Configuration rejected," + " server is during startup/shutdown phase."); + return (answer); + } + + // The configuration passed to this handler function is partial. + // In other words, it just includes the values being modified. + // In the same time, there are dependencies between various + // DHCP configuration parsers. For example: the option value can + // be set if the definition of this option is set. If someone removes + // an existing option definition then the partial configuration that + // removes that definition is triggered while a relevant option value + // may remain configured. This eventually results in the DHCP server + // configuration being in the inconsistent state. + // In order to work around this problem we need to merge the new + // configuration with the existing (full) configuration. + + // 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 = 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). + if (full_config && merged_config) { + merged_config->setValue(full_config->mapValue()); + + // Merge an existing and new configuration. + isc::data::merge(merged_config, new_config); + LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE) + .arg(full_config->str()); + } + + // Configure the server. + return (ControlledDhcpv4Srv::processConfig(merged_config)); +} + + + + +void ControlledDhcpv4Srv::init(const std::string& /*config_file*/) { + + string specfile; + if (getenv("B10_FROM_BUILD")) { + specfile = string(getenv("B10_FROM_BUILD")) + + "/src/bin/dhcp4/dhcp4.spec"; + } else { + specfile = string(DHCP4_SPECFILE_LOCATION); + } + + /// @todo: Check if session is not established already. Throw, if it is. + + LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTING) + .arg(specfile); + cc_session_ = new Session(io_service_.get_io_service()); + // Create a session with the dummy configuration handler. + // Dumy configuration handler is internally invoked by the + // constructor and on success the constructor updates + // the current session with the configuration that had been + // committed in the previous session. If we did not install + // the dummy handler, the previous configuration would have + // been lost. + config_session_ = new ModuleCCSession(specfile, *cc_session_, + dhcp4StubConfigHandler, + 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(bundyConfigHandler); + + try { + configureDhcp4Server(*this, config_session_->getFullConfig()); + + // Server will start DDNS communications if its enabled. + server_->startD2(); + + // Configuration may disable or enable interfaces so we have to + // reopen sockets according to new configuration. + openActiveSockets(getPort(), useBroadcast()); + + } catch (const std::exception& ex) { + LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what()); + + } + + /// Integrate the asynchronous I/O model of BIND 10 configuration + /// control with the "select" model of the DHCP server. This is + /// fully explained in \ref dhcpv4Session. + int ctrl_socket = cc_session_->getSocketDesc(); + LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTED) + .arg(ctrl_socket); + IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader); +} + + +void ControlledDhcpv4Srv::cleanup() { + if (config_session_) { + delete config_session_; + config_session_ = NULL; + } + if (cc_session_) { + + int ctrl_socket = cc_session_->getSocketDesc(); + cc_session_->disconnect(); + + IfaceMgr::instance().deleteExternalSocket(ctrl_socket); + delete cc_session_; + cc_session_ = NULL; + } +} + +void +Daemon::loggerInit(const char* log_name, bool verbose) { + isc::log::initLogger(log_name, + (verbose ? isc::log::DEBUG : isc::log::INFO), + isc::log::MAX_DEBUG_LEVEL, NULL, true); +} + +}; +}; diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc index 8c522569c8..57bd677686 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC") +// 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 @@ -13,36 +13,14 @@ // PERFORMANCE OF THIS SOFTWARE. #include - -#include #include -#include -#include -#include -#include #include #include -#include -#include -#include -#include #include -#include +#include -#include -#include -#include -#include -#include - -using namespace isc::asiolink; -using namespace isc::cc; -using namespace isc::config; using namespace isc::data; -using namespace isc::dhcp; using namespace isc::hooks; -using namespace isc::log; -using namespace isc::util; using namespace std; namespace isc { @@ -51,217 +29,141 @@ namespace dhcp { ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL; ConstElementPtr -ControlledDhcpv4Srv::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 - // using ModuleCCSession constructor. This constructor will - // invoke the handler and will store the configuration for - // the configuration session when the handler returns success. - // Since this configuration is partial we just pretend to - // parse it and always return success. The function that - // initiates the session must get the configuration on its - // own using getFullConfig. - return (isc::config::createAnswer(0, "Configuration accepted.")); +ControlledDhcpv4Srv::commandShutdownHandler(const string&, ConstElementPtr) { + if (ControlledDhcpv4Srv::getInstance()) { + ControlledDhcpv4Srv::getInstance()->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); } ConstElementPtr -ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) { - if (!server_ || !server_->config_session_) { - // That should never happen as we install config_handler - // after we instantiate the server. - ConstElementPtr answer = - isc::config::createAnswer(1, "Configuration rejected," - " server is during startup/shutdown phase."); +ControlledDhcpv4Srv::commandLibReloadHandler(const string&, ConstElementPtr) { + + /// @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); +} - // The configuration passed to this handler function is partial. - // In other words, it just includes the values being modified. - // In the same time, there are dependencies between various - // DHCP configuration parsers. For example: the option value can - // be set if the definition of this option is set. If someone removes - // an existing option definition then the partial configuration that - // removes that definition is triggered while a relevant option value - // may remain configured. This eventually results in the DHCP server - // configuration being in the inconsistent state. - // In order to work around this problem we need to merge the new - // configuration with the existing (full) configuration. +ConstElementPtr +ControlledDhcpv4Srv::commandConfigReloadHandler(const string&, + ConstElementPtr args) { + return (processConfig(args)); +} - // 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(); - // 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). - if (full_config && merged_config) { - merged_config->setValue(full_config->mapValue()); +ConstElementPtr +ControlledDhcpv4Srv::processCommand(const string& command, + ConstElementPtr args) { + LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED) + .arg(command).arg(args->str()); - // Merge an existing and new configuration. - isc::data::merge(merged_config, new_config); - LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE) - .arg(full_config->str()); + ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance(); + + if (!srv) { + ConstElementPtr no_srv = isc::config::createAnswer(1, + "Server object not initialized, so can't process command '" + + command + "', arguments: '" + args->str() + "'."); + return (no_srv); } - // Configure the server. - ConstElementPtr answer = configureDhcp4Server(*server_, merged_config); + try { + if (command == "shutdown") { + return (srv->commandShutdownHandler(command, args)); - // Check that configuration was successful. If not, do not reopen sockets. - int rcode = 0; - parseAnswer(rcode, answer); - if (rcode != 0) { + } else if (command == "libreload") { + return (srv->commandLibReloadHandler(command, args)); + + } else if (command == "config-reload") { + return (srv->commandConfigReloadHandler(command, args)); + + } + ConstElementPtr answer = isc::config::createAnswer(1, + "Unrecognized command:" + command); return (answer); + } catch (const Exception& ex) { + return (isc::config::createAnswer(1, "Error while processing command '" + + command + "':" + ex.what() + + ", params: '" + args->str() + "'")); + } +} + +isc::data::ConstElementPtr +ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) { + + LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED) + .arg(config->str()); + + ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance(); + + // Single stream instance used in all error clauses + std::ostringstream err; + + if (!srv) { + err << "Server object not initialized, can't process config."; + return (isc::config::createAnswer(1, err.str())); + } + + ConstElementPtr answer = configureDhcp4Server(*srv, config); + + + // Check that configuration was successful. If not, do not reopen sockets + // and don't bother with DDNS stuff. + try { + int rcode = 0; + isc::config::parseAnswer(rcode, answer); + if (rcode != 0) { + return (answer); + } + } catch (const std::exception& ex) { + err << "Failed to process configuration:" << ex.what(); + return (isc::config::createAnswer(1, err.str())); } // Server will start DDNS communications if its enabled. try { - server_->startD2(); + srv->startD2(); } catch (const std::exception& ex) { - std::ostringstream err; - err << "error starting DHCP_DDNS client " - " after server reconfiguration: " << ex.what(); + 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. + // safe and we really don't want to emit exceptions to whoever called this + // method. Instead, catch an exception and create appropriate answer. try { - server_->openActiveSockets(server_->getPort(), server_->useBroadcast()); + srv->openActiveSockets(srv->getPort(), getInstance()->useBroadcast()); } catch (std::exception& ex) { - std::ostringstream err; - err << "failed to open sockets after server reconfiguration: " << ex.what(); + err << "failed to open sockets after server reconfiguration: " + << ex.what(); answer = isc::config::createAnswer(1, err.str()); } return (answer); } -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() { - - string specfile; - if (getenv("B10_FROM_BUILD")) { - specfile = string(getenv("B10_FROM_BUILD")) + - "/src/bin/dhcp4/dhcp4.spec"; - } else { - specfile = string(DHCP4_SPECFILE_LOCATION); - } - - /// @todo: Check if session is not established already. Throw, if it is. - - LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTING) - .arg(specfile); - cc_session_ = new Session(io_service_.get_io_service()); - // Create a session with the dummy configuration handler. - // Dumy configuration handler is internally invoked by the - // constructor and on success the constructor updates - // the current session with the configuration that had been - // committed in the previous session. If we did not install - // the dummy handler, the previous configuration would have - // been lost. - config_session_ = new ModuleCCSession(specfile, *cc_session_, - dhcp4StubConfigHandler, - dhcp4CommandHandler, 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); - - try { - configureDhcp4Server(*this, config_session_->getFullConfig()); - - // Server will start DDNS communications if its enabled. - server_->startD2(); - - // Configuration may disable or enable interfaces so we have to - // reopen sockets according to new configuration. - openActiveSockets(getPort(), useBroadcast()); - - } catch (const std::exception& ex) { - LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what()); - - } - - /// Integrate the asynchronous I/O model of BIND 10 configuration - /// control with the "select" model of the DHCP server. This is - /// fully explained in \ref dhcpv4Session. - int ctrl_socket = cc_session_->getSocketDesc(); - LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTED) - .arg(ctrl_socket); - IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader); -} - -void ControlledDhcpv4Srv::disconnectSession() { - if (config_session_) { - delete config_session_; - config_session_ = NULL; - } - if (cc_session_) { - - int ctrl_socket = cc_session_->getSocketDesc(); - cc_session_->disconnect(); - - IfaceMgr::instance().deleteExternalSocket(ctrl_socket); - delete cc_session_; - cc_session_ = NULL; - } -} - 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 + :Dhcpv4Srv(port) { + if (getInstance()) { + isc_throw(InvalidOperation, + "There is another Dhcpv4Srv instance already."); + } + server_ = this; // remember this instance for later use in handlers } void ControlledDhcpv4Srv::shutdown() { @@ -270,22 +172,19 @@ void ControlledDhcpv4Srv::shutdown() { } ControlledDhcpv4Srv::~ControlledDhcpv4Srv() { - disconnectSession(); - - server_ = NULL; // forget this instance. There should be no callback anymore - // at this stage anyway. + cleanup(); + + server_ = NULL; // forget this instance. Noone should call any handlers at + // this stage. } -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 ControlledDhcpv4Srv::sessionReader(void) { + // Process one asio event. If there are more events, iface_mgr will call + // this callback more than once. + if (getInstance()) { + getInstance()->io_service_.run_one(); } } -}; -}; +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h index 59dde870ee..2717ae15c0 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 @@ -26,12 +26,10 @@ namespace dhcp { /// @brief Controlled version of the DHCPv4 server /// -/// This is a class that is responsible for establishing connection -/// with msqg (receving commands and configuration). This is an extended -/// version of Dhcpv4Srv class that is purely a DHCPv4 server, without -/// external control. ControlledDhcpv4Srv should be used in typical BIND10 -/// (i.e. featuring msgq) environment, while Dhcpv4Srv should be used in -/// embedded environments. +/// This is a class that is responsible for DHCPv4 server being controllable. +/// It does various things, depending on the configuration backend. +/// For Bundy backend it establishes a connection with msqg and later receives +/// commands over it. For Kea backend, it reads configuration file from disk. /// /// For detailed explanation or relations between main(), ControlledDhcpv4Srv, /// Dhcpv4Srv and other classes, see \ref dhcpv4Session. @@ -46,44 +44,57 @@ 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 + /// + /// This method may throw if initialization fails. Exception types may be + /// specific to used configuration backend. + 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 Dhcpv4Srv 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. + /// + /// Currently supported commands are: + /// - shutdown + /// - libreload + /// - config-reload + /// + /// @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 method 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 +103,72 @@ protected: /// /// @return status of the config update static isc::data::ConstElementPtr - dhcp4ConfigHandler(isc::data::ConstElementPtr new_config); + processConfig(isc::data::ConstElementPtr new_config); - /// @brief A dummy configuration handler that always returns success. + /// @brief Returns pointer to the sole instance of Dhcpv4Srv /// - /// 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); + /// @return server instance (may return NULL, if called before server is spawned) + static ControlledDhcpv4Srv* getInstance() { + return (server_); + } - /// @brief A callback for handling incoming commands. - /// - /// @param command textual representation of the command - /// @param args parameters of the command - /// - /// @return status of the processed command - static isc::data::ConstElementPtr - dhcp4CommandHandler(const std::string& command, isc::data::ConstElementPtr args); - /// @brief Callback that will be called from iface_mgr when command/config arrives. +protected: + /// @brief Static pointer to the sole instance of the DHCP server. /// - /// This static callback method is called from IfaceMgr::receive4() method, - /// when there is a new command or configuration sent over msgq. + /// 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 data + /// is received over control socket. + /// + /// 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 Session that receives configuration and commands - isc::config::ModuleCCSession* config_session_; + /// @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 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.dox b/src/bin/dhcp4/dhcp4.dox index 22603f31dd..7d02bbc0ec 100644 --- a/src/bin/dhcp4/dhcp4.dox +++ b/src/bin/dhcp4/dhcp4.dox @@ -191,6 +191,46 @@ being passed in isc::dhcp::Dhcpv4Srv::selectSubnet() to isc::dhcp::CfgMgr::getSu Currently this capability is usable, but the number of scenarios it supports is limited. + @section dhcpv4ConfigBackend Configuration backend for DHCPv4 + +There are many theoretical ways in which server configuration can be stored. Kea 0.8 and +earlier versions used BIND10 framework and its internal storage for DHCPv6 server configuration. +The legacy ISC-DHCP implementation uses flat files. Configuration stored in JSON files is +becoming more and more popular among various projects. There are unofficial patches for +ISC-DHCP that keep parts of the configuration in LDAP. It was also suggested that in some +cases it would be convenient to keep configuration in XML files. + +Kea 0.9 introduces configuration backends that are switchable during compilation phase. +There is a new parameter for configure script: --with-kea-config. It currently supports +two values: BUNDY and JSON. + +BUNDY (which is the default value as of May 2014) means that Kea4 is linked with the +Bundy (former BIND10) configuration backend that connects to the BIND10 framework and in general works +exactly the same as Kea 0.8 and earlier versions. The benefits of that backend are uniform +integration with Bundy/BIND10 framework, easy on-line reconfiguration using bindctl, available +RESTful API. On the other hand, it requires the whole heavy Bundy framework that requires +Python3 to be present. That framework is going away with the release of Kea 0.9. + +JSON is a new configuration backend that causes Kea to read JSON configuration file from +disk. It does not require any framework and thus is considered more lightweight. It will +allow dynamic on-line reconfiguration, but will lack remote capabilities (i.e. no RESTful +API). This configuration backend is expected to be the default for upcoming Kea 0.9. It +requires -c config-file command-line option. + +Internally, configuration backends are implemented as different implementations of the +isc::dhcp::ControlledDhcpv4Srv class, stored in {kea,bundy}_controller.cc files. Depending on +the choice made by ./configure script, only one of those files is compiled and linked. +There are backend specific tests in src/bin/dhcp4/tests/{kea,bundy}_controller_unittest.cc. +Only tests specific to selected backend are linked and executed during make distcheck. + +While it is unlikely that ISC will support more than one backend at any given time, there +are several aspects that make that approach appealing in the long term. First, having +two backends is essential during transition time, where both old and new backend is used. +Second, there are external organizations that develop and presumably maintain LDAP backend +for ISC-DHCP. Is at least possible that similar will happen for Kea. Finally, if we ever +extend the isc::dhcp::CfgMgr with configuration export, this approach could be used as +a migration tool. + @section dhcpv4Other Other DHCPv4 topics For hooks API support in DHCPv4, see @ref dhcpv4Hooks. diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes index 40215a2583..9777391b37 100644 --- a/src/bin/dhcp4/dhcp4_messages.mes +++ b/src/bin/dhcp4/dhcp4_messages.mes @@ -44,6 +44,10 @@ name was malformed or due to internal server error. A debug message listing the command (and possible arguments) received from the BIND 10 control system by the DHCPv4 server. +% DHCP4_CONFIG_RECEIVED received configuration %1 +A debug message listing the configuration received by the DHCPv4 server. +The source of that configuration depends on used configuration backend. + % DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1 This is an informational message announcing the successful processing of a new configuration. It is output during server startup, and when an updated @@ -339,17 +343,11 @@ core component within the DHCPv4 server (the Dhcpv4 server object) has failed. As a result, the server will exit. The reason for the failure is given within the message. -% DHCP4_STANDALONE skipping message queue, running standalone -This is a debug message indicating that the DHCPv4 server is running in -standalone mode, not connected to the message queue. Standalone mode -is only useful during program development, and should not be used in a -production environment. - % DHCP4_STARTING server starting This informational message indicates that the DHCPv4 server has processed any command-line switches and is starting. -% DHCP4_START_INFO pid: %1, port: %2, verbose: %3, standalone: %4 +% DHCP4_START_INFO pid: %1, port: %2, verbose: %3 This is a debug message issued during the DHCPv4 server startup. It lists some information about the parameters with which the server is running. 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/kea_controller.cc b/src/bin/dhcp4/kea_controller.cc new file mode 100644 index 0000000000..a8d43e8362 --- /dev/null +++ b/src/bin/dhcp4/kea_controller.cc @@ -0,0 +1,124 @@ +// 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 + +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace std; + +namespace isc { +namespace dhcp { + +void +ControlledDhcpv4Srv::init(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 dhcp4; + 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(BadValue, "JSON configuration file not specified. Please " + "use -c command line option."); + } + + // Read contents of the file and parse it as JSON + json = isc::data::Element::fromJSONFile(file_name, true); + + if (!json) { + LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL) + .arg("Config file " + file_name + " missing or empty."); + isc_throw(BadValue, "Unable to process JSON configuration file:" + + file_name); + } + + // Get Dhcp4 component from the config + dhcp4 = json->get("Dhcp4"); + + if (!dhcp4) { + LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL) + .arg("Config file " + file_name + " does not include 'Dhcp4' entry."); + isc_throw(BadValue, "Unable to process JSON configuration file:" + + file_name); + } + + // Use parsed JSON structures to configure the server + result = processCommand("config-reload", dhcp4); + + } catch (const std::exception& ex) { + LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what()); + isc_throw(BadValue, "Unable to process JSON configuration file:" + + file_name); + } + + 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. + LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL) + .arg("Configuration failed: Undefined result of processCommand(" + "config-reload, " + file_name + ")"); + isc_throw(BadValue, "Configuration failed: Undefined result of " + "processCommand('config-reload', " + file_name + ")"); + } + + // Now check is the returned result is successful (rcode=0) or not + isc::data::ConstElementPtr comment; /// see @ref isc::config::parseAnswer + int rcode; + comment = isc::config::parseAnswer(rcode, result); + if (rcode != 0) { + string reason = ""; + if (comment) { + reason = string(" (") + comment->stringValue() + string(")"); + } + LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(reason); + isc_throw(BadValue, "Failed to apply configuration:" << reason); + } + + // We don't need to call openActiveSockets() or startD2() as these + // methods are called in processConfig() which is called by + // processCommand("reload-config", ...) +} + +void ControlledDhcpv4Srv::cleanup() { + // Nothing to do here. No need to disconnect from anything. +} + +/// This is a logger initialization for JSON file backend. +/// For now, it's just setting log messages to be printed on stdout. +/// @todo: Implement this properly (see #3427) +void Daemon::loggerInit(const char*, bool verbose) { + + setenv("B10_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1); + setenv("B10_LOGGER_ROOT", "kea", 0); + setenv("B10_LOGGER_SEVERITY", (verbose ? "DEBUG":"INFO"), 0); + setenv("B10_LOGGER_DBGLEVEL", "99", 0); + setenv("B10_LOGGER_DESTINATION", "stdout", 0); + isc::log::initLogger(); +} + +}; +}; diff --git a/src/bin/dhcp4/main.cc b/src/bin/dhcp4/main.cc index 45f1de1612..99e49d42fb 100644 --- a/src/bin/dhcp4/main.cc +++ b/src/bin/dhcp4/main.cc @@ -39,13 +39,15 @@ 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; + cerr << "Usage: " << DHCP4_NAME << " [-v] [-p number] [-c file]" << endl; cerr << " -v: verbose output" << endl; - 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 @@ -55,19 +57,17 @@ main(int argc, char* argv[]) { int ch; int port_number = DHCP4_SERVER_PORT; // The default. any other values are // useful for testing only. - 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, "vp:c:")) != -1) { switch (ch) { case 'v': verbose_mode = true; break; - case 's': - stand_alone = true; - break; - case 'p': try { port_number = boost::lexical_cast(optarg); @@ -83,6 +83,10 @@ main(int argc, char* argv[]) { } break; + case 'c': // config file + config_file = optarg; + break; + default: usage(); } @@ -93,36 +97,39 @@ 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); + LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO) + .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no"); + + LOG_INFO(dhcp4_logger, DHCP4_STARTING); + + // Create the server instance. ControlledDhcpv4Srv server(port_number); - if (!stand_alone) { - try { - server.establishSession(); - } 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 - isc::log::LoggerManager log_manager; - log_manager.process(); - } - } else { - LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_STANDALONE); + + try { + // Initialize the server. + server.init(config_file); + } catch (const std::exception& ex) { + LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what()); + + // 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); } + + // And run the main loop of the server. server.run(); + LOG_INFO(dhcp4_logger, DHCP4_SHUTDOWN); } catch (const std::exception& ex) { diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am index 1961883706..c835aaa1bf 100644 --- a/src/bin/dhcp4/tests/Makefile.am +++ b/src/bin/dhcp4/tests/Makefile.am @@ -33,6 +33,7 @@ AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt +CLEANFILES += *.json AM_CXXFLAGS = $(B10_CXXFLAGS) if USE_CLANGPP @@ -77,7 +78,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 +90,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 @@ -109,4 +123,6 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la endif +noinst_EXTRA_DIST = configs-list.txt + noinst_PROGRAMS = $(TESTS) 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..0578ec6fe6 --- /dev/null +++ b/src/bin/dhcp4/tests/bundy_controller_unittest.cc @@ -0,0 +1,30 @@ +// 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 { + +/// As of May 2014, maintaining or extending Bundy support is very low +/// prority for Kea team. We are looking for contributors, who would +/// like to maintain this backend. + +// 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/configs-list.txt b/src/bin/dhcp4/tests/configs-list.txt new file mode 100644 index 0000000000..f7e5587bd0 --- /dev/null +++ b/src/bin/dhcp4/tests/configs-list.txt @@ -0,0 +1,5 @@ +# This is a list of config files that the unit-tests (specifically +# JSONFileBackendTest.loadAllConfigs) is going to load. + +../../../../doc/examples/kea4/single-subnet.json +../../../../doc/examples/kea4/several-subnets.json 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 f9ad81c911..164b2c2fe1 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.py b/src/bin/dhcp4/tests/dhcp4_test.py index 276456e01d..37e9629d6f 100644 --- a/src/bin/dhcp4/tests/dhcp4_test.py +++ b/src/bin/dhcp4/tests/dhcp4_test.py @@ -13,7 +13,7 @@ # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -from init import ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME +from init import ProcessInfo import unittest import sys @@ -207,22 +207,5 @@ class TestDhcpv4Daemon(unittest.TestCase): # Check that there is an error message about invalid port number printed on stderr self.assertEqual( str(error).count("Failed to parse port number"), 1) - def test_portnumber_nonroot(self): - print("Check that specifying unprivileged port number will work.") - - # Check that there is a message about running with an unprivileged port - (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-v', '-s', '-p', '10057']) - output_text = str(output) + str(error) - self.assertEqual(output_text.count("DHCP4_OPEN_SOCKET opening sockets on port 10057"), 1) - - def test_skip_msgq(self): - print("Check that connection to BIND10 msgq can be disabled.") - - # Check that the system outputs a message on one of its streams about running - # standalone. - (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-v', '-s', '-p', '10057']) - output_text = str(output) + str(error) - self.assertEqual(output_text.count("DHCP4_STANDALONE"), 1) - if __name__ == '__main__': unittest.main() 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..6bda818ada --- /dev/null +++ b/src/bin/dhcp4/tests/kea_controller_unittest.cc @@ -0,0 +1,296 @@ +// 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) { } +}; + +/// @brief test class for Kea configuration backend +/// +/// This class is used for testing Kea configuration backend. +/// It is very simple and currently focuses on reading +/// config file from disk. It is expected to be expanded in the +/// near future. +class JSONFileBackendTest : public ::testing::Test { +public: + JSONFileBackendTest() { + } + + ~JSONFileBackendTest() { + static_cast(unlink(TEST_FILE)); + }; + + /// @brief writes specified content to a well known file + /// + /// Writes specified content to TEST_FILE. Tests will + /// attempt to read that file. + /// + /// @param content content to be written to file + void writeFile(const std::string& content) { + static_cast(unlink(TEST_FILE)); + + ofstream out(TEST_FILE, ios::trunc); + EXPECT_TRUE(out.is_open()); + out << content; + out.close(); + } + + /// Name of a config file used during tests + 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 = "{ \"Dhcp4\": { \"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(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_V4, 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(config_hash_comments); + + // Now initialize the server + boost::scoped_ptr srv; + ASSERT_NO_THROW( + srv.reset(new ControlledDhcpv4Srv(0)) + ); + + // And configure it using config with comments. + 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_V4); + 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_V4, 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(config_empty); + EXPECT_THROW(srv->init(TEST_FILE), BadValue); + + // Now try to load a config that does not have Dhcp4 component. + writeFile(config_v4); + EXPECT_THROW(srv->init(TEST_FILE), BadValue); + + // Now try to load a config with Dhcp4 full of nonsense. + writeFile(config_nonsense); + EXPECT_THROW(srv->init(TEST_FILE), BadValue); +} + +/// This unit-test reads all files enumerated in configs-test.txt file, loads +/// each of them and verify that they can be loaded. +/// +/// @todo: Unfortunately, we have this test disabled, because all loaded +/// configs use memfile, which attempts to create lease file in +/// /usr/local/var/bind10/kea-leases4.csv. We have couple options here: +/// a) disable persistence in example configs - a very bad thing to do +/// as users will forget to reenable it and then will be surprised when their +/// leases disappear +/// b) change configs to store lease file in /tmp. It's almost as bad as the +/// previous one. Users will then be displeased when all their leases are +/// wiped. (most systems wipe /tmp during boot) +/// c) read each config and rewrite it on the fly, so persistence is disabled. +/// This is probably the way to go, but this is a work for a dedicated ticket. +/// +/// Hence I'm leaving the test in, but it is disabled. +TEST_F(JSONFileBackendTest, DISABLED_loadAllConfigs) { + + // Create server first + boost::scoped_ptr srv; + ASSERT_NO_THROW( + srv.reset(new ControlledDhcpv4Srv(0)) + ); + + const char* configs_list = "configs-list.txt"; + fstream configs(configs_list, ios::in); + ASSERT_TRUE(configs.is_open()); + std::string config_name; + while (std::getline(configs, config_name)) { + + // Ignore empty and commented lines + if (config_name.empty() || config_name[0] == '#') { + continue; + } + + // Unit-tests usually do not print out anything, but in this case I + // think printing out tests configs is warranted. + std::cout << "Loading config file " << config_name << std::endl; + + try { + srv->init(config_name); + } catch (const std::exception& ex) { + ADD_FAILURE() << "Exception thrown" << ex.what() << endl; + } + } +} + +} // End of anonymous namespace diff --git a/src/bin/dhcp6/bundy_controller.cc b/src/bin/dhcp6/bundy_controller.cc index 8b2d3338a9..672b23d26b 100644 --- a/src/bin/dhcp6/bundy_controller.cc +++ b/src/bin/dhcp6/bundy_controller.cc @@ -220,10 +220,10 @@ void ControlledDhcpv6Srv::cleanup() { } void -Daemon::loggerInit(const char* log_name, bool verbose, bool stand_alone) { +Daemon::loggerInit(const char* log_name, bool verbose) { isc::log::initLogger(log_name, (verbose ? isc::log::DEBUG : isc::log::INFO), - isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone); + isc::log::MAX_DEBUG_LEVEL, NULL, true); } }; // end of isc::dhcp namespace diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc index 3408e3be50..950c5baf55 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc @@ -43,8 +43,8 @@ ControlledDhcpv6Srv::commandShutdownHandler(const string&, ConstElementPtr) { ConstElementPtr ControlledDhcpv6Srv::commandLibReloadHandler(const string&, ConstElementPtr) { - // TODO delete any stored CalloutHandles referring to the old libraries - // Get list of currently loaded libraries and reload them. + /// @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) { @@ -99,9 +99,12 @@ ControlledDhcpv6Srv::processCommand(const std::string& command, } } - isc::data::ConstElementPtr ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) { + + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_RECEIVED) + .arg(config->str()); + ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance(); if (!srv) { diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h index c0a5a5709c..9789bb077f 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.h +++ b/src/bin/dhcp6/ctrl_dhcp6_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 @@ -26,12 +26,10 @@ namespace dhcp { /// @brief Controlled version of the DHCPv6 server /// -/// This is a class that is responsible for establishing connection -/// with msqg (receving commands and configuration). This is an extended -/// version of Dhcpv6Srv class that is purely a DHCPv6 server, without -/// external control. ControlledDhcpv6Srv should be used in typical BIND10 -/// (i.e. featuring msgq) environment, while Dhcpv6Srv should be used in -/// embedded environments. +/// This is a class that is responsible for DHCPv6 server being controllable. +/// It does various things, depending on the configuration backend. +/// For Bundy backend it establishes a connection with msqg and later receives +/// commands over it. For Kea backend, it reads configuration file from disk. /// /// For detailed explanation or relations between main(), ControlledDhcpv6Srv, /// Dhcpv6Srv and other classes, see \ref dhcpv6Session. @@ -53,7 +51,8 @@ public: /// operation. For specific details, see actual implementation in /// *_backend.cc /// - /// @return true if initialization was successful, false if it failed + /// This method may throw if initialization fails. Exception types may be + /// specific to used configuration backend. void init(const std::string& config_file); /// @brief Performs cleanup, immediately before termination @@ -77,6 +76,11 @@ public: /// wrapper that calls process*Command() methods and catches exceptions /// in them. /// + /// Currently supported commands are: + /// - shutdown + /// - libreload + /// - config-reload + /// /// @note It never throws. /// /// @param command Text represenation of the command (e.g. "shutdown") @@ -88,7 +92,7 @@ public: /// @brief configuration processor /// - /// This is a callback for handling incoming configuration updates. + /// This is a method 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. /// @@ -103,7 +107,7 @@ public: /// @brief returns pointer to the sole instance of Dhcpv6Srv /// - /// @note may return NULL, if called before server is spawned + /// @return server instance (may return NULL, if called before server is spawned) static ControlledDhcpv6Srv* getInstance() { return (server_); } diff --git a/src/bin/dhcp6/dhcp6.dox b/src/bin/dhcp6/dhcp6.dox index fdcd4091c7..b9ccea6047 100644 --- a/src/bin/dhcp6/dhcp6.dox +++ b/src/bin/dhcp6/dhcp6.dox @@ -234,24 +234,25 @@ cases it would be convenient to keep configuration in XML files. Kea 0.9 introduces configuration backends that are switchable during compilation phase. There is a new parameter for configure script: --with-kea-config. It currently supports -two values: BIND10 and JSON. +two values: BUNDY and JSON. -BIND10 (which is the default value as of April 2014) means that Kea6 is linked with the -BIND10 configuration backend that connects to the BIND10 framework and in general works +BUNDY (which is the default value as of April 2014) means that Kea6 is linked with the +Bundy (former BIND10) configuration backend that connects to the BIND10 framework and in general works exactly the same as Kea 0.8 and earlier versions. The benefits of that backend are uniform -integration with BIND10 framework, easy on-line reconfiguration using bindctl, available -RESTful API. On the other hand, it requires the whole heavy BIND10 framework that requires -Python3 to be present. That backend is likely to go away with the release of Kea 0.9. +integration with Bundy/BIND10 framework, easy on-line reconfiguration using bindctl, available +RESTful API. On the other hand, it requires the whole heavy Bundy framework that requires +Python3 to be present. That framework is going away with the release of Kea 0.9. JSON is a new configuration backend that causes Kea to read JSON configuration file from disk. It does not require any framework and thus is considered more lightweight. It will allow dynamic on-line reconfiguration, but will lack remote capabilities (i.e. no RESTful -API). This configuration backend is expected to be the default for upcoming Kea 0.9. +API). This configuration backend is expected to be the default for upcoming Kea 0.9. It +requires -c config-file command-line option. Internally, configuration backends are implemented as different implementations of the -isc::dhcp::ControlledDhcpv6Srv class, stored in ctrl_*_dhcpv6_srv.cc files. Depending on +isc::dhcp::ControlledDhcpv6Srv class, stored in {kea,bundy}_controller.cc files. Depending on the choice made by ./configure script, only one of those files is compiled and linked. -There are backend specific tests in src/bin/dhcp6/tests/ctrl_*_dhcpv6_srv_unittest.cc. +There are backend specific tests in src/bin/dhcp6/tests/{kea,bundy}_controller_unittest.cc. Only tests specific to selected backend are linked and executed during make distcheck. While it is unlikely that ISC will support more than one backend at any given time, there diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes index fb69cc4861..5dec541999 100644 --- a/src/bin/dhcp6/dhcp6_messages.mes +++ b/src/bin/dhcp6/dhcp6_messages.mes @@ -40,6 +40,10 @@ address assignment. The most likely cause is a problem with the client. A debug message listing the command (and possible arguments) received from the BIND 10 control system by the IPv6 DHCP server. +% DHCP6_CONFIG_RECEIVED received configuration: %1 +A debug message listing the configuration received by the DHCPv6 server. +The source of that configuration depends on used configuration backend. + % DHCP6_CONFIG_COMPLETE DHCPv6 server has completed configuration: %1 This is an informational message announcing the successful processing of a new configuration. it is output during server startup, and when an updated @@ -540,7 +544,7 @@ production environment. This informational message indicates that the IPv6 DHCP server has processed any command-line switches and is starting. -% DHCP6_START_INFO pid: %1, port: %2, verbose: %3, standalone: %4 +% DHCP6_START_INFO pid: %1, port: %2, verbose: %3 This is a debug message issued during the IPv6 DHCP server startup. It lists some information about the parameters with which the server is running. diff --git a/src/bin/dhcp6/kea_controller.cc b/src/bin/dhcp6/kea_controller.cc index 3c8135c8a1..33404cac58 100644 --- a/src/bin/dhcp6/kea_controller.cc +++ b/src/bin/dhcp6/kea_controller.cc @@ -15,35 +15,19 @@ #include #include -#include #include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include #include + #include -#include using namespace isc::asiolink; -using namespace isc::cc; -using namespace isc::config; -using namespace isc::data; using namespace isc::dhcp; -using namespace isc::log; -using namespace isc::util; using namespace std; namespace { @@ -74,7 +58,7 @@ void configure(const std::string& file_name) { } // Read contents of the file and parse it as JSON - json = Element::fromJSONFile(file_name, true); + json = isc::data::Element::fromJSONFile(file_name, true); if (!json) { LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL) @@ -113,9 +97,9 @@ void configure(const std::string& file_name) { } // Now check is the returned result is successful (rcode=0) or not - ConstElementPtr comment; /// see @ref isc::config::parseAnswer + isc::data::ConstElementPtr comment; /// see @ref isc::config::parseAnswer int rcode; - comment = parseAnswer(rcode, result); + comment = isc::config::parseAnswer(rcode, result); if (rcode != 0) { string reason = ""; if (comment) { @@ -151,7 +135,7 @@ void signalHandler(int signo) { .arg(file); } } else if ((signo == SIGTERM) || (signo == SIGINT)) { - ElementPtr params(new isc::data::MapElement()); + isc::data::ElementPtr params(new isc::data::MapElement()); ControlledDhcpv6Srv::processCommand("shutdown", params); } } @@ -188,7 +172,7 @@ void ControlledDhcpv6Srv::cleanup() { /// This is a logger initialization for JSON file backend. /// For now, it's just setting log messages to be printed on stdout. /// @todo: Implement this properly (see #3427) -void Daemon::loggerInit(const char*, bool verbose, bool ) { +void Daemon::loggerInit(const char*, bool verbose) { setenv("B10_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1); setenv("B10_LOGGER_ROOT", "kea", 0); diff --git a/src/bin/dhcp6/main.cc b/src/bin/dhcp6/main.cc index 49c70ec319..4d49af9c39 100644 --- a/src/bin/dhcp6/main.cc +++ b/src/bin/dhcp6/main.cc @@ -43,9 +43,8 @@ const char* const DHCP6_LOGGER_NAME = "kea"; void usage() { - cerr << "Usage: " << DHCP6_NAME << " [-v] [-s] [-p port_number] [-c cfgfile]" << endl; + cerr << "Usage: " << DHCP6_NAME << " [-v] [-p port_number] [-c cfgfile]" << endl; cerr << " -v: verbose output" << endl; - cerr << " -s: skip configuration (don't connect to BIND10 or don't read config file)" << endl; cerr << " -p number: specify non-standard port number 1-65535 " << "(useful for testing only)" << endl; cerr << " -c file: specify configuration file" << endl; @@ -58,22 +57,17 @@ main(int argc, char* argv[]) { int ch; int port_number = DHCP6_SERVER_PORT; // The default. Any other values are // useful for testing only. - bool stand_alone = false; // Should be connect to BIND10 msgq? bool verbose_mode = false; // Should server be verbose? // The standard config file std::string config_file(""); - while ((ch = getopt(argc, argv, "vsp:c:")) != -1) { + while ((ch = getopt(argc, argv, "vp:c:")) != -1) { switch (ch) { case 'v': verbose_mode = true; break; - case 's': // stand-alone - stand_alone = true; - break; - case 'p': // port number try { port_number = boost::lexical_cast(optarg); @@ -107,39 +101,37 @@ main(int argc, char* argv[]) { 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(DHCP6_LOGGER_NAME, verbose_mode, stand_alone); + Daemon::loggerInit(DHCP6_LOGGER_NAME, verbose_mode); LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_START_INFO) - .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no") - .arg(stand_alone ? "yes" : "no" ); + .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no"); LOG_INFO(dhcp6_logger, DHCP6_STARTING); + // Create the server instance. ControlledDhcpv6Srv server(port_number); - if (!stand_alone) { - try { - // Initialize the server, i.e. establish control session - // if BIND10 backend is used or read a configuration file - server.init(config_file); + try { + // Initialize the server, i.e. establish control session + // if BIND10 backend is used or read a configuration file + // if Kea backend is used. + server.init(config_file); - } catch (const std::exception& ex) { - LOG_ERROR(dhcp6_logger, DHCP6_INIT_FAIL).arg(ex.what()); + } catch (const std::exception& ex) { + LOG_ERROR(dhcp6_logger, DHCP6_INIT_FAIL).arg(ex.what()); - // We should not continue if were told to configure (either read - // config file or establish BIND10 control session). - isc::log::LoggerManager log_manager; - log_manager.process(); + // We should not continue if were told to configure (either read + // config file or establish BIND10 control session). + isc::log::LoggerManager log_manager; + log_manager.process(); - cerr << "Failed to initialize server: " << ex.what() << endl; - return (EXIT_FAILURE); - } - } else { - LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_STANDALONE); + cerr << "Failed to initialize server: " << ex.what() << endl; + return (EXIT_FAILURE); } + // And run the main loop of the server. server.run(); + LOG_INFO(dhcp6_logger, DHCP6_SHUTDOWN); } catch (const std::exception& ex) { diff --git a/src/bin/dhcp6/tests/bundy_controller_unittest.cc b/src/bin/dhcp6/tests/bundy_controller_unittest.cc index f556560a14..0578ec6fe6 100644 --- a/src/bin/dhcp6/tests/bundy_controller_unittest.cc +++ b/src/bin/dhcp6/tests/bundy_controller_unittest.cc @@ -18,6 +18,10 @@ namespace { +/// As of May 2014, maintaining or extending Bundy support is very low +/// prority for Kea team. We are looking for contributors, who would +/// like to maintain this backend. + // Bundy framework specific tests should be added here. TEST(BundyBackendTest, dummy) { diff --git a/src/bin/dhcp6/tests/dhcp6_test.py b/src/bin/dhcp6/tests/dhcp6_test.py index 3333111ba0..9b4af4791e 100644 --- a/src/bin/dhcp6/tests/dhcp6_test.py +++ b/src/bin/dhcp6/tests/dhcp6_test.py @@ -210,22 +210,5 @@ class TestDhcpv6Daemon(unittest.TestCase): # Check that there is an error message about invalid port number printed on stderr self.assertEqual( str(error).count("Failed to parse port number"), 1) - def test_portnumber_nonroot(self): - print("Check that specifying unprivileged port number will work.") - - # Check that there is a message about running with an unprivileged port - (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-v', '-s', '-p', '10547']) - output_text = str(output) + str(error) - self.assertEqual(output_text.count("DHCP6_OPEN_SOCKET opening sockets on port 10547"), 1) - - def test_skip_msgq(self): - print("Check that connection to BIND10 msgq can be disabled.") - - # Check that the system outputs a message on one of its streams about running - # standalone. - (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-v', '-s', '-p', '10547']) - output_text = str(output) + str(error) - self.assertEqual(output_text.count("DHCP6_STANDALONE"), 1) - if __name__ == '__main__': unittest.main() diff --git a/src/lib/dhcpsrv/daemon.h b/src/lib/dhcpsrv/daemon.h index 52623f0ff3..515b168b6f 100644 --- a/src/lib/dhcpsrv/daemon.h +++ b/src/lib/dhcpsrv/daemon.h @@ -100,8 +100,12 @@ public: /// Initializes logger /// - /// This method initializes logger. - static void loggerInit(const char* log_name, bool verbose, bool stand_alone); + /// This method initializes logger. Currently its implementation is specific + /// to each configuration backend. + /// + /// @param log_name name used in logger initialization + /// @param verbose verbose mode (true usually enables DEBUG messages) + static void loggerInit(const char* log_name, bool verbose); private: