diff --git a/ChangeLog b/ChangeLog index d70adcda7bc76f6388d9f429beb3e98b85757415..d1f5ec4a5d5161b6dd09e41d7ffd2c5343118484 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +7XX. [func]* tomek + b10-dhcp6: New parameter added to configure: --with-kea-config. + It allows selecting configuration backend and accepts one of two + values: BIND10, which uses BIND10 framework as Kea 0.8 did, or + JSON, which reads configuration from a JSON file. + (Trac #3400, git TBD) + 782. [func] tmark Added sender-ip, sender-port, and max-queue-size parameters to the dhcp-ddns configuration section of both b10-dhcp4 and b10-dhcp6. diff --git a/configure.ac b/configure.ac index 58658a9c90958dc3867d2b7d252fb669d79a1183..3d13aa850e6bfe7e43a3516e6bef0bc11e3fe294 100644 --- a/configure.ac +++ b/configure.ac @@ -565,6 +565,9 @@ AC_SUBST(PYCOVERAGE) AC_SUBST(PYCOVERAGE_RUN) AC_SUBST(USE_PYCOVERAGE) + + + enable_gtest="no" GTEST_INCLUDES= @@ -1282,6 +1285,34 @@ AC_SUBST(PERL) AC_PATH_PROGS(AWK, gawk awk) AC_SUBST(AWK) + +# Kea configuration backend section +# Currently there are 2 backends available: BUNDY and JSON +# It is possible that we may extend this to accept additional backends. +AC_ARG_WITH(kea-config, + AC_HELP_STRING([--with-kea-config], + [Selects configuration backend; currently available options are: BUNDY (default, + Kea reads configuration and commands from Bundy framework) or JSON (Kea reads + configuration from a JSON file from disk)]), + [CONFIG_BACKEND="$withval"], + [CONFIG_BACKEND=BUNDY]) + +AM_CONDITIONAL(CONFIG_BACKEND_BUNDY, test "x$CONFIG_BACKEND" = "xBUNDY") +AM_CONDITIONAL(CONFIG_BACKEND_JSON, test "x$CONFIG_BACKEND" = "xJSON") + +if test "x$CONFIG_BACKEND" = "xBUNDY"; then + AC_DEFINE(CONFIG_BACKEND_BUNDY, 1, [Define to 1 if Kea config was set to BUNDY]) +fi + +if test "x$CONFIG_BACKEND" = "xJSON"; then + AC_DEFINE(CONFIG_BACKEND_JSON, 1, [Define to 1 if Kea config was set to JSON]) +fi + +# Let's sanity check if the specified backend value is allowed +if test "x$CONFIG_BACKEND" != "xBUNDY" && test "x$CONFIG_BACKEND" != "xJSON"; then + AC_MSG_ERROR("Invalid configuration backend specified: $CONFIG_BACKEND. The only supported are: BUNDY JSON") +fi + AC_ARG_ENABLE(generate_docs, [AC_HELP_STRING([--enable-generate-docs], [regenerate documentation using Docbook [default=no]])], enable_generate_docs=$enableval, enable_generate_docs=no) @@ -1620,6 +1651,9 @@ SQLite: SQLITE_VERSION: ${SQLITE_VERSION} SQLITE_CFLAGS: ${SQLITE_CFLAGS} SQLITE_LIBS: ${SQLITE_LIBS} + +Kea config backend: + CONFIG_BACKEND: ${CONFIG_BACKEND} END # Avoid confusion on DNS/DHCP and only mention MySQL if it diff --git a/doc/devel/mainpage.dox b/doc/devel/mainpage.dox index 7847f2530e466d1f0ba5f82541afa51b77557a33..b775fe024f3c00e32c6121b42b81e147da34585f 100644 --- a/doc/devel/mainpage.dox +++ b/doc/devel/mainpage.dox @@ -63,6 +63,7 @@ * - @subpage dhcpv6DDNSIntegration * - @subpage dhcpv6OptionsParse * - @subpage dhcpv6Classifier + * - @subpage dhcpv6ConfigBackend * - @subpage dhcpv6Other * - @subpage d2 * - @subpage d2CPL diff --git a/doc/examples/kea6/several-subnets.json b/doc/examples/kea6/several-subnets.json new file mode 100644 index 0000000000000000000000000000000000000000..1fe7254e694ee70b7234624007e830f7ecbe86a6 --- /dev/null +++ b/doc/examples/kea6/several-subnets.json @@ -0,0 +1,36 @@ +# This is an example configuration file for DHCPv6 server in Kea. +# It's a basic scenario with four IPv6 subnets configured. In each +# subnet, there's a smaller pool of dynamic addresses. + +{ "Dhcp6": + +{ +# Kea is told to listen on eth0 interface only. + "interfaces": [ "eth0" ], + +# Addresses will be assigned with preferred and valid lifetimes +# being 3000 and 4000, respectively. 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). + "preferred-lifetime": 3000, + "valid-lifetime": 4000, + "renew-timer": 1000, + "rebind-timer": 2000, + +# The following list defines subnets. Each subnet consists of at +# least subnet and pool entries. + "subnet6": [ + { "pool": [ "2001:db8:1::/80" ], + "subnet": "2001:db8:1::/64" }, + { "pool": [ "2001:db8:2::/80" ], + "subnet": "2001:db8:2::/64" }, + { "pool": [ "2001:db8:3::/80" ], + "subnet": "2001:db8:3::/64" }, + { "pool": [ "2001:db8:4::/80" ], + "subnet": "2001:db8:4::/64" } ] +} + +} + diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml index 44d804c7c1b32da46154c366bd8f058f8c3fd7ae..9bf190395ee1c6c7ef10f322ed5109173917bfca 100644 --- a/doc/guide/bind10-guide.xml +++ b/doc/guide/bind10-guide.xml @@ -710,7 +710,9 @@ as a dependency earlier --> For additional instructions concerning the building and installation of - Kea, see . + Kea for various databases, see . + For additional instructions concerning configuration backends, see + . @@ -1864,6 +1866,46 @@ address, but the usual ones don't." mean? --> The DHCP-DDNS server details are covered in + +
+ Selecting configuration backend + 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. This is currently only supported by DHCPv6 component. + + + + + BIND10 + + 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 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. + + + + + JSON + + 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. + + + + +
+
DHCP Database Installation and Configuration diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am index 0b4a75fc54dfced482ff9720f15c3f504ee53bde..8f0dd76f1240d885b15f25dabba9f8c090d15b40 100644 --- a/src/bin/dhcp6/Makefile.am +++ b/src/bin/dhcp6/Makefile.am @@ -52,10 +52,18 @@ BUILT_SOURCES = spec_config.h dhcp6_messages.h dhcp6_messages.cc pkglibexec_PROGRAMS = b10-dhcp6 b10_dhcp6_SOURCES = main.cc -b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h -b10_dhcp6_SOURCES += config_parser.cc config_parser.h b10_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h +b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h +b10_dhcp6_SOURCES += json_config_parser.cc json_config_parser.h + +if CONFIG_BACKEND_BUNDY +b10_dhcp6_SOURCES += bundy_controller.cc +endif + +if CONFIG_BACKEND_JSON +b10_dhcp6_SOURCES += kea_controller.cc +endif nodist_b10_dhcp6_SOURCES = dhcp6_messages.h dhcp6_messages.cc EXTRA_DIST += dhcp6_messages.mes diff --git a/src/bin/dhcp6/bundy_controller.cc b/src/bin/dhcp6/bundy_controller.cc new file mode 100644 index 0000000000000000000000000000000000000000..2a6b4092da3a19bc9373d9f716bfdb58baa10125 --- /dev/null +++ b/src/bin/dhcp6/bundy_controller.cc @@ -0,0 +1,227 @@ +// 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 + +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 dhcp6ConfigHandler 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 +dhcp6StubConfigHandler(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 (!ControlledDhcpv6Srv::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(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE) + .arg(merged_config->str()); + } + + // Configure the server. + + return (ControlledDhcpv6Srv::processConfig(merged_config)); +} + +void +ControlledDhcpv6Srv::init(const std::string& /* config_file*/) { + // This is Bundy configuration backed. It established control session + // that is used to connect to Bundy framework. + // + // Creates session that will be used to receive commands and updated + // configuration from cfgmgr (or indirectly from user via bindctl). + + string specfile; + if (getenv("B10_FROM_BUILD")) { + specfile = string(getenv("B10_FROM_BUILD")) + + "/src/bin/dhcp6/dhcp6.spec"; + } else { + specfile = string(DHCP6_SPECFILE_LOCATION); + } + + /// @todo: Check if session is not established already. Throw, if it is. + + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_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_, + dhcp6StubConfigHandler, + processCommand, false); + config_session_->start(); + + // The constructor already pulled the configuration that had + // been created in the previous session thanks to the dummy + // handler. We can switch to the handler that will be + // parsing future changes to the configuration. + config_session_->setConfigHandler(bundyConfigHandler); + + try { + // Pull the full configuration out from the session. + processConfig(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()); + + } catch (const std::exception& ex) { + LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what()); + + } + + /// Integrate the asynchronous I/O model of Bundy configuration + /// control with the "select" model of the DHCP server. This is + /// fully explained in \ref dhcpv6Session. + int ctrl_socket = cc_session_->getSocketDesc(); + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTED) + .arg(ctrl_socket); + IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader); + + return; +} + +void ControlledDhcpv6Srv::cleanup() { + if (config_session_) { + delete config_session_; + config_session_ = NULL; + } + if (cc_session_) { + + int ctrl_socket = cc_session_->getSocketDesc(); + cc_session_->disconnect(); + + // deregister session socket + IfaceMgr::instance().deleteExternalSocket(ctrl_socket); + + delete cc_session_; + cc_session_ = NULL; + } +} + +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); +} + +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc index dea65ac3eb5ced3413e21b1fbd31525f617a6bac..3408e3be50d31a7898e03d8e2e27d5a338d7e579 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2013 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,35 +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 -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 { @@ -50,73 +29,105 @@ namespace dhcp { ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL; ConstElementPtr -ControlledDhcpv6Srv::dhcp6StubConfigHandler(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.")); +ControlledDhcpv6Srv::commandShutdownHandler(const string&, ConstElementPtr) { + if (ControlledDhcpv6Srv::server_) { + ControlledDhcpv6Srv::server_->shutdown(); + } else { + LOG_WARN(dhcp6_logger, DHCP6_NOT_RUNNING); + ConstElementPtr answer = isc::config::createAnswer(1, "Shutdown failure."); + return (answer); + } + ConstElementPtr answer = isc::config::createAnswer(0, "Shutting down."); + return (answer); } ConstElementPtr -ControlledDhcpv6Srv::dhcp6ConfigHandler(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."); +ControlledDhcpv6Srv::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(dhcp6_logger, DHCP6_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 +ControlledDhcpv6Srv::commandConfigReloadHandler(const string&, ConstElementPtr args) { + + return (processConfig(args)); +} - // 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. +isc::data::ConstElementPtr +ControlledDhcpv6Srv::processCommand(const std::string& command, + isc::data::ConstElementPtr args) { + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_COMMAND_RECEIVED) + .arg(command).arg(args->str()); - // 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()); + ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance(); - // Merge an existing and new configuration. - isc::data::merge(merged_config, new_config); - LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE) - .arg(merged_config->str()); + if (!srv) { + ConstElementPtr no_srv = isc::config::createAnswer(1, + "Server object not initialized, can't process command '" + + command + "'."); + return (no_srv); } - // Configure the server. - ConstElementPtr answer = configureDhcp6Server(*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) { - return (answer); + } else if (command == "libreload") { + return (srv->commandLibReloadHandler(command, args)); + + } else if (command == "config-reload") { + return (srv->commandConfigReloadHandler(command, args)); + } + + return (isc::config::createAnswer(1, "Unrecognized command:" + + command)); + + } catch (const Exception& ex) { + return (isc::config::createAnswer(1, "Error while processing command '" + + command + "':" + ex.what())); + } +} + + +isc::data::ConstElementPtr +ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) { + ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance(); + + if (!srv) { + ConstElementPtr no_srv = isc::config::createAnswer(1, + "Server object not initialized, can't process config."); + return (no_srv); + } + + ConstElementPtr answer = configureDhcp6Server(*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) { + return (isc::config::createAnswer(1, "Failed to process configuration:" + + string(ex.what()))); } // 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 " @@ -129,141 +140,22 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) { // 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()); + srv->openActiveSockets(srv->getPort()); } catch (const 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); -} - -ConstElementPtr -ControlledDhcpv6Srv::dhcp6CommandHandler(const string& command, ConstElementPtr args) { - LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_COMMAND_RECEIVED) - .arg(command).arg(args->str()); - - if (command == "shutdown") { - if (ControlledDhcpv6Srv::server_) { - ControlledDhcpv6Srv::server_->shutdown(); - } else { - LOG_WARN(dhcp6_logger, DHCP6_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(dhcp6_logger, DHCP6_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 ControlledDhcpv6Srv::sessionReader(void) { - // Process one asio event. If there are more events, iface_mgr will call - // this callback more than once. +ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port) + : Dhcpv6Srv(port) { if (server_) { - server_->io_service_.run_one(); - } -} - -void ControlledDhcpv6Srv::establishSession() { - - string specfile; - if (getenv("B10_FROM_BUILD")) { - specfile = string(getenv("B10_FROM_BUILD")) + - "/src/bin/dhcp6/dhcp6.spec"; - } else { - specfile = string(DHCP6_SPECFILE_LOCATION); - } - - /// @todo: Check if session is not established already. Throw, if it is. - - LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_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_, - dhcp6StubConfigHandler, - dhcp6CommandHandler, false); - config_session_->start(); - - // The constructor already pulled the configuration that had - // been created in the previous session thanks to the dummy - // handler. We can switch to the handler that will be - // parsing future changes to the configuration. - config_session_->setConfigHandler(dhcp6ConfigHandler); - - try { - // Pull the full configuration out from the session. - configureDhcp6Server(*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()); - - } catch (const std::exception& ex) { - LOG_ERROR(dhcp6_logger, DHCP6_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 dhcpv6Session. - int ctrl_socket = cc_session_->getSocketDesc(); - LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTED) - .arg(ctrl_socket); - IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader); -} - -void ControlledDhcpv6Srv::disconnectSession() { - if (config_session_) { - delete config_session_; - config_session_ = NULL; - } - if (cc_session_) { - - int ctrl_socket = cc_session_->getSocketDesc(); - cc_session_->disconnect(); - - // deregister session socket - IfaceMgr::instance().deleteExternalSocket(ctrl_socket); - - delete cc_session_; - cc_session_ = NULL; + isc_throw(InvalidOperation, + "There is another Dhcpv6Srv instance already."); } -} - -ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port) - : Dhcpv6Srv(port), cc_session_(NULL), config_session_(NULL) { server_ = this; // remember this instance for use in callback } @@ -273,22 +165,19 @@ void ControlledDhcpv6Srv::shutdown() { } ControlledDhcpv6Srv::~ControlledDhcpv6Srv() { - disconnectSession(); + cleanup(); server_ = NULL; // forget this instance. There should be no callback anymore // at this stage anyway. } -isc::data::ConstElementPtr -ControlledDhcpv6Srv::execDhcpv6ServerCommand(const std::string& command_id, - isc::data::ConstElementPtr args) { - try { - return (dhcp6CommandHandler(command_id, args)); - } catch (const Exception& ex) { - ConstElementPtr answer = isc::config::createAnswer(1, ex.what()); - return (answer); +void ControlledDhcpv6Srv::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(); } } -}; -}; +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h index 62c1813cb63404369a470b9b7a385b23f40bdec7..c0a5a5709c4e13e9e07bf8d4cd442e650f8c43ff 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.h +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.h @@ -44,44 +44,53 @@ public: ControlledDhcpv6Srv(uint16_t port = DHCP6_SERVER_PORT); /// @brief Destructor. - ~ControlledDhcpv6Srv(); + virtual ~ControlledDhcpv6Srv(); - /// @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 DHCPv6 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 /// /// @return status of the command static isc::data::ConstElementPtr - execDhcpv6ServerCommand(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 ControlledDhcpv6Srv* 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. @@ -90,53 +99,71 @@ protected: /// /// @return status of the config update static isc::data::ConstElementPtr - dhcp6ConfigHandler(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 dhcp6ConfigHandler 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 - dhcp6StubConfigHandler(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 Dhcpv6Srv /// - /// @param command textual representation of the command - /// @param args parameters of the command + /// @note may return NULL, if called before server is spawned + static ControlledDhcpv6Srv* 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 - dhcp6CommandHandler(const std::string& command, isc::data::ConstElementPtr args); + /// This is required for config and command handlers to gain access to + /// the server. Some of them need to be static methods. + static ControlledDhcpv6Srv* 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::receive6() method, - /// when there is a new command or configuration sent over msgq. + /// 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/dhcp6/dhcp6.dox b/src/bin/dhcp6/dhcp6.dox index 81f52c653d456f8cd0d3ac70b2b5f3cf4a97fe5c..1571c93c911b91856cb9d05b8c47937907a45ca2 100644 --- a/src/bin/dhcp6/dhcp6.dox +++ b/src/bin/dhcp6/dhcp6.dox @@ -223,6 +223,45 @@ being passed in isc::dhcp::Dhcpv6Srv::selectSubnet() to isc::dhcp::CfgMgr::getSu Currently this capability is usable, but the number of scenarios it supports is limited. + @section dhcpv6ConfigBackend Configuration backend for DHCPv6 + +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: BIND10 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 +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. + +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. + +Internally, configuration backends are implemented as different implementations of the +isc::dhcp::ControlledDhcpv6Srv class, stored in ctrl_*_dhcpv6_srv.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. +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 dhcpv6Other Other DHCPv6 topics For hooks API support in DHCPv6, see @ref dhcpv6Hooks. diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes index e3903a41c041dcf66268bca2bb131926ac2733cb..828ab65f042fba75c4805af0b16705738bcdf415 100644 --- a/src/bin/dhcp6/dhcp6_messages.mes +++ b/src/bin/dhcp6/dhcp6_messages.mes @@ -499,10 +499,11 @@ to recover. The IPv6 DHCP server has encountered a fatal error and is terminating. The reason for the failure is included in the message. -% DHCP6_SESSION_FAIL failed to establish BIND 10 session (%1), running stand-alone -The server has failed to establish communication with the rest of BIND -10 and is running in stand-alone mode. (This behavior will change once -the IPv6 DHCP server is properly integrated with the rest of BIND 10.) +% DHCP6_INIT_FAIL failed to initialize Kea server: %1 +The server has failed to establish communication with the rest of BIND 10, +failed to read JSON configuration file or excountered any other critical +issue that prevents it from starting up properly. Attached error message +provides more details about the issue. % DHCP6_SHUTDOWN server shutdown The IPv6 DHCP server has terminated normally. diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index 24655291d0b25c1084bf4564919243fe930d4178..bac344dea7949cc542802d076a0113555073fa67 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -27,8 +27,7 @@ #include #include #include - -#include +#include #include #include @@ -52,11 +51,7 @@ public: /// that is going to be used as server-identifier, receives incoming /// packets, processes them, manages leases assignment and generates /// appropriate responses. -/// -/// @note Only one instance of this class is instantiated as it encompasses -/// the whole operation of the server. Nothing, however, enforces the -/// singleton status of the object. -class Dhcpv6Srv : public boost::noncopyable { +class Dhcpv6Srv : public Daemon { public: /// @brief defines if certain option may, must or must not appear diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/json_config_parser.cc similarity index 99% rename from src/bin/dhcp6/config_parser.cc rename to src/bin/dhcp6/json_config_parser.cc index 7fbade394ce3b662afe0d2d08e70c0a1924be2ac..79365bdfefbd8ef985dda302d1782925d06e796f 100644 --- a/src/bin/dhcp6/config_parser.cc +++ b/src/bin/dhcp6/json_config_parser.cc @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/json_config_parser.h similarity index 100% rename from src/bin/dhcp6/config_parser.h rename to src/bin/dhcp6/json_config_parser.h diff --git a/src/bin/dhcp6/kea_controller.cc b/src/bin/dhcp6/kea_controller.cc new file mode 100644 index 0000000000000000000000000000000000000000..a5b357dd85cd938fab926755bd385833b030b609 --- /dev/null +++ b/src/bin/dhcp6/kea_controller.cc @@ -0,0 +1,143 @@ +// 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 +#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 isc { +namespace dhcp { + +void +ControlledDhcpv6Srv::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 dhcp6; + 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 = Element::fromJSONFile(file_name, true); + + if (!json) { + LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL) + .arg("Config file " + file_name + " missing or empty."); + isc_throw(BadValue, "Unable to process JSON configuration file:" + + file_name); + } + + // Get Dhcp6 component from the config + dhcp6 = json->get("Dhcp6"); + + if (!dhcp6) { + LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL) + .arg("Config file " + file_name + " does not include 'Dhcp6' entry."); + isc_throw(BadValue, "Unable to process JSON configuration file:" + + file_name); + } + + // Use parsed JSON structures to configure the server + result = processCommand("config-reload", dhcp6); + + } catch (const std::exception& ex) { + LOG_ERROR(dhcp6_logger, DHCP6_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 configureDhcp6Server returns a pointer, it is theoretically + // possible that it will return NULL. + LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL) + .arg("Configuration failed: Undefined result of configureDhcp6Server" + "() function after attempting to read " + file_name); + return; + } + + // Now check is the returned result is successful (rcode=0) or not + ConstElementPtr comment; /// see @ref isc::config::parseAnswer + int rcode; + comment = parseAnswer(rcode, result); + if (rcode != 0) { + string reason = ""; + if (comment) { + reason = string(" (") + comment->stringValue() + string(")"); + } + LOG_ERROR(dhcp6_logger, DHCP6_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 ControlledDhcpv6Srv::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, bool ) { + + 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/dhcp6/main.cc b/src/bin/dhcp6/main.cc index 269eae4290bc1a36c7af0473cd88850de0fd8e72..49c70ec319227d5c334a91adb649e94e8c1e52a9 100644 --- a/src/bin/dhcp6/main.cc +++ b/src/bin/dhcp6/main.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -38,13 +39,16 @@ using namespace std; namespace { const char* const DHCP6_NAME = "b10-dhcp6"; +const char* const DHCP6_LOGGER_NAME = "kea"; + void usage() { - cerr << "Usage: " << DHCP6_NAME << " [-v] [-s] [-p number]" << endl; + cerr << "Usage: " << DHCP6_NAME << " [-v] [-s] [-p port_number] [-c cfgfile]" << endl; cerr << " -v: verbose output" << endl; - cerr << " -s: stand-alone mode (don't connect to BIND10)" << 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; exit(EXIT_FAILURE); } } // end of anonymous namespace @@ -57,17 +61,20 @@ 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; break; - case 's': + case 's': // stand-alone stand_alone = true; break; - case 'p': + case 'p': // port number try { port_number = boost::lexical_cast(optarg); } catch (const boost::bad_lexical_cast &) { @@ -82,6 +89,10 @@ main(int argc, char* argv[]) { } break; + case 'c': // config file + config_file = optarg; + break; + default: usage(); } @@ -92,39 +103,48 @@ 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(DHCP6_NAME, - (verbose_mode ? isc::log::DEBUG : isc::log::INFO), - isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone); - LOG_INFO(dhcp6_logger, DHCP6_STARTING); - 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" ); 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); + + 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" ); + + LOG_INFO(dhcp6_logger, DHCP6_STARTING); + ControlledDhcpv6Srv server(port_number); + if (!stand_alone) { try { - server.establishSession(); + // Initialize the server, i.e. establish control session + // if BIND10 backend is used or read a configuration file + server.init(config_file); + } catch (const std::exception& ex) { - LOG_ERROR(dhcp6_logger, DHCP6_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 + 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(); + + cerr << "Failed to initialize server: " << ex.what() << endl; + return (EXIT_FAILURE); } } else { LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_STANDALONE); } + server.run(); LOG_INFO(dhcp6_logger, DHCP6_SHUTDOWN); } catch (const std::exception& ex) { LOG_FATAL(dhcp6_logger, DHCP6_SERVER_FAILED).arg(ex.what()); + cerr << "Fatal error during start up: " << ex.what() << endl; ret = EXIT_FAILURE; } diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am index 424bd5b29bdc7a920db40343f0a9bc50dd8cd80c..1c8b6e5a69b32b11ccbe792570ca6fcbc31a3018 100644 --- a/src/bin/dhcp6/tests/Makefile.am +++ b/src/bin/dhcp6/tests/Makefile.am @@ -24,6 +24,7 @@ check-local: AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header AM_CPPFLAGS += -I$(top_srcdir)/src/bin +AM_CPPFLAGS += -DTOP_BUILDDIR="\"$(top_builddir)\"" AM_CPPFLAGS += $(BOOST_INCLUDES) AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" @@ -75,17 +76,29 @@ dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc dhcp6_unittests_SOURCES += fqdn_unittest.cc dhcp6_unittests_SOURCES += hooks_unittest.cc dhcp6_unittests_SOURCES += dhcp6_test_utils.cc dhcp6_test_utils.h -dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc -dhcp6_unittests_SOURCES += config_parser_unittest.cc dhcp6_unittests_SOURCES += d2_unittest.cc d2_unittest.h dhcp6_unittests_SOURCES += marker_file.cc dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc -dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc -dhcp6_unittests_SOURCES += ../config_parser.cc ../config_parser.h +dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.h ../ctrl_dhcp6_srv.cc +dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc dhcp6_unittests_SOURCES += wireshark.cc dhcp6_unittests_SOURCES += dhcp6_client.cc dhcp6_client.h dhcp6_unittests_SOURCES += rebind_unittest.cc +dhcp6_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h +dhcp6_unittests_SOURCES += config_parser_unittest.cc + +if CONFIG_BACKEND_BUNDY +# For Bundy backend, we only need to run the usual tests. There are no +# Bundy-specific tests yet. +dhcp6_unittests_SOURCES += ../bundy_controller.cc +dhcp6_unittests_SOURCES += bundy_controller_unittest.cc +endif + +if CONFIG_BACKEND_JSON +dhcp6_unittests_SOURCES += ../kea_controller.cc +dhcp6_unittests_SOURCES += kea_controller_unittest.cc +endif nodist_dhcp6_unittests_SOURCES = ../dhcp6_messages.h ../dhcp6_messages.cc nodist_dhcp6_unittests_SOURCES += marker_file.h test_libraries.h diff --git a/src/bin/dhcp6/tests/bundy_controller_unittest.cc b/src/bin/dhcp6/tests/bundy_controller_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..f556560a149e6254c6cd9350a2263c1537eee8ac --- /dev/null +++ b/src/bin/dhcp6/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/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc index b77ecbfb19d0b5a640e51c14edf796966098a0f1..36f9a861a382fafac5c69b1bb1e8ed8716217cb1 100644 --- a/src/bin/dhcp6/tests/config_parser_unittest.cc +++ b/src/bin/dhcp6/tests/config_parser_unittest.cc @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include @@ -591,6 +591,11 @@ TEST_F(Dhcp6ParserTest, multipleSubnets) { ElementPtr json = Element::fromJSON(config); + ofstream out("config.json"); + out << config; + out.close(); + + do { EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json)); checkResult(x, 0); diff --git a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc index 6dfb9811f9a77f0ada553761f4b9c2befc5b0f32..bd0b88f987830cc3c9602dbecbb1a0544551dd28 100644 --- a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include @@ -25,17 +25,7 @@ #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::dhcp::test; @@ -85,13 +75,13 @@ TEST_F(CtrlDhcpv6SrvTest, commands) { int rcode = -1; // Case 1: send bogus command - ConstElementPtr result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("blah", params); - ConstElementPtr comment = parseAnswer(rcode, result); + ConstElementPtr result = ControlledDhcpv6Srv::processCommand("blah", params); + ConstElementPtr comment = isc::config::parseAnswer(rcode, result); EXPECT_EQ(1, rcode); // expect failure (no such command as blah) // Case 2: send shutdown command without any parameters - result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params); - comment = parseAnswer(rcode, result); + result = ControlledDhcpv6Srv::processCommand("shutdown", params); + comment = isc::config::parseAnswer(rcode, result); EXPECT_EQ(0, rcode); // expect success const pid_t pid(getpid()); @@ -99,14 +89,21 @@ TEST_F(CtrlDhcpv6SrvTest, commands) { params->set("pid", x); // Case 3: send shutdown command with 1 parameter: pid - result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params); - comment = parseAnswer(rcode, result); + result = ControlledDhcpv6Srv::processCommand("shutdown", params); + comment = isc::config::parseAnswer(rcode, result); EXPECT_EQ(0, rcode); // Expect success } // Check that the "libreload" command will reload libraries - TEST_F(CtrlDhcpv6SrvTest, libreload) { + + // Sending commands for processing now requires a server that can process + // them. + boost::scoped_ptr srv; + ASSERT_NO_THROW( + srv.reset(new ControlledDhcpv6Srv(0)) + ); + // Ensure no marker files to start with. ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE)); ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE)); @@ -137,8 +134,8 @@ TEST_F(CtrlDhcpv6SrvTest, libreload) { int rcode = -1; ConstElementPtr result = - ControlledDhcpv6Srv::execDhcpv6ServerCommand("libreload", params); - ConstElementPtr comment = parseAnswer(rcode, result); + ControlledDhcpv6Srv::processCommand("libreload", params); + ConstElementPtr comment = isc::config::parseAnswer(rcode, result); EXPECT_EQ(0, rcode); // Expect success // Check that the libraries have unloaded and reloaded. The libraries are @@ -148,4 +145,58 @@ TEST_F(CtrlDhcpv6SrvTest, libreload) { EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "1212")); } +// Check that the "configReload" command will reload libraries +TEST_F(CtrlDhcpv6SrvTest, configReload) { + + // Sending commands for processing now requires a server that can process + // them. + boost::scoped_ptr srv; + ASSERT_NO_THROW( + srv.reset(new ControlledDhcpv6Srv(0)) + ); + + // Now execute the "libreload" command. This should cause the libraries + // to unload and to reload. + + // Use empty parameters list + // Prepare configuration file. + string config_txt = "{ \"interfaces\": [ \"*\" ]," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ { " + " \"pool\": [ \"2001:db8:1::/80\" ]," + " \"subnet\": \"2001:db8:1::/64\" " + " }," + " {" + " \"pool\": [ \"2001:db8:2::/80\" ]," + " \"subnet\": \"2001:db8:2::/64\", " + " \"id\": 0" + " }," + " {" + " \"pool\": [ \"2001:db8:3::/80\" ]," + " \"subnet\": \"2001:db8:3::/64\" " + " } ]," + "\"valid-lifetime\": 4000 }"; + + ElementPtr config = Element::fromJSON(config_txt); + + // Make sure there are no subnets configured. + CfgMgr::instance().deleteSubnets6(); + + // Now send the command + int rcode = -1; + ConstElementPtr result = + ControlledDhcpv6Srv::processCommand("config-reload", config); + ConstElementPtr comment = isc::config::parseAnswer(rcode, result); + EXPECT_EQ(0, rcode); // Expect success + + // Check that the config was indeed applied. + const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6(); + EXPECT_EQ(3, subnets->size()); + + // Clean up after the test. + CfgMgr::instance().deleteSubnets6(); +} + } // End of anonymous namespace diff --git a/src/bin/dhcp6/tests/d2_unittest.cc b/src/bin/dhcp6/tests/d2_unittest.cc index de140d614919b68dad621819e61ce889b427fb82..1f92c480dd336fad216187c233fdd2556c334a68 100644 --- a/src/bin/dhcp6/tests/d2_unittest.cc +++ b/src/bin/dhcp6/tests/d2_unittest.cc @@ -13,7 +13,7 @@ // PERFORMANCE OF THIS SOFTWARE. #include -#include +#include #include #include diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc index 231464bb71449f1359d366aa8963d762a8b63eb0..01e58262513e16249c9548bcb164df75ae988d14 100644 --- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include #include @@ -197,12 +197,12 @@ TEST_F(Dhcpv6SrvTest, basic) { ASSERT_NO_THROW( { // Skip opening any sockets - srv.reset(new Dhcpv6Srv(0)); + srv.reset(new NakedDhcpv6Srv(0)); }); srv.reset(); ASSERT_NO_THROW({ // open an unpriviledged port - srv.reset(new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000)); + srv.reset(new NakedDhcpv6Srv(DHCP6_SERVER_PORT + 10000)); }); } diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.cc b/src/bin/dhcp6/tests/dhcp6_test_utils.cc index 9207d4dbf749cc6d7f10f8d2ef67c0addd81c761..59ac50c9a90ffd5a0dc8c605c84d40393f6e3cc2 100644 --- a/src/bin/dhcp6/tests/dhcp6_test_utils.cc +++ b/src/bin/dhcp6/tests/dhcp6_test_utils.cc @@ -14,7 +14,7 @@ #include #include -#include +#include using namespace isc::data; using namespace isc::dhcp; diff --git a/src/bin/dhcp6/tests/hooks_unittest.cc b/src/bin/dhcp6/tests/hooks_unittest.cc index 3b3a66a954dbaf6673fa8564454f3778650ed5b6..139ef38e9f67a4779496e4ed187c3da549eefdd2 100644 --- a/src/bin/dhcp6/tests/hooks_unittest.cc +++ b/src/bin/dhcp6/tests/hooks_unittest.cc @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/bin/dhcp6/tests/kea_controller_unittest.cc b/src/bin/dhcp6/tests/kea_controller_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..3198561345742e7e54981fd2dd394203f74ebc88 --- /dev/null +++ b/src/bin/dhcp6/tests/kea_controller_unittest.cc @@ -0,0 +1,238 @@ +// 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 + +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 NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv { + // "Naked" DHCPv6 server, exposes internal fields +public: + NakedControlledDhcpv6Srv():ControlledDhcpv6Srv(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 = "{ \"Dhcp6\": { \"interfaces\": [ \"*\" ]," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ { " + " \"pool\": [ \"2001:db8:1::/80\" ]," + " \"subnet\": \"2001:db8:1::/64\" " + " }," + " {" + " \"pool\": [ \"2001:db8:2::/80\" ]," + " \"subnet\": \"2001:db8:2::/64\", " + " \"id\": 0" + " }," + " {" + " \"pool\": [ \"2001:db8:3::/80\" ]," + " \"subnet\": \"2001:db8:3::/64\" " + " } ]," + "\"valid-lifetime\": 4000 }" + "}"; + writeFile(TEST_FILE, config); + + // Now initialize the server + boost::scoped_ptr srv; + ASSERT_NO_THROW( + srv.reset(new ControlledDhcpv6Srv(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 Subnet6Collection* subnets = CfgMgr::instance().getSubnets6(); + ASSERT_TRUE(subnets); + ASSERT_EQ(3, subnets->size()); // We expect 3 subnets. + + + // Check subnet 1. + EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText()); + EXPECT_EQ(64, 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("2001:db8:1::", pools1.at(0)->getFirstAddress().toText()); + EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText()); + EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType()); + + // Check subnet 2. + EXPECT_EQ("2001:db8:2::", subnets->at(1)->get().first.toText()); + EXPECT_EQ(64, subnets->at(1)->get().second); + + // Check pools in the second subnet. + const PoolCollection& pools2 = subnets->at(1)->getPools(Lease::TYPE_NA); + ASSERT_EQ(1, pools2.size()); + EXPECT_EQ("2001:db8:2::", pools2.at(0)->getFirstAddress().toText()); + EXPECT_EQ("2001:db8:2::ffff:ffff:ffff", pools2.at(0)->getLastAddress().toText()); + EXPECT_EQ(Lease::TYPE_NA, pools2.at(0)->getType()); + + // And finally check subnet 3. + EXPECT_EQ("2001:db8:3::", subnets->at(2)->get().first.toText()); + EXPECT_EQ(64, subnets->at(2)->get().second); + + // ... and it's only pool. + const PoolCollection& pools3 = subnets->at(2)->getPools(Lease::TYPE_NA); + EXPECT_EQ("2001:db8:3::", pools3.at(0)->getFirstAddress().toText()); + EXPECT_EQ("2001:db8:3::ffff:ffff:ffff", 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" + "{ \"Dhcp6\": { \"interfaces\": [ \"*\" ]," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, \n" + "# comments in the middle should be ignored, too\n" + "\"subnet6\": [ { " + " \"pool\": [ \"2001:db8:1::/80\" ]," + " \"subnet\": \"2001:db8:1::/64\" " + " } ]," + "\"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 ControlledDhcpv6Srv(0)) + ); + + // And configure it using config without + EXPECT_NO_THROW(srv->init(TEST_FILE)); + + // Now check if the configuration has been applied correctly. + const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6(); + ASSERT_TRUE(subnets); + ASSERT_EQ(1, subnets->size()); + + // Check subnet 1. + EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText()); + EXPECT_EQ(64, 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("2001:db8:1::", pools1.at(0)->getFirstAddress().toText()); + EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", 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 Dhcp6 element +// - Config file that contains Dhcp6 but has a content error +TEST_F(JSONFileBackendTest, configBroken) { + + // Empty config is not allowed, because Dhcp6 element is missing + string config_empty = ""; + + // This config does not have mandatory Dhcp6 element + string config_v4 = "{ \"Dhcp4\": { \"interfaces\": [ \"*\" ]," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet4\": [ { " + " \"pool\": [ \"192.0.2.0/24\" ]," + " \"subnet\": \"192.0.2.0/24\" " + " } ]}"; + + // This has Dhcp6 element, but it's utter nonsense + string config_nonsense = "{ \"Dhcp6\": { \"reviews\": \"are so much fun\" } }"; + + // Now initialize the server + boost::scoped_ptr srv; + ASSERT_NO_THROW( + srv.reset(new ControlledDhcpv6Srv(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 Dhcp6 component. + writeFile(TEST_FILE, config_v4); + EXPECT_THROW(srv->init(TEST_FILE), BadValue); + + // Now try to load a config with Dhcp6 full of nonsense. + writeFile(TEST_FILE, config_nonsense); + EXPECT_THROW(srv->init(TEST_FILE), BadValue); +} + +} // End of anonymous namespace diff --git a/src/bin/dhcp6/tests/rebind_unittest.cc b/src/bin/dhcp6/tests/rebind_unittest.cc index 2de10d1b4ff487449ad2ac2934d8f24a328115e6..b6262f7f15c6a4368cfe40869bf5975441502f4a 100644 --- a/src/bin/dhcp6/tests/rebind_unittest.cc +++ b/src/bin/dhcp6/tests/rebind_unittest.cc @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc index 075447abaac479b5000a075b4b335c42a76d11fd..26c5673bab232dc3e5718f1c76f524479154537b 100644 --- a/src/lib/cc/data.cc +++ b/src/lib/cc/data.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -610,17 +611,29 @@ Element::nameToType(const std::string& type_name) { } ElementPtr -Element::fromJSON(std::istream& in) throw(JSONError) { +Element::fromJSON(std::istream& in, bool preproc) throw(JSONError) { + int line = 1, pos = 1; - return (fromJSON(in, "", line, pos)); + stringstream filtered; + if (preproc) { + preprocess(in, filtered); + } + + ElementPtr value = fromJSON(preproc ? filtered : in, "", line, pos); + + return (value); } ElementPtr -Element::fromJSON(std::istream& in, const std::string& file_name) +Element::fromJSON(std::istream& in, const std::string& file_name, bool preproc) throw(JSONError) { int line = 1, pos = 1; - return (fromJSON(in, file_name, line, pos)); + stringstream filtered; + if (preproc) { + preprocess(in, filtered); + } + return (fromJSON(preproc ? filtered : in, file_name, line, pos)); } ElementPtr @@ -698,11 +711,16 @@ Element::fromJSON(std::istream& in, const std::string& file, int& line, } ElementPtr -Element::fromJSON(const std::string& in) { +Element::fromJSON(const std::string& in, bool preproc) { std::stringstream ss; ss << in; + int line = 1, pos = 1; - ElementPtr result(fromJSON(ss, "", line, pos)); + stringstream filtered; + if (preproc) { + preprocess(ss, filtered); + } + ElementPtr result(fromJSON(preproc ? filtered : ss, "", line, pos)); skipChars(ss, WHITESPACE, line, pos); // ss must now be at end if (ss.peek() != EOF) { @@ -711,6 +729,20 @@ Element::fromJSON(const std::string& in) { return result; } +ElementPtr +Element::fromJSONFile(const std::string& file_name, + bool preproc) { + std::ifstream infile(file_name.c_str(), std::ios::in | std::ios::binary); + if (!infile.is_open()) + { + const char* error = strerror(errno); + isc_throw(InvalidOperation, "Failed to read file " << file_name + << ",error:" << error); + } + + return (fromJSON(infile, file_name, preproc)); +} + // to JSON format void @@ -1029,5 +1061,24 @@ merge(ElementPtr element, ConstElementPtr other) { } } +void Element::preprocess(std::istream& in, std::stringstream& out) { + + std::string line; + + while (std::getline(in, line)) { + // If this is a comments line, replace it with empty line + // (so the line numbers will still match + if (!line.empty() && line[0] == '#') { + line = ""; + } + + // getline() removes end line charaters. Unfortunately, we need + // it for getting the line numbers right (in case we report an + // error. + out << line; + out << "\n"; + } +} + } } diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h index 44832188bc57bb2eadec9542eebb45a66d4d0875..8859af72c4efca3675885c21dfe10c629959e2f1 100644 --- a/src/lib/cc/data.h +++ b/src/lib/cc/data.h @@ -151,6 +151,7 @@ protected: : type_(t), position_(pos) { } + public: // any is a special type used in list specifications, specifying @@ -401,18 +402,34 @@ public: //@{ /// Creates an Element from the given JSON string /// \param in The string to parse the element from + /// \param preproc specified whether preprocessing (e.g. comment removal) + /// should be performed /// \return An ElementPtr that contains the element(s) specified /// in the given string. - static ElementPtr fromJSON(const std::string& in); + static ElementPtr fromJSON(const std::string& in, bool preproc = false); /// Creates an Element from the given input stream containing JSON /// formatted data. /// /// \param in The string to parse the element from + /// \param preproc specified whether preprocessing (e.g. comment removal) + /// should be performed /// \return An ElementPtr that contains the element(s) specified /// in the given input stream. - static ElementPtr fromJSON(std::istream& in) throw(JSONError); - static ElementPtr fromJSON(std::istream& in, const std::string& file_name) + static ElementPtr fromJSON(std::istream& in, bool preproc = false) + throw(JSONError); + + /// Creates an Element from the given input stream containing JSON + /// formatted data. + /// + /// \param in The string to parse the element from + /// \param file_name specified input file name (used in error reporting) + /// \param preproc specified whether preprocessing (e.g. comment removal) + /// should be performed + /// \return An ElementPtr that contains the element(s) specified + /// in the given input stream. + static ElementPtr fromJSON(std::istream& in, const std::string& file_name, + bool preproc = false) throw(JSONError); /// Creates an Element from the given input stream, where we keep @@ -430,6 +447,16 @@ public: static ElementPtr fromJSON(std::istream& in, const std::string& file, int& line, int &pos) throw(JSONError); + + /// Reads contents of specified file and interprets it as JSON. + /// + /// @param file_name name of the file to read + /// @param preproc specified whether preprocessing (e.g. comment removal) + /// should be performed + /// @return An ElementPtr that contains the element(s) specified + /// if the given file. + static ElementPtr fromJSONFile(const std::string& file_name, + bool preproc = false); //@} /// \name Type name conversion functions @@ -448,6 +475,22 @@ public: /// \return the corresponding type value static Element::types nameToType(const std::string& type_name); + /// \brief input text preprocessor + /// + /// This method performs preprocessing of the input stream (which is + /// expected to contain a text version of to be parsed JSON). For now the + /// sole supported operation is bash-style (line starting with #) comment + /// removal, but it will be extended later to cover more cases (C, C++ style + /// comments, file inclusions, maybe macro replacements?). + /// + /// This method processes the whole input stream. It reads all contents of + /// the input stream, filters the content and returns the result in a + /// different stream. + /// + /// @param in input stream to be preprocessed + /// @param out output stream (filtered content will be written here) + static void preprocess(std::istream& in, std::stringstream& out); + /// \name Wire format factory functions /// These function pparse the wireformat at the given stringstream diff --git a/src/lib/cc/tests/Makefile.am b/src/lib/cc/tests/Makefile.am index b2fb1d2f0e4d085f85d88c1d8acff4f9f7685dea..b307dad10f14a1dc31d378dceafca7c8ad28e017 100644 --- a/src/lib/cc/tests/Makefile.am +++ b/src/lib/cc/tests/Makefile.am @@ -21,6 +21,7 @@ if HAVE_GTEST TESTS += run_unittests # (TODO: these need to be completed and moved to tests/) run_unittests_SOURCES = data_unittests.cc session_unittests.cc run_unittests.cc +run_unittests_SOURCES += data_file_unittests.cc run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) diff --git a/src/lib/cc/tests/data_file_unittests.cc b/src/lib/cc/tests/data_file_unittests.cc new file mode 100644 index 0000000000000000000000000000000000000000..082c0df672e6c552159dbab1805022156deab47e --- /dev/null +++ b/src/lib/cc/tests/data_file_unittests.cc @@ -0,0 +1,107 @@ +// 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; +using namespace isc::data; + +namespace { + +/// @brief Test class for testing Deamon class +class DataFileTest : public ::testing::Test { +public: + + /// @brief writes specified text to a file + /// + /// That is an auxilliary funtion used in fileRead() tests. + /// + /// @param content text to be written to disk + void writeFile(const std::string& content) { + // Write sample content to disk + unlink(TEMP_FILE); + std::ofstream write_me(TEMP_FILE); + EXPECT_TRUE(write_me.is_open()); + write_me << content; + write_me.close(); + } + + /// destructor + ~DataFileTest() { + static_cast(unlink(TEMP_FILE)); + } + + /// Name of the temporary file + static const char* TEMP_FILE; +}; + +/// Temporary file name used in some tests +const char* DataFileTest::TEMP_FILE="temp-file.json"; + +// Test checks whether a text file can be read from disk. +TEST_F(DataFileTest, readFileMultiline) { + + const char* no_endline = "{ \"abc\": 123 }"; + const char* with_endline = "{\n \"abc\":\n 123\n }\n"; + + // That's what we expect + ElementPtr exp = Element::fromJSON(no_endline); + + // Write sample content to disk + writeFile(no_endline); + + // Check that the read content is correct + EXPECT_TRUE(exp->equals(*Element::fromJSONFile(TEMP_FILE))); + + // Write sample content to disk + writeFile(with_endline); + + // Check that the read content is correct + EXPECT_TRUE(exp->equals(*Element::fromJSONFile(TEMP_FILE))); +} + +// Test checks whether comments in file are ignored as expected. +TEST_F(DataFileTest, readFileComments) { + const char* commented_content = "# This is a comment\n" + "{ \"abc\":\n" + "# a comment comment\n" + "1 }\n"; + + // That's what we expect + ElementPtr exp = Element::fromJSON("{ \"abc\": 1 }"); + + // Write sample content to disk + writeFile(commented_content); + + // Check that the read will fail (without comment elimination) + EXPECT_THROW(Element::fromJSONFile(TEMP_FILE), JSONError); + + // Check that the read content is correct (with comment elimination) + EXPECT_NO_THROW(Element::fromJSONFile(TEMP_FILE, true)); + EXPECT_TRUE(exp->equals(*Element::fromJSONFile(TEMP_FILE, true))); +} + +// This test checks that missing file will generate an exception. +TEST_F(DataFileTest, readFileError) { + + // Check that the read content is correct + EXPECT_THROW(Element::fromJSONFile("no-such-file.txt"), isc::InvalidOperation); +} + +}; diff --git a/src/lib/cc/tests/data_unittests.cc b/src/lib/cc/tests/data_unittests.cc index 8ddfae9bd84fa7c6d08e9184bd29418baedb342a..fa3e509a9f02ee4b2ad8a22fdb27eaab521434e4 100644 --- a/src/lib/cc/tests/data_unittests.cc +++ b/src/lib/cc/tests/data_unittests.cc @@ -939,6 +939,60 @@ TEST(Element, merge) { } +// This test checks whether it is possible to ignore comments. It also checks +// that the comments are ignored only when told to. +TEST(Element, preprocessor) { + + string no_comment = "{ \"a\": 1,\n" + " \"b\": 2}"; + + string head_comment = "# this is a comment, ignore me\n" + "{ \"a\": 1,\n" + " \"b\": 2}"; + + string mid_comment = "{ \"a\": 1,\n" + "# this is a comment, ignore me\n" + " \"b\": 2}"; + + string tail_comment = "{ \"a\": 1,\n" + " \"b\": 2}" + "# this is a comment, ignore me\n"; + + string dbl_head_comment = "# this is a comment, ignore me\n" + "# second line, still ignored\n" + "{ \"a\": 1,\n" + " \"b\": 2}"; + + string dbl_mid_comment = "{ \"a\": 1,\n" + "# this is a comment, ignore me\n" + "# second line, still ignored\n" + " \"b\": 2}"; + + string dbl_tail_comment = "{ \"a\": 1,\n" + " \"b\": 2}" + "# this is a comment, ignore me\n" + "# second line, still ignored\n"; + + // This is what we expect in all cases. + ElementPtr exp = Element::fromJSON(no_comment); + + // Let's convert them all and see that the result it the same every time + EXPECT_TRUE(exp->equals(*Element::fromJSON(head_comment, true))); + EXPECT_TRUE(exp->equals(*Element::fromJSON(mid_comment, true))); + EXPECT_TRUE(exp->equals(*Element::fromJSON(tail_comment, true))); + EXPECT_TRUE(exp->equals(*Element::fromJSON(dbl_head_comment, true))); + EXPECT_TRUE(exp->equals(*Element::fromJSON(dbl_mid_comment, true))); + EXPECT_TRUE(exp->equals(*Element::fromJSON(dbl_tail_comment, true))); + + // With preprocessing disabled, it should fail all around + EXPECT_THROW(Element::fromJSON(head_comment), JSONError); + EXPECT_THROW(Element::fromJSON(mid_comment), JSONError); + EXPECT_THROW(Element::fromJSON(tail_comment), JSONError); + EXPECT_THROW(Element::fromJSON(dbl_head_comment), JSONError); + EXPECT_THROW(Element::fromJSON(dbl_mid_comment), JSONError); + EXPECT_THROW(Element::fromJSON(dbl_tail_comment), JSONError); +} + TEST(Element, getPosition) { std::istringstream ss("{\n" " \"a\": 2,\n" @@ -957,7 +1011,7 @@ TEST(Element, getPosition) { // Create a JSON string holding different type of values. Some of the // values in the config string are not aligned, so as we can check that // the position is set correctly for the elements. - ElementPtr top = Element::fromJSON(ss, "kea.conf"); + ElementPtr top = Element::fromJSON(ss, string("kea.conf")); ASSERT_TRUE(top); // Element "a" @@ -1033,4 +1087,58 @@ TEST(Element, getPosition) { } +// Tests whether position is returned properly for a commented input JSON text. +TEST(Element, getPositionCommented) { + std::istringstream ss("{\n" + " \"a\": 2,\n" + "# comment\n" + " \"cy\": \"a string\",\n" + " \"dyz\": {\n" + "# another comment\n" + " \"e\": 3,\n" + " \"f\": null\n" + "\n" + " } }\n"); + + // Create a JSON string holding different type of values. Some of the + // values in the config string are not aligned, so as we can check that + // the position is set correctly for the elements. + ElementPtr top = Element::fromJSON(ss, string("kea.conf"), true); + ASSERT_TRUE(top); + + // Element "a" + ConstElementPtr level1_el = top->get("a"); + ASSERT_TRUE(level1_el); + EXPECT_EQ(2, level1_el->getPosition().line_); + EXPECT_EQ(11, level1_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level1_el->getPosition().file_); + + // Element "cy" + level1_el = top->get("cy"); + ASSERT_TRUE(level1_el); + EXPECT_EQ(4, level1_el->getPosition().line_); + EXPECT_EQ(11, level1_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level1_el->getPosition().file_); + + // Element "dyz" + level1_el = top->get("dyz"); + ASSERT_TRUE(level1_el); + EXPECT_EQ(5, level1_el->getPosition().line_); + EXPECT_EQ(13, level1_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level1_el->getPosition().file_); + + // Element "e" is a sub element of "dyz". + ConstElementPtr level2_el = level1_el->get("e"); + ASSERT_TRUE(level2_el); + EXPECT_EQ(7, level2_el->getPosition().line_); + EXPECT_EQ(12, level2_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level2_el->getPosition().file_); + + // Element "f" is also a sub element of "dyz" + level2_el = level1_el->get("f"); + ASSERT_TRUE(level2_el); + EXPECT_EQ(8, level2_el->getPosition().line_); + EXPECT_EQ(14, level2_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level2_el->getPosition().file_); +} } diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index 2f4be2ed639f6b369ea5a946df4fbb78df164085..6044edcf3e7dcae173451c1adb44964293092b89 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -3,6 +3,7 @@ SUBDIRS = . testutils tests dhcp_data_dir = @localstatedir@/@PACKAGE@ AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -DDHCP_DATA_DIR="\"$(dhcp_data_dir)\"" +AM_CPPFLAGS += -DTOP_BUILDDIR="\"$(top_builddir)\"" AM_CPPFLAGS += $(BOOST_INCLUDES) if HAVE_MYSQL AM_CPPFLAGS += $(MYSQL_CPPFLAGS) @@ -48,6 +49,7 @@ libkea_dhcpsrv_la_SOURCES += csv_lease_file4.cc csv_lease_file4.h libkea_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h libkea_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h libkea_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h +libkea_dhcpsrv_la_SOURCES += daemon.cc daemon.h libkea_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h libkea_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h @@ -58,6 +60,7 @@ libkea_dhcpsrv_la_SOURCES += lease.cc lease.h libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h libkea_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h libkea_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h + if HAVE_MYSQL libkea_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h endif diff --git a/src/lib/dhcpsrv/daemon.cc b/src/lib/dhcpsrv/daemon.cc new file mode 100644 index 0000000000000000000000000000000000000000..c37f923254fd15263254763c2e8c44587e5672d9 --- /dev/null +++ b/src/lib/dhcpsrv/daemon.cc @@ -0,0 +1,45 @@ +// 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 + +/// @brief provides default implementation for basic daemon operations +/// +/// This file provides stub implementations that are expected to be redefined +/// in derived classes (e.g. ControlledDhcpv6Srv) +namespace isc { +namespace dhcp { + +Daemon::Daemon() { +} + +void Daemon::init(const std::string&) { +} + +void Daemon::cleanup() { + +} + +void Daemon::shutdown() { + +} + +Daemon::~Daemon() { +} + +}; +}; diff --git a/src/lib/dhcpsrv/daemon.h b/src/lib/dhcpsrv/daemon.h new file mode 100644 index 0000000000000000000000000000000000000000..7c144890447242fc3b782e715446474fd77ee354 --- /dev/null +++ b/src/lib/dhcpsrv/daemon.h @@ -0,0 +1,93 @@ +// 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 + + +namespace isc { +namespace dhcp { + +/// @brief Base class for all services +/// +/// This is the base class that all daemons (DHCPv4, DHCPv6, D2 and possibly +/// others) are derived from. It provides a standard interface for starting up, +/// reconfiguring, shutting down and several other operations. It also covers +/// some common operations. +/// +/// This class is not expected to be instantiated directly, but rather daemon +/// implementations should derive from it. +/// +/// Methods are not pure virtual, as we need to instantiate basic daemons (e.g. +/// Dhcpv6Srv) in tests, without going through the hassles of implemeting stub +/// methods. +/// +/// @note Only one instance of this class is instantiated as it encompasses +/// the whole operation of the server. Nothing, however, enforces the +/// singleton status of the object. +class Daemon : public boost::noncopyable { + +public: + /// @brief Default constructor + /// + /// Currently it does nothing. + Daemon(); + + /// @brief Initializes the server. + /// + /// Depending on the configuration backend, it establishes msgq session, + /// or reads the + /// Creates session that will be used to receive commands and updated + /// configuration from cfgmgr (or indirectly from user via bindctl). + /// + /// Note: This function may throw to report enountered problems. It may + /// also return false if the initialization was skipped. That may seem + /// redundant, but the idea here is that in some cases the configuration + /// was read, understood and the decision was made to not start. One + /// case where such capability could be needed is when we have a single + /// config file for Kea4 and D2, but the DNS Update is disabled. It is + /// likely that the D2 will be started, it will analyze its config file, + /// decide that it is not needed and will shut down. + /// + /// @note this method may throw + virtual void init(const std::string& config_file); + + /// @brief Performs final deconfiguration. + /// + /// Performs configuration backend specific final clean-up. This is called + /// shortly before the daemon terminates. Depending on backend, it may + /// terminat existing msgq session, close LDAP connection or similar. + /// + /// The daemon is not expected to receive any further commands or + /// configuration updates as it is in final stages of shutdown. + virtual void cleanup(); + + /// @brief Initiates shutdown procedure for the whole DHCPv6 server. + virtual void shutdown(); + + /// @brief Desctructor + /// + /// Having virtual destructor ensures that all derived classes will have + /// virtual destructor as well. + virtual ~Daemon(); + + /// Initializez logger + /// + /// This method initializes logger. I + static void loggerInit(const char* log_name, bool verbose, bool stand_alone); +}; + +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index 6ef9a7dfac6ec66f81e6ec80d84956f75a0e4257..0df7d579efe2bd5faf11e8a375331137ea538ca8 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -60,6 +60,7 @@ libdhcpsrv_unittests_SOURCES += csv_lease_file4_unittest.cc libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc +libdhcpsrv_unittests_SOURCES += daemon_unittest.cc libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc libdhcpsrv_unittests_SOURCES += lease_file_io.cc lease_file_io.h libdhcpsrv_unittests_SOURCES += lease_unittest.cc diff --git a/src/lib/dhcpsrv/tests/daemon_unittest.cc b/src/lib/dhcpsrv/tests/daemon_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..315148b5d4cef639cecac378da70ad922fa8b94f --- /dev/null +++ b/src/lib/dhcpsrv/tests/daemon_unittest.cc @@ -0,0 +1,32 @@ +// 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 + +using namespace isc; +using namespace isc::dhcp; + +namespace { + +// Very simple test. Checks whether Daemon can be instantiated. +TEST(DaemonTest, noop) { + EXPECT_NO_THROW(Daemon x); +} + +// More tests will appear here as we develop Daemon class. + +};