Browse Source

[3399] Bundy controller implemented.

Tomek Mrugalski 11 years ago
parent
commit
d8ebb8039b

+ 9 - 1
src/bin/dhcp4/Makefile.am

@@ -51,10 +51,18 @@ pkglibexec_PROGRAMS = b10-dhcp4
 
 b10_dhcp4_SOURCES  = main.cc
 b10_dhcp4_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
-b10_dhcp4_SOURCES += config_parser.cc config_parser.h
+b10_dhcp4_SOURCES += json_config_parser.cc json_config_parser.h
 b10_dhcp4_SOURCES += dhcp4_log.cc dhcp4_log.h
 b10_dhcp4_SOURCES += dhcp4_srv.cc dhcp4_srv.h
 
+if CONFIG_BACKEND_BUNDY
+b10_dhcp4_SOURCES += bundy_controller.cc
+endif
+
+if CONFIG_BACKEND_JSON
+b10_dhcp4_SOURCES += kea_controller.cc
+endif
+
 nodist_b10_dhcp4_SOURCES = dhcp4_messages.h dhcp4_messages.cc
 EXTRA_DIST += dhcp4_messages.mes
 

+ 41 - 113
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -19,7 +19,7 @@
 #include <cc/session.h>
 #include <config/ccsession.h>
 #include <dhcp/iface_mgr.h>
-#include <dhcp4/config_parser.h>
+#include <dhcp4/json_config_parser.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/spec_config.h>
@@ -48,10 +48,33 @@ using namespace std;
 namespace isc {
 namespace dhcp {
 
-ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
-
+/// @brief Helper session object that represents raw connection to msgq.
+isc::cc::Session* cc_session_ = NULL;
+
+/// @brief Session that receives configuration and commands
+isc::config::ModuleCCSession* config_session_ = NULL;
+
+/// @brief A dummy configuration handler that always returns success.
+///
+/// This configuration handler does not perform configuration
+/// parsing and always returns success. A dummy handler should
+/// be installed using \ref isc::config::ModuleCCSession ctor
+/// to get the initial configuration. This initial configuration
+/// comprises values for only those elements that were modified
+/// the previous session. The \ref dhcp4ConfigHandler can't be
+/// used to parse the initial configuration because it needs the
+/// full configuration to satisfy dependencies between the
+/// various configuration values. Installing the dummy handler
+/// that guarantees to return success causes initial configuration
+/// to be stored for the session being created and that it can
+/// be later accessed with
+/// \ref isc::config::ConfigData::getFullConfig().
+///
+/// @param new_config new configuration.
+///
+/// @return success configuration status.
 ConstElementPtr
-ControlledDhcpv4Srv::dhcp4StubConfigHandler(ConstElementPtr) {
+dhcp4StubConfigHandler(ConstElementPtr) {
     // This configuration handler is intended to be used only
     // when the initial configuration comes in. To receive this
     // configuration a pointer to this handler must be passed
@@ -66,8 +89,8 @@ ControlledDhcpv4Srv::dhcp4StubConfigHandler(ConstElementPtr) {
 }
 
 ConstElementPtr
-ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
-    if (!server_ || !server_->config_session_) {
+bundyConfigHandler(ConstElementPtr new_config) {
+    if (!ControlledDhcpv4Srv::getInstance() || !config_session_) {
         // That should never happen as we install config_handler
         // after we instantiate the server.
         ConstElementPtr answer =
@@ -91,7 +114,7 @@ ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
     // Let's create a new object that will hold the merged configuration.
     boost::shared_ptr<MapElement> merged_config(new MapElement());
     // Let's get the existing configuration.
-    ConstElementPtr full_config = server_->config_session_->getFullConfig();
+    ConstElementPtr full_config = config_session_->getFullConfig();
     // The full_config and merged_config should be always non-NULL
     // but to provide some level of exception safety we check that they
     // really are (in case we go out of memory).
@@ -105,88 +128,13 @@ ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
     }
 
     // Configure the server.
-    ConstElementPtr answer = configureDhcp4Server(*server_, merged_config);
-
-    // Check that configuration was successful. If not, do not reopen sockets.
-    int rcode = 0;
-    parseAnswer(rcode, answer);
-    if (rcode != 0) {
-        return (answer);
-    }
-
-    // Server will start DDNS communications if its enabled.
-    try {
-        server_->startD2();
-    } catch (const std::exception& ex) {
-        std::ostringstream err;
-        err << "error starting DHCP_DDNS client "
-                " after server reconfiguration: " << ex.what();
-        return (isc::config::createAnswer(1, err.str()));
-    }
-
-    // Configuration may change active interfaces. Therefore, we have to reopen
-    // sockets according to new configuration. This operation is not exception
-    // safe and we really don't want to emit exceptions to the callback caller.
-    // Instead, catch an exception and create appropriate answer.
-    try {
-        server_->openActiveSockets(server_->getPort(), server_->useBroadcast());
-    } catch (std::exception& ex) {
-        std::ostringstream err;
-        err << "failed to open sockets after server reconfiguration: " << ex.what();
-        answer = isc::config::createAnswer(1, err.str());
-    }
-    return (answer);
+    return (ControlledDhcpv4Srv::processConfig(merged_config));
 }
 
-ConstElementPtr
-ControlledDhcpv4Srv::dhcp4CommandHandler(const string& command, ConstElementPtr args) {
-    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
-              .arg(command).arg(args->str());
-
-    if (command == "shutdown") {
-        if (ControlledDhcpv4Srv::server_) {
-            ControlledDhcpv4Srv::server_->shutdown();
-        } else {
-            LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING);
-            ConstElementPtr answer = isc::config::createAnswer(1,
-                                     "Shutdown failure.");
-            return (answer);
-        }
-        ConstElementPtr answer = isc::config::createAnswer(0,
-                                 "Shutting down.");
-        return (answer);
-
-    } else if (command == "libreload") {
-        // TODO delete any stored CalloutHandles referring to the old libraries
-        // Get list of currently loaded libraries and reload them.
-        vector<string> loaded = HooksManager::getLibraryNames();
-        bool status = HooksManager::loadLibraries(loaded);
-        if (!status) {
-            LOG_ERROR(dhcp4_logger, DHCP4_HOOKS_LIBS_RELOAD_FAIL);
-            ConstElementPtr answer = isc::config::createAnswer(1,
-                                     "Failed to reload hooks libraries.");
-            return (answer);
-        }
-        ConstElementPtr answer = isc::config::createAnswer(0,
-                                 "Hooks libraries successfully reloaded.");
-        return (answer);
-    }
-
-    ConstElementPtr answer = isc::config::createAnswer(1,
-                             "Unrecognized command.");
 
-    return (answer);
-}
 
-void ControlledDhcpv4Srv::sessionReader(void) {
-    // Process one asio event. If there are more events, iface_mgr will call
-    // this callback more than once.
-    if (server_) {
-        server_->io_service_.run_one();
-    }
-}
 
-void ControlledDhcpv4Srv::establishSession() {
+void ControlledDhcpv4Srv::init(const std::string& /*config_file*/) {
 
     string specfile;
     if (getenv("B10_FROM_BUILD")) {
@@ -210,14 +158,14 @@ void ControlledDhcpv4Srv::establishSession() {
     // been lost.
     config_session_ = new ModuleCCSession(specfile, *cc_session_,
                                           dhcp4StubConfigHandler,
-                                          dhcp4CommandHandler, false);
+                                          processCommand, false);
     config_session_->start();
 
     // We initially create ModuleCCSession() without configHandler, as
     // the session module is too eager to send partial configuration.
     // We want to get the full configuration, so we explicitly call
     // getFullConfig() and then pass it to our configHandler.
-    config_session_->setConfigHandler(dhcp4ConfigHandler);
+    config_session_->setConfigHandler(bundyConfigHandler);
 
     try {
         configureDhcp4Server(*this, config_session_->getFullConfig());
@@ -243,7 +191,8 @@ void ControlledDhcpv4Srv::establishSession() {
     IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
 }
 
-void ControlledDhcpv4Srv::disconnectSession() {
+
+void ControlledDhcpv4Srv::cleanup() {
     if (config_session_) {
         delete config_session_;
         config_session_ = NULL;
@@ -259,32 +208,11 @@ void ControlledDhcpv4Srv::disconnectSession() {
     }
 }
 
-ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)
-    :Dhcpv4Srv(port), cc_session_(NULL), config_session_(NULL) {
-    server_ = this; // remember this instance for use in callback
-}
-
-void ControlledDhcpv4Srv::shutdown() {
-    io_service_.stop(); // Stop ASIO transmissions
-    Dhcpv4Srv::shutdown(); // Initiate DHCPv4 shutdown procedure.
-}
-
-ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
-    disconnectSession();
-
-    server_ = NULL; // forget this instance. There should be no callback anymore
-                    // at this stage anyway.
-}
-
-isc::data::ConstElementPtr
-ControlledDhcpv4Srv::execDhcpv4ServerCommand(const std::string& command_id,
-                                             isc::data::ConstElementPtr args) {
-    try {
-        return (dhcp4CommandHandler(command_id, args));
-    } catch (const Exception& ex) {
-        ConstElementPtr answer = isc::config::createAnswer(1, ex.what());
-        return (answer);
-    }
+void
+Daemon::loggerInit(const char* log_name, bool verbose, bool stand_alone) {
+    isc::log::initLogger(log_name,
+                         (verbose ? isc::log::DEBUG : isc::log::INFO),
+                         isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
 }
 
 };

+ 84 - 59
src/bin/dhcp4/ctrl_dhcp4_srv.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -46,44 +46,51 @@ public:
     /// @brief Destructor.
     ~ControlledDhcpv4Srv();
 
-    /// @brief Establishes msgq session.
+    /// @brief Initializes the server.
     ///
-    /// Creates session that will be used to receive commands and updated
-    /// configuration from cfgmgr (or indirectly from user via bindctl).
-    void establishSession();
+    /// Depending on the configuration backend, it establishes msgq session,
+    /// reads the JSON file from disk or may perform any other setup
+    /// operation. For specific details, see actual implementation in
+    /// *_backend.cc
+    ///
+    /// @return true if initialization was successful, false if it failed
+    void init(const std::string& config_file);
 
-    /// @brief Terminates existing msgq session.
+    /// @brief Performs cleanup, immediately before termination
     ///
-    /// This method terminates existing session with msgq. After calling
+    /// This method performs final clean up, just before the Dhcpv6Srv object
+    /// is destroyed. The actual behavior is backend dependent. For Bundy
+    /// backend, it terminates existing session with msgq. After calling
     /// it, no further messages over msgq (commands or configuration updates)
-    /// may be received.
+    /// may be received. For JSON backend, it is no-op.
     ///
-    /// It is ok to call this method when session is disconnected already.
-    void disconnectSession();
+    /// For specific details, see actual implementation in *_backend.cc
+    void cleanup();
 
     /// @brief Initiates shutdown procedure for the whole DHCPv4 server.
     void shutdown();
 
-    /// @brief Session callback, processes received commands.
+    /// @brief command processor
+    ///
+    /// This method is uniform for all config backends. It processes received
+    /// command (as a string + JSON arguments). Internally, it's just a
+    /// wrapper that calls process*Command() methods and catches exceptions
+    /// in them.
+    ///
+    /// @note It never throws.
     ///
     /// @param command Text represenation of the command (e.g. "shutdown")
     /// @param args Optional parameters
-    /// @param command text represenation of the command (e.g. "shutdown")
-    /// @param args optional parameters
     ///
     /// @return status of the command
     static isc::data::ConstElementPtr
-    execDhcpv4ServerCommand(const std::string& command,
-                            isc::data::ConstElementPtr args);
+    processCommand(const std::string& command, isc::data::ConstElementPtr args);
 
-protected:
-    /// @brief Static pointer to the sole instance of the DHCP server.
+    /// @brief configuration processor
     ///
-    /// This is required for config and command handlers to gain access to
-    /// the server
-    static ControlledDhcpv4Srv* server_;
-
-    /// @brief A callback for handling incoming configuration updates.
+    /// This is a callback for handling incoming configuration updates.
+    /// This method should be called by all configuration backends when the
+    /// server is starting up or when configuration has changed.
     ///
     /// As pointer to this method is used a callback in ASIO used in
     /// ModuleCCSession, it has to be static.
@@ -92,54 +99,72 @@ protected:
     ///
     /// @return status of the config update
     static isc::data::ConstElementPtr
-    dhcp4ConfigHandler(isc::data::ConstElementPtr new_config);
-
-    /// @brief A dummy configuration handler that always returns success.
-    ///
-    /// This configuration handler does not perform configuration
-    /// parsing and always returns success. A dummy handler should
-    /// be installed using \ref isc::config::ModuleCCSession ctor
-    /// to get the initial configuration. This initial configuration
-    /// comprises values for only those elements that were modified
-    /// the previous session. The \ref dhcp4ConfigHandler can't be
-    /// used to parse the initial configuration because it needs the
-    /// full configuration to satisfy dependencies between the
-    /// various configuration values. Installing the dummy handler
-    /// that guarantees to return success causes initial configuration
-    /// to be stored for the session being created and that it can
-    /// be later accessed with
-    /// \ref isc::config::ConfigData::getFullConfig().
-    ///
-    /// @param new_config new configuration.
-    ///
-    /// @return success configuration status.
-    static isc::data::ConstElementPtr
-    dhcp4StubConfigHandler(isc::data::ConstElementPtr new_config);
+    processConfig(isc::data::ConstElementPtr new_config);
 
-    /// @brief A callback for handling incoming commands.
+    /// @brief returns pointer to the sole instance of Dhcpv4Srv
     ///
-    /// @param command textual representation of the command
-    /// @param args parameters of the command
+    /// @note may return NULL, if called before server is spawned
+    static ControlledDhcpv4Srv* getInstance() {
+        return (server_);
+    }
+
+
+protected:
+    /// @brief Static pointer to the sole instance of the DHCP server.
     ///
-    /// @return status of the processed command
-    static isc::data::ConstElementPtr
-    dhcp4CommandHandler(const std::string& command, isc::data::ConstElementPtr args);
+    /// This is required for config and command handlers to gain access to
+    /// the server
+    static ControlledDhcpv4Srv* server_;
 
-    /// @brief Callback that will be called from iface_mgr when command/config arrives.
+    /// @brief Callback that will be called from iface_mgr when data
+    /// is received over control socket.
     ///
-    /// This static callback method is called from IfaceMgr::receive4() method,
-    /// when there is a new command or configuration sent over msgq.
+    /// This static callback method is called from IfaceMgr::receive6() method,
+    /// when there is a new command or configuration sent over control socket
+    /// (that was sent from msgq if backend is Bundy, or some yet unspecified
+    /// sender if the backend is JSON file).
     static void sessionReader(void);
 
-
     /// @brief IOService object, used for all ASIO operations.
     isc::asiolink::IOService io_service_;
 
-    /// @brief Helper session object that represents raw connection to msgq.
-    isc::cc::Session* cc_session_;
+    /// @brief handler for processing 'shutdown' command
+    ///
+    /// This handler processes shutdown command, which initializes shutdown
+    /// procedure.
+    /// @param command (parameter ignored)
+    /// @param args (parameter ignored)
+    ///
+    /// @return status of the command
+    isc::data::ConstElementPtr
+    commandShutdownHandler(const std::string& command,
+                           isc::data::ConstElementPtr args);
+
+    /// @brief handler for processing 'libreload' command
+    ///
+    /// This handler processes libreload command, which unloads all hook
+    /// libraries and reloads them.
+    ///
+    /// @param command (parameter ignored)
+    /// @param args (parameter ignored)
+    ///
+    /// @return status of the command
+    isc::data::ConstElementPtr
+    commandLibReloadHandler(const std::string& command,
+                            isc::data::ConstElementPtr args);
 
-    /// @brief Session that receives configuration and commands
-    isc::config::ModuleCCSession* config_session_;
+    /// @brief handler for processing 'config-reload' command
+    ///
+    /// This handler processes config-reload command, which processes
+    /// configuration specified in args parameter.
+    ///
+    /// @param command (parameter ignored)
+    /// @param args configuration to be processed
+    ///
+    /// @return status of the command
+    isc::data::ConstElementPtr
+    commandConfigReloadHandler(const std::string& command,
+                               isc::data::ConstElementPtr args);
 };
 
 }; // namespace isc::dhcp

+ 3 - 2
src/bin/dhcp4/dhcp4_srv.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -26,6 +26,7 @@
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <hooks/callout_handle.h>
+#include <dhcpsrv/daemon.h>
 
 #include <boost/noncopyable.hpp>
 
@@ -57,7 +58,7 @@ public:
 ///
 /// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
 /// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
-class Dhcpv4Srv : public boost::noncopyable {
+class Dhcpv4Srv : public Daemon {
 
 public:
 

+ 1 - 1
src/bin/dhcp4/config_parser.cc

@@ -17,7 +17,7 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option_definition.h>
 #include <dhcpsrv/cfgmgr.h>
-#include <dhcp4/config_parser.h>
+#include <dhcp4/json_config_parser.h>
 #include <dhcpsrv/dbaccess_parser.h>
 #include <dhcpsrv/dhcp_parsers.h>
 #include <dhcpsrv/option_space_container.h>

src/bin/dhcp4/config_parser.h → src/bin/dhcp4/json_config_parser.h


+ 31 - 17
src/bin/dhcp4/main.cc

@@ -39,6 +39,8 @@ namespace {
 
 const char* const DHCP4_NAME = "b10-dhcp4";
 
+const char* const DHCP4_LOGGER_NAME = "kea";
+
 void
 usage() {
     cerr << "Usage: " << DHCP4_NAME << " [-v] [-s] [-p number]" << endl;
@@ -46,6 +48,7 @@ usage() {
     cerr << "  -s: stand-alone mode (don't connect to BIND10)" << endl;
     cerr << "  -p number: specify non-standard port number 1-65535 "
          << "(useful for testing only)" << endl;
+    cerr << "  -c file: specify configuration file" << endl;
     exit(EXIT_FAILURE);
 }
 } // end of anonymous namespace
@@ -58,7 +61,10 @@ main(int argc, char* argv[]) {
     bool stand_alone = false;  // Should be connect to BIND10 msgq?
     bool verbose_mode = false; // Should server be verbose?
 
-    while ((ch = getopt(argc, argv, "vsp:")) != -1) {
+    // The standard config file
+    std::string config_file("");
+
+    while ((ch = getopt(argc, argv, "vsp:c:")) != -1) {
         switch (ch) {
         case 'v':
             verbose_mode = true;
@@ -83,6 +89,10 @@ main(int argc, char* argv[]) {
             }
             break;
 
+        case 'c': // config file
+            config_file = optarg;
+            break;
+
         default:
             usage();
         }
@@ -93,31 +103,35 @@ main(int argc, char* argv[]) {
         usage();
     }
 
-    // Initialize logging.  If verbose, we'll use maximum verbosity.
-    // If standalone is enabled, do not buffer initial log messages
-    isc::log::initLogger(DHCP4_NAME,
-                         (verbose_mode ? isc::log::DEBUG : isc::log::INFO),
-                         isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
-    LOG_INFO(dhcp4_logger, DHCP4_STARTING);
-    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
-              .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
-              .arg(stand_alone ? "yes" : "no" );
-
-
     int ret = EXIT_SUCCESS;
+
     try {
+        // Initialize logging.  If verbose, we'll use maximum verbosity.
+        // If standalone is enabled, do not buffer initial log messages
+        Daemon::loggerInit(DHCP4_LOGGER_NAME, verbose_mode, stand_alone);
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
+            .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
+            .arg(stand_alone ? "yes" : "no" );
+        
+        LOG_INFO(dhcp4_logger, DHCP4_STARTING);
+
         ControlledDhcpv4Srv server(port_number);
+
         if (!stand_alone) {
+
             try {
-                server.establishSession();
+                server.init(config_file);
             } catch (const std::exception& ex) {
                 LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what());
-                // Let's continue. It is useful to have the ability to run
-                // DHCP server in stand-alone mode, e.g. for testing
-                // We do need to make sure logging is no longer buffered
-                // since then it would not print until dhcp6 is stopped
+
+                // We should not continue if were told to configure (either read
+                // config file or establish Bundy control session).
+
                 isc::log::LoggerManager log_manager;
                 log_manager.process();
+
+                cerr << "Failed to initialize server: " << ex.what() << endl;
+                return (EXIT_FAILURE);
             }
         } else {
             LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_STANDALONE);

+ 14 - 1
src/bin/dhcp4/tests/Makefile.am

@@ -77,7 +77,7 @@ TESTS += dhcp4_unittests
 
 dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc
 dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc
-dhcp4_unittests_SOURCES += ../config_parser.cc ../config_parser.h
+dhcp4_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h
 dhcp4_unittests_SOURCES += d2_unittest.h d2_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4_test_utils.h
 dhcp4_unittests_SOURCES += dhcp4_unittests.cc
@@ -89,6 +89,19 @@ dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += config_parser_unittest.cc
 dhcp4_unittests_SOURCES += fqdn_unittest.cc
 dhcp4_unittests_SOURCES += marker_file.cc
+
+if CONFIG_BACKEND_BUNDY
+# For Bundy backend, we only need to run the usual tests. There are no
+# Bundy-specific tests yet.
+dhcp4_unittests_SOURCES += ../bundy_controller.cc
+dhcp4_unittests_SOURCES += bundy_controller_unittest.cc
+endif
+
+if CONFIG_BACKEND_JSON
+dhcp4_unittests_SOURCES += ../kea_controller.cc
+dhcp4_unittests_SOURCES += kea_controller_unittest.cc
+endif
+
 nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
 nodist_dhcp4_unittests_SOURCES += marker_file.h test_libraries.h
 

+ 26 - 0
src/bin/dhcp4/tests/bundy_controller_unittest.cc

@@ -0,0 +1,26 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+namespace {
+
+// Bundy framework specific tests should be added here.
+TEST(BundyBackendTest, dummy) {
+
+}
+
+} // End of anonymous namespace

+ 1 - 1
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -19,7 +19,7 @@
 
 #include <config/ccsession.h>
 #include <dhcp4/dhcp4_srv.h>
-#include <dhcp4/config_parser.h>
+#include <dhcp4/json_config_parser.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>

+ 12 - 4
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc

@@ -85,12 +85,12 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
     int rcode = -1;
 
     // Case 1: send bogus command
-    ConstElementPtr result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("blah", params);
+    ConstElementPtr result = ControlledDhcpv4Srv::processCommand("blah", params);
     ConstElementPtr comment = parseAnswer(rcode, result);
     EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
 
     // Case 2: send shutdown command without any parameters
-    result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
+    result = ControlledDhcpv4Srv::processCommand("shutdown", params);
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
 
@@ -99,7 +99,7 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
     params->set("pid", x);
 
     // Case 3: send shutdown command with 1 parameter: pid
-    result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
+    result = ControlledDhcpv4Srv::processCommand("shutdown", params);
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
 }
@@ -107,6 +107,14 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
 // Check that the "libreload" command will reload libraries
 
 TEST_F(CtrlDhcpv4SrvTest, libreload) {
+
+    // Sending commands for processing now requires a server that can process
+    // them.
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv4Srv(0))
+    );
+
     // Ensure no marker files to start with.
     ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
     ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
@@ -137,7 +145,7 @@ TEST_F(CtrlDhcpv4SrvTest, libreload) {
     int rcode = -1;
 
     ConstElementPtr result =
-        ControlledDhcpv4Srv::execDhcpv4ServerCommand("libreload", params);
+        ControlledDhcpv4Srv::processCommand("libreload", params);
     ConstElementPtr comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // Expect success
 

+ 1 - 1
src/bin/dhcp4/tests/d2_unittest.cc

@@ -13,7 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <dhcp/iface_mgr.h>
-#include <dhcp4/config_parser.h>
+#include <dhcp4/json_config_parser.h>
 #include <dhcp4/tests/d2_unittest.h>
 #include <dhcpsrv/cfgmgr.h>
 

+ 1 - 1
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -32,7 +32,7 @@
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
-#include <dhcp4/config_parser.h>
+#include <dhcp4/json_config_parser.h>
 #include <hooks/server_hooks.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr.h>

+ 1 - 1
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -17,7 +17,7 @@
 #include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <config/ccsession.h>
-#include <dhcp4/config_parser.h>
+#include <dhcp4/json_config_parser.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option_int_array.h>

+ 1 - 1
src/bin/dhcp4/tests/direct_client_unittest.cc

@@ -19,7 +19,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/subnet.h>
-#include <dhcp4/config_parser.h>
+#include <dhcp4/json_config_parser.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <gtest/gtest.h>
 #include <string>

+ 236 - 0
src/bin/dhcp4/tests/kea_controller_unittest.cc

@@ -0,0 +1,236 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <config/ccsession.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp6/ctrl_dhcp4_srv.h>
+#include <dhcpsrv/cfgmgr.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <unistd.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+
+namespace {
+
+class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
+    // "Naked" DHCPv4 server, exposes internal fields
+public:
+    NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(0) { }
+};
+
+
+class JSONFileBackendTest : public ::testing::Test {
+public:
+    JSONFileBackendTest() {
+    }
+
+    ~JSONFileBackendTest() {
+        static_cast<void>(unlink(TEST_FILE));
+    };
+
+    void writeFile(const std::string& file_name, const std::string& content) {
+        static_cast<void>(unlink(file_name.c_str()));
+
+        ofstream out(file_name.c_str(), ios::trunc);
+        EXPECT_TRUE(out.is_open());
+        out << content;
+        out.close();
+    }
+
+    static const char* TEST_FILE;
+};
+
+const char* JSONFileBackendTest::TEST_FILE = "test-config.json";
+
+// This test checks if configuration can be read from a JSON file.
+TEST_F(JSONFileBackendTest, jsonFile) {
+
+    // Prepare configuration file.
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
+        "    \"subnet\": \"192.0.3.0/24\", "
+        "    \"id\": 0 "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
+        "    \"subnet\": \"192.0.4.0/24\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    writeFile(TEST_FILE, config);
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv4Srv(0))
+    );
+
+    // And configure it using the config file.
+    EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+    // Now check if the configuration has been applied correctly.
+    const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(3, subnets->size()); // We expect 3 subnets.
+
+
+    // Check subnet 1.
+    EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText());
+    EXPECT_EQ(24, subnets->at(0)->get().second);
+
+    // Check pools in the first subnet.
+    const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_V4);
+    ASSERT_EQ(1, pools1.size());
+    EXPECT_EQ("192.0.2.1", pools1.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("192.0.2.100", pools1.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
+
+    // Check subnet 2.
+    EXPECT_EQ("192.0.3.0", subnets->at(1)->get().first.toText());
+    EXPECT_EQ(24, subnets->at(1)->get().second);
+
+    // Check pools in the second subnet.
+    const PoolCollection& pools2 = subnets->at(1)->getPools(Lease::TYPE_V4);
+    ASSERT_EQ(1, pools2.size());
+    EXPECT_EQ("192.0.3.101", pools2.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("192.0.3.150", pools2.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_V4, pools2.at(0)->getType());
+
+    // And finally check subnet 3.
+    EXPECT_EQ("192.0.4.0", subnets->at(2)->get().first.toText());
+    EXPECT_EQ(24, subnets->at(2)->get().second);
+
+    // ... and it's only pool.
+    const PoolCollection& pools3 = subnets->at(2)->getPools(Lease::TYPE_V4);
+    EXPECT_EQ("192.0.4.101", pools3.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("192.0.4.150", pools3.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_NA, pools3.at(0)->getType());
+}
+
+// This test checks if configuration can be read from a JSON file.
+TEST_F(JSONFileBackendTest, comments) {
+
+    string config_hash_comments = "# This is a comment. It should be \n"
+        "#ignored. Real config starts in line below\n"
+        "{ \"Dhcp4\": { \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, \n"
+        "# comments in the middle should be ignored, too\n"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.0/24\" ],"
+        "    \"subnet\": \"192.0.2.0/22\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }"
+        "}";
+
+    /// @todo: Implement C++-style (// ...) comments
+    /// @todo: Implement C-style (/* ... */) comments
+
+    writeFile(TEST_FILE, config_hash_comments);
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv4Srv(0))
+    );
+
+    // And configure it using config without
+    EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+    // Now check if the configuration has been applied correctly.
+    const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(1, subnets->size());
+
+    // Check subnet 1.
+    EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText());
+    EXPECT_EQ(22, subnets->at(0)->get().second);
+
+    // Check pools in the first subnet.
+    const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
+    ASSERT_EQ(1, pools1.size());
+    EXPECT_EQ("192.0.2.0", pools1.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("192.0.2.255", pools1.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
+}
+
+// This test checks if configuration detects failure when trying:
+// - empty file
+// - empty filename
+// - no Dhcp4 element
+// - Config file that contains Dhcp4 but has a content error
+TEST_F(JSONFileBackendTest, configBroken) {
+
+    // Empty config is not allowed, because Dhcp4 element is missing
+    string config_empty = "";
+
+    // This config does not have mandatory Dhcp4 element
+    string config_v4 = "{ \"Dhcp6\": { \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"2001:db8::/80\" ],"
+        "    \"subnet\": \"2001:db8::/64\" "
+        " } ]}";
+
+    // This has Dhcp4 element, but it's utter nonsense
+    string config_nonsense = "{ \"Dhcp4\": { \"reviews\": \"are so much fun\" } }";
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv4Srv(0))
+    );
+
+    // Try to configure without filename. Should fail.
+    EXPECT_THROW(srv->init(""), BadValue);
+
+    // Try to configure it using empty file. Should fail.
+    writeFile(TEST_FILE, config_empty);
+    EXPECT_THROW(srv->init(TEST_FILE), BadValue);
+
+    // Now try to load a config that does not have Dhcp4 component.
+    writeFile(TEST_FILE, config_v4);
+    EXPECT_THROW(srv->init(TEST_FILE), BadValue);
+
+    // Now try to load a config with Dhcp4 full of nonsense.
+    writeFile(TEST_FILE, config_nonsense);
+    EXPECT_THROW(srv->init(TEST_FILE), BadValue);
+}
+
+} // End of anonymous namespace