]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[128-netconf-use-libprocess] Added use of libprocess in netconf
authorFrancis Dupont <fdupont@isc.org>
Thu, 27 Sep 2018 10:42:10 +0000 (12:42 +0200)
committerFrancis Dupont <fdupont@isc.org>
Thu, 27 Sep 2018 13:08:13 +0000 (09:08 -0400)
19 files changed:
configure.ac
src/bin/netconf/Makefile.am
src/bin/netconf/main.cc
src/bin/netconf/netconf_cfg_mgr.cc [new file with mode: 0644]
src/bin/netconf/netconf_cfg_mgr.h [new file with mode: 0644]
src/bin/netconf/netconf_controller.cc [new file with mode: 0644]
src/bin/netconf/netconf_controller.h [new file with mode: 0644]
src/bin/netconf/netconf_messages.mes
src/bin/netconf/netconf_process.cc [new file with mode: 0644]
src/bin/netconf/netconf_process.h [new file with mode: 0644]
src/bin/netconf/simple_parser.cc [new file with mode: 0644]
src/bin/netconf/simple_parser.h [new file with mode: 0644]
src/bin/netconf/tests/Makefile.am
src/bin/netconf/tests/basic_library.cc [new file with mode: 0644]
src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc [new file with mode: 0644]
src/bin/netconf/tests/netconf_controller_unittests.cc [new file with mode: 0644]
src/bin/netconf/tests/netconf_process_unittests.cc [new file with mode: 0644]
src/bin/netconf/tests/run_unittests.cc
src/bin/netconf/tests/test_libraries.h.in [new file with mode: 0644]

index 1b570b31344be3890977bc8da6218934ede82a3e..0470c642149ad5bf82590dd70090bfbf107af2f9 100644 (file)
@@ -1513,6 +1513,7 @@ AC_CONFIG_FILES([Makefile
                  src/bin/netconf/Makefile
                  src/bin/netconf/tests/Makefile
                  src/bin/netconf/tests/netconf_tests.sh
+                 src/bin/netconf/tests/test_libraries.h
                  src/bin/perfdhcp/Makefile
                  src/bin/perfdhcp/tests/Makefile
                  src/bin/perfdhcp/tests/testdata/Makefile
index a52ceafbc3e5d19ae38c191c406115b23df7c2e2..ce81c68e569e35fc8c57ecb10f71318efbb57f0e 100644 (file)
@@ -46,7 +46,11 @@ BUILT_SOURCES = netconf_messages.h netconf_messages.cc
 
 noinst_LTLIBRARIES = libnetconf.la
 
-libnetconf_la_SOURCES  = netconf_log.cc netconf_log.h
+libnetconf_la_SOURCES  = netconf_cfg_mgr.cc netconf_cfg_mgr.h
+libnetconf_la_SOURCES += netconf_controller.h netconf_controller.cc
+libnetconf_la_SOURCES += netconf_log.cc netconf_log.h
+libnetconf_la_SOURCES += netconf_process.cc netconf_process.h
+libnetconf_la_SOURCES += simple_parser.cc simple_parser.h
 
 nodist_libnetconf_la_SOURCES = netconf_messages.h netconf_messages.cc
 
index 14015e75b638afcfaee74fec2e6d9c6ec667484a..30155b0909d0722c04a476ca2b7b534d829af642 100644 (file)
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
-#include <kea_version.h>
-
-#include <netconf/netconf_log.h>
+#include <netconf/netconf_controller.h>
 #include <exceptions/exceptions.h>
-#include <process/daemon.h>
+#include <cstdlib>
 #include <iostream>
-#include <fstream>
-#include <unistd.h>
-#include <cstdio>
-#include <signal.h>
-
-#include <sysrepo-cpp/Session.h>
 
-using namespace std;
-using namespace isc;
 using namespace isc::netconf;
+using namespace isc::process;
 
-/// @brief Prints Kea Usage and exits
-///
-/// Note: This function never returns. It terminates the process.
-void
-usage() {
-    cerr << "Kea netconf daemon, version " << VERSION << endl
-         << endl
-         << "Usage: " << endl
-         << "  -c: config-file" << endl
-         << "  -d: debug mode (maximum verbosity)" << endl
-         << "  -v: print version number and exit" << endl
-         << "  -V: print extended version and exit" << endl;
-    exit(EXIT_FAILURE);
-}
-
-/// @name Temporary code until isc::process::Daemon is used.
-///
-/// @{
-const char* PID_FILENAME = "kea-netconf.test_config.pid";
-
-volatile bool SHUTDOWN_FLAG = false;
-
-void
-createPIDFile(int pid) {
-    // This is not a real implemented. We will soon use the one coming
-    // from isc::process::Daemon AFTER it's moved to libprocess.
-
-    ofstream file(PID_FILENAME, ios::trunc);
-    file << pid;
-}
-
-void
-deletePIDFile() {
-    remove(PID_FILENAME);
-}
-
-static void signal_handler(int ) {
-    SHUTDOWN_FLAG = true;
-}
-/// @}
-
-int
-main(int argc, char* argv[]) {
-    // The standard config file
-    std::string config_file("");
-    int ch;
-
-    while ((ch = getopt(argc, argv, "vVc:")) != -1) {
-        switch (ch) {
-        case 'v':
-            cout << string(PACKAGE_VERSION) << endl;
-            return (EXIT_SUCCESS);
-
-        case 'V':
-            cout << string(PACKAGE_VERSION) << endl;
-            cout << "git " << EXTENDED_VERSION << endl;
-            return (EXIT_SUCCESS);
-
-        case 'c': // config file
-            config_file = optarg;
-            break;
-        default:
-            usage();
-        }
-    }
-
-    // Check for extraneous parameters.
-    if (argc > optind) {
-        usage();
-    }
-
-    // Configuration file is required.
-    if (config_file.empty()) {
-        cerr << "Configuration file not specified." << endl;
-        usage();
-    }
-
+int main(int argc, char* argv[]) {
     int ret = EXIT_SUCCESS;
-    try {
-
-        // Temporary code. This will be replaced with isc::process::Daemon
-        // once it is migrated to libprocess. We DO NOT want to bring
-        // the whole libdhcpsrv into netconf.
-        createPIDFile(getpid());
-        signal(SIGHUP, signal_handler);
-        signal(SIGINT, signal_handler);
-        signal(SIGTERM, signal_handler);
 
-        // Initialize logging.  If verbose, we'll use maximum verbosity.
-        bool verbose_mode = true;
-        isc::process::Daemon::loggerInit(NETCONF_LOGGER_NAME, verbose_mode);
-        LOG_INFO(netconf_logger, NETCONF_STARTING).arg(VERSION).arg(getpid());
-
-        Connection conn("kea-netconf");
-        
-        // Tell the admin we are ready to process packets
-        LOG_INFO(netconf_logger, NETCONF_STATED).arg(VERSION);
-
-        // And run the main loop of the server.
-        while (!SHUTDOWN_FLAG) {
-            cout << "Dummy kea-netconf running. Press ctrl-c to terminate."
-                 << endl;
-            sleep(1);
+    // Launch the controller passing in command line arguments.
+    // Exit program with the controller's return code.
+    try  {
+        // Instantiate/fetch the application controller singleton.
+        DControllerBasePtr& controller = NetconfController::instance();
+
+        // 'false' value disables test mode.
+        controller->launch(argc, argv, false);
+    } catch (const VersionMessage& ex) {
+        std::string msg(ex.what());
+        if (!msg.empty()) {
+            std::cout << msg << std::endl;
         }
-
-        LOG_INFO(netconf_logger, NETCONF_SHUTDOWN);
-
-        deletePIDFile();
-
+    } catch (const InvalidUsage& ex) {
+        std::string msg(ex.what());
+        if (!msg.empty()) {
+            std::cerr << msg << std::endl;
+        }
+        ret = EXIT_FAILURE;
     } catch (const isc::Exception& ex) {
-        // First, we parint the error on stderr (that should always work)
-        cerr << "ERROR:" << ex.what() << endl;
+        std::cerr << "Service failed: " << ex.what() << std::endl;
         ret = EXIT_FAILURE;
     }
 
diff --git a/src/bin/netconf/netconf_cfg_mgr.cc b/src/bin/netconf/netconf_cfg_mgr.cc
new file mode 100644 (file)
index 0000000..c80b8a7
--- /dev/null
@@ -0,0 +1,123 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <netconf/netconf_cfg_mgr.h>
+#include <netconf/netconf_log.h>
+#include <netconf/simple_parser.h>
+#include <cc/simple_parser.h>
+#include <cc/command_interpreter.h>
+#include <exceptions/exceptions.h>
+
+using namespace isc::dhcp;
+using namespace isc::process;
+using namespace isc::data;
+
+namespace isc {
+namespace netconf {
+
+NetconfCfgContext::NetconfCfgContext() {
+}
+
+NetconfCfgContext::NetconfCfgContext(const NetconfCfgContext& orig)
+    : DCfgContextBase(), hooks_config_(orig.hooks_config_) {
+}
+
+NetconfCfgMgr::NetconfCfgMgr()
+    : DCfgMgrBase(DCfgContextBasePtr(new NetconfCfgContext())) {
+}
+
+NetconfCfgMgr::~NetconfCfgMgr() {
+}
+
+std::string
+NetconfCfgMgr::getConfigSummary(const uint32_t /*selection*/) {
+
+    NetconfCfgContextPtr ctx = getNetconfCfgContext();
+
+    std::ostringstream s;
+
+    // Finally, print the hook libraries names
+    const isc::hooks::HookLibsCollection libs = ctx->getHooksConfig().get();
+    s << ", " << libs.size() << " lib(s):";
+    for (auto lib = libs.begin(); lib != libs.end(); ++lib) {
+        s << lib->first << " ";
+    }
+
+    return (s.str());
+}
+
+DCfgContextBasePtr
+NetconfCfgMgr::createNewContext() {
+    return (DCfgContextBasePtr(new NetconfCfgContext()));
+}
+
+isc::data::ConstElementPtr
+NetconfCfgMgr::parse(isc::data::ConstElementPtr config_set,
+                     bool check_only) {
+    // Do a sanity check first.
+    if (!config_set) {
+        isc_throw(DhcpConfigError, "Mandatory config parameter not provided");
+    }
+
+    NetconfCfgContextPtr ctx = getNetconfCfgContext();
+
+    // Set the defaults
+    ElementPtr cfg = boost::const_pointer_cast<Element>(config_set);
+    NetconfSimpleParser::setAllDefaults(cfg);
+
+    // And parse the configuration.
+    ConstElementPtr answer;
+    std::string excuse;
+    try {
+        // Do the actual parsing
+        NetconfSimpleParser parser;
+        parser.parse(ctx, cfg, check_only);
+    } catch (const isc::Exception& ex) {
+        excuse = ex.what();
+        answer = isc::config::createAnswer(2, excuse);
+    } catch (...) {
+        excuse = "undefined configuration parsing error";
+        answer = isc::config::createAnswer(2, excuse);
+    }
+
+    // At this stage the answer was created only in case of exception.
+    if (answer) {
+        if (check_only) {
+            LOG_ERROR(netconf_logger, NETCONF_CONFIG_CHECK_FAIL).arg(excuse);
+        } else {
+            LOG_ERROR(netconf_logger, NETCONF_CONFIG_FAIL).arg(excuse);
+        }
+        return (answer);
+    }
+
+    if (check_only) {
+        answer = isc::config::createAnswer(0, "Configuration check successful");
+    } else {
+        answer = isc::config::createAnswer(0, "Configuration applied successfully.");
+    }
+
+    return (answer);
+}
+
+ElementPtr
+NetconfCfgContext::toElement() const {
+    ElementPtr netconf = Element::createMap();
+    // Set user-context
+    contextToElement(netconf);
+    // Set hooks-libraries
+    netconf->set("hooks-libraries", hooks_config_.toElement());
+    // Set Netconf
+    ElementPtr result = Element::createMap();
+    result->set("Netconf", netconf);
+
+    // Set Logging (not yet)
+
+    return (result);
+}
+
+} // namespace isc::netconf
+} // namespace isc
diff --git a/src/bin/netconf/netconf_cfg_mgr.h b/src/bin/netconf/netconf_cfg_mgr.h
new file mode 100644 (file)
index 0000000..796f118
--- /dev/null
@@ -0,0 +1,147 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef NETCONF_CFG_MGR_H
+#define NETCONF_CFG_MGR_H
+
+#include <cc/data.h>
+#include <hooks/hooks_config.h>
+#include <process/d_cfg_mgr.h>
+#include <boost/pointer_cast.hpp>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace netconf {
+
+class NetconfCfgContext;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<NetconfCfgContext> NetconfCfgContextPtr;
+
+/// @brief Netconf Configuration Context.
+///
+/// Implement the storage container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other Netconf specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// It is derived from the context base class, DCfgContextBase.
+class NetconfCfgContext : public process::DCfgContextBase {
+public:
+
+    /// @brief Default constructor
+    NetconfCfgContext();
+
+    /// @brief Creates a clone of this context object.
+    ///
+    /// @return A pointer to the new clone.
+    virtual process::DCfgContextBasePtr clone() {
+        return (process::DCfgContextBasePtr(new NetconfCfgContext(*this)));
+    }
+
+    /// @brief Returns non-const reference to configured hooks libraries.
+    ///
+    /// @return non-const reference to configured hooks libraries.
+    isc::hooks::HooksConfig& getHooksConfig() {
+        return (hooks_config_);
+    }
+
+    /// @brief Returns const reference to configured hooks libraries.
+    ///
+    /// @return const reference to configured hooks libraries.
+    const isc::hooks::HooksConfig& getHooksConfig() const {
+        return (hooks_config_);
+    }
+
+    /// @brief Unparse a configuration object
+    ///
+    /// Returns an element which must parse into the same object, i.e.
+    /// @code
+    /// for all valid config C parse(parse(C)->toElement()) == parse(C)
+    /// @endcode
+    ///
+    /// @return a pointer to a configuration which can be parsed into
+    /// the initial configuration object
+    virtual isc::data::ElementPtr toElement() const;
+
+private:
+
+    /// @brief Private copy constructor
+    ///
+    /// It is private to forbid anyone outside of this class to make copies.
+    /// The only legal way to copy a context is to call @ref clone().
+    ///
+    /// @param orig the original context to copy from
+    NetconfCfgContext(const NetconfCfgContext& orig);
+
+    /// @brief Private assignment operator to avoid potential for slicing.
+    ///
+    /// @param rhs Context to be assigned.
+    NetconfCfgContext& operator=(const NetconfCfgContext& rhs);
+
+    /// @brief Configured hooks libraries.
+    isc::hooks::HooksConfig hooks_config_;
+};
+
+/// @brief Ctrl Netconf Configuration Manager.
+///
+/// Provides the mechanisms for managing the Netconf application's
+/// configuration.
+class NetconfCfgMgr : public process::DCfgMgrBase {
+public:
+
+    /// @brief Constructor.
+    NetconfCfgMgr();
+
+    /// @brief Destructor
+    virtual ~NetconfCfgMgr();
+
+    /// @brief Convenience method that returns the Netconf configuration
+    /// context.
+    ///
+    /// @return returns a pointer to the configuration context.
+    NetconfCfgContextPtr getNetconfCfgContext() {
+        return (boost::dynamic_pointer_cast<NetconfCfgContext>(getContext()));
+    }
+
+    /// @brief Returns configuration summary in the textual format.
+    ///
+    /// @param selection Bitfield which describes the parts of the configuration
+    /// to be returned. This parameter is ignored for Netconf.
+    ///
+    /// @return Summary of the configuration in the textual format.
+    virtual std::string getConfigSummary(const uint32_t selection);
+
+protected:
+
+    /// @brief Parses configuration of Netconf.
+    ///
+    /// @param config Pointer to a configuration specified for netconf.
+    /// @param check_only Boolean flag indicating if this method should
+    /// only verify correctness of the provided configuration.
+    /// @return Pointer to a result of configuration parsing.
+    virtual isc::data::ConstElementPtr
+    parse(isc::data::ConstElementPtr config, bool check_only);
+
+    /// @brief Creates a new, blank NetconfCfgContext context.
+    ///
+    ///
+    /// This method is used at the beginning of configuration process to
+    /// create a fresh, empty copy of a NetconfCfgContext. This new context
+    /// will be populated during the configuration process and will replace the
+    /// existing context provided the configuration process completes without
+    /// error.
+    ///
+    /// @return Returns a DCfgContextBasePtr to the new context instance.
+    virtual process::DCfgContextBasePtr createNewContext();
+};
+
+/// @brief Defines a shared pointer to NetconfCfgMgr.
+typedef boost::shared_ptr<NetconfCfgMgr> NetconfCfgMgrPtr;
+
+} // namespace isc::netconf
+} // namespace isc
+
+#endif // NETCONF_CFG_MGR_H
diff --git a/src/bin/netconf/netconf_controller.cc b/src/bin/netconf/netconf_controller.cc
new file mode 100644 (file)
index 0000000..bccc5df
--- /dev/null
@@ -0,0 +1,70 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <netconf/netconf_controller.h>
+#include <netconf/netconf_process.h>
+#ifdef notyet
+#include <netconf/parser_context.h>
+#endif
+
+using namespace isc::process;
+
+namespace isc {
+namespace netconf {
+
+/// @brief Defines the application name, this is passed into base class
+/// it may be used to locate configuration data and appears in log statement.
+const char* NetconfController::netconf_app_name_ = "Netconf";
+
+/// @brief Defines the executable name. This is passed into the base class
+const char* NetconfController::netconf_bin_name_ = "kea-netconf";
+
+DControllerBasePtr&
+NetconfController::instance() {
+    // If the instance hasn't been created yet, create it.  Note this method
+    // must use the base class singleton instance methods.
+    if (!getController()) {
+        DControllerBasePtr controller_ptr(new NetconfController());
+        setController(controller_ptr);
+    }
+
+    return (getController());
+}
+
+DProcessBase*
+NetconfController::createProcess() {
+    // Instantiate and return an instance of the D2 application process. Note
+    // that the process is passed the controller's io_service.
+    return (new NetconfProcess(getAppName().c_str(), getIOService()));
+}
+
+isc::data::ConstElementPtr
+NetconfController::parseFile(const std::string& name) {
+#ifdef notyet
+    ParserContext parser;
+    return (parser.parseFile(name, ParserContext::PARSER_NETCONF));
+#else
+    isc_throw(NotImplemented, "NetconfController::parseFile("
+              << name << ")");
+#endif
+}
+
+NetconfController::NetconfController()
+    : DControllerBase(netconf_app_name_, netconf_bin_name_) {
+}
+
+NetconfController::~NetconfController() {
+}
+
+NetconfProcessPtr
+NetconfController::getNetconfProcess() {
+    return (boost::dynamic_pointer_cast<NetconfProcess>(getProcess()));
+}
+
+} // namespace isc::netconf
+} // namespace isc
diff --git a/src/bin/netconf/netconf_controller.h b/src/bin/netconf/netconf_controller.h
new file mode 100644 (file)
index 0000000..015a135
--- /dev/null
@@ -0,0 +1,78 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef NETCONF_CONTROLLER_H
+#define NETCONF_CONTROLLER_H
+
+#include <netconf/netconf_process.h>
+#include <process/d_controller.h>
+
+namespace isc {
+namespace netconf {
+
+/// @brief Process Controller for Netconf Process.
+///
+/// This class is the Netconf specific derivation of the DControllerBase.
+/// It creates and manages an instance of the Netconf application process,
+/// NetconfProcess.
+class NetconfController : public process::DControllerBase {
+public:
+
+    /// @brief Static singleton instance method.
+    ///
+    /// This method returns the base class singleton instance member.
+    /// It instantiates the singleton and sets the base class instance
+    /// member upon first invocation.
+    ///
+    /// @return returns the pointer reference to the singleton instance.
+    static process::DControllerBasePtr& instance();
+
+    /// @brief Destructor
+    virtual ~NetconfController();
+
+    /// @brief Returns pointer to an instance of the underlying process object.
+    NetconfProcessPtr getNetconfProcess();
+
+    /// @brief Defines the application name, this is passed into base class
+    /// and appears in log statements.
+    static const char* netconf_app_name_;
+
+    /// @brief Defines the executable name. This is passed into the base class
+    /// by convention this should match the executable name.
+    static const char* netconf_bin_name_;
+
+    /// @brief Parses the configuration file using Netconf::ParserContext (bison)
+    ///
+    /// @param name name of the text file to be parsed
+    /// @return Element tree structure representing parsed configuration
+    isc::data::ConstElementPtr
+    parseFile(const std::string& name);
+
+private:
+
+    /// @brief Creates an instance of the Netconf application process.
+    ///
+    /// This method is invoked during the process initialization step of
+    /// the controller launch.
+    ///
+    /// @return returns a DProcessBase* to the application process created.
+    /// Note the caller is responsible for destructing the process. This
+    /// is handled by the base class, which wraps this pointer with a smart
+    /// pointer.
+    virtual process::DProcessBase* createProcess();
+
+    /// @brief Constructor is declared private to maintain the integrity of
+    /// the singleton instance.
+    NetconfController();
+};
+
+// @Defines a shared pointer to NetconfController
+typedef boost::shared_ptr<NetconfController> NetconfControllerPtr;
+
+} // namespace isc::netconf
+} // namespace isc
+
+#endif // NETCONF_CONTROLLER_H
index d7ef00d6d1cfacda3e8318fae8f7342dd364c903..2c90f74b2c05854f016b76444a121482e882ba0d 100644 (file)
@@ -6,14 +6,25 @@
 
 $NAMESPACE isc::netconf
 
-% NETCONF_STARTING Kea-netconf agent (version %1) is starting with process-id %2
-Describe it later.
+% NETCONF_CONFIG_CHECK_FAIL Netconf configuration check failed: %1
+This error message indicates that Netconf had failed configuration
+check. Details are provided. Additional details may be available
+in earlier log entries, possibly on lower levels.
 
-% NETCONF_STATED Kea-netconf agent (version %1) started
-Describe it later.
+% NETCONF_CONFIG_FAIL Netconf configuration failed: %1
+This error message indicates that Netconf had failed configuration
+attempt. Details are provided. Additional details may be available
+in earlier log entries, possibly on lower levels.
 
-% NETCONF_SHUTDOWN Kea-netconf agent shutting down.
-Describe it later.
+% NETCONF_FAILED application experienced a fatal error: %1
+This is a debug message issued when the Netconf application
+encounters an unrecoverable error from within the event loop.
 
-% NETCONF_EXCEPTION Exception encountered: %1
-Oops.
+% NETCONF_RUN_EXIT application is exiting the event loop
+This is a debug message issued when the Netconf application exits its
+event loop.
+
+% NETCONF_STARTED Netconf (version %1) started
+This informational message indicates that Netconf has processed
+all configuration information and is ready to begin processing.
+The version is also printed.
diff --git a/src/bin/netconf/netconf_process.cc b/src/bin/netconf/netconf_process.cc
new file mode 100644 (file)
index 0000000..fc23364
--- /dev/null
@@ -0,0 +1,107 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <netconf/netconf_process.h>
+#include <netconf/netconf_controller.h>
+#include <netconf/netconf_log.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+#include <cc/command_interpreter.h>
+#include <config/timeouts.h>
+#include <boost/pointer_cast.hpp>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::process;
+
+
+namespace isc {
+namespace netconf {
+
+NetconfProcess::NetconfProcess(const char* name,
+                               const asiolink::IOServicePtr& io_service)
+    : DProcessBase(name, io_service, DCfgMgrBasePtr(new NetconfCfgMgr())) {
+}
+
+NetconfProcess::~NetconfProcess() {
+}
+
+void
+NetconfProcess::init() {
+}
+
+void
+NetconfProcess::run() {
+    LOG_INFO(netconf_logger, NETCONF_STARTED).arg(VERSION);
+
+    try {
+        // Let's process incoming data or expiring timers in a loop until
+        // shutdown condition is detected.
+        while (!shouldShutdown()) {
+            getIoService()->get_io_service().poll();
+        }
+        stopIOService();
+    } catch (const std::exception& ex) {
+        LOG_FATAL(netconf_logger, NETCONF_FAILED).arg(ex.what());
+        try {
+            stopIOService();
+        } catch (...) {
+            // Ignore double errors
+        }
+        isc_throw(DProcessBaseError,
+                  "Process run method failed: " << ex.what());
+    }
+
+    LOG_DEBUG(netconf_logger, isc::log::DBGLVL_START_SHUT, NETCONF_RUN_EXIT);
+}
+
+isc::data::ConstElementPtr
+NetconfProcess::shutdown(isc::data::ConstElementPtr /*args*/) {
+    setShutdownFlag(true);
+    return (isc::config::createAnswer(0, "Netconf is shutting down"));
+}
+
+isc::data::ConstElementPtr
+NetconfProcess::configure(isc::data::ConstElementPtr config_set,
+                          bool check_only) {
+    // System reconfiguration often poses an interesting issue whereby the
+    // configuration parsing is successful, but an attempt to use a new
+    // configuration is not. This will leave us in the inconsistent state
+    // when the configuration is in fact only partially applied and the
+    // system's ability to operate is impaired. The use of C++ lambda is
+    // a way to resolve this problem by injecting the code to the
+    // simpleParseConfig which performs an attempt to open new instance
+    // of the listener (if required). The lambda code will throw an
+    // exception if it fails and cause the simpleParseConfig to rollback
+    // configuration changes and report an error.
+    ConstElementPtr answer = getCfgMgr()->simpleParseConfig(config_set,
+                                                            check_only,
+                                                            [this]() {
+        DCfgContextBasePtr base_ctx = getCfgMgr()->getContext();
+        NetconfCfgContextPtr
+            ctx = boost::dynamic_pointer_cast<NetconfCfgContext>(base_ctx);
+
+        if (!ctx) {
+            isc_throw(Unexpected, "Internal logic error: bad context type");
+        }
+    });
+
+    int rcode = 0;
+    config::parseAnswer(rcode, answer);
+    return (answer);
+}
+
+NetconfCfgMgrPtr
+NetconfProcess::getNetconfCfgMgr() {
+    return (boost::dynamic_pointer_cast<NetconfCfgMgr>(getCfgMgr()));
+}
+
+} // namespace isc::netconf
+} // namespace isc
diff --git a/src/bin/netconf/netconf_process.h b/src/bin/netconf/netconf_process.h
new file mode 100644 (file)
index 0000000..ca02faf
--- /dev/null
@@ -0,0 +1,95 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef NETCONF_PROCESS_H
+#define NETCONF_PROCESS_H
+
+#include <netconf/netconf_cfg_mgr.h>
+#include <http/listener.h>
+#include <process/d_process.h>
+#include <vector>
+
+namespace isc {
+namespace netconf {
+
+/// @brief Kea Netconf Application Process
+///
+/// NetconfProcess provides top level application logic for the Netconf,
+/// a process managing Kea servers using YANG / NETCONF.
+///
+/// The Netconf receives YANG configuration change events, converts them
+/// to JSON commands sent to the respective Kea servers.
+class NetconfProcess : public process::DProcessBase {
+public:
+    /// @brief Constructor
+    ///
+    /// @param name name is a text label for the process. Generally used
+    /// in log statements, but otherwise arbitrary.
+    /// @param io_service is the io_service used by the caller for
+    /// asynchronous event handling.
+    NetconfProcess(const char* name, const asiolink::IOServicePtr& io_service);
+
+    /// @brief Destructor
+    virtual ~NetconfProcess();
+
+    /// @brief Initialize the Netconf process.
+    ///
+    /// This is invoked by the controller after command line arguments but
+    /// prior to configuration reception. The base class provides this method
+    /// as a place to perform any derivation-specific initialization steps
+    /// that are inappropriate for the constructor but necessary prior to
+    /// launch.
+    virtual void init();
+
+    /// @brief Implements the process's event loop.
+    ///
+    /// @throw DProcessBaseError if an operational error is encountered.
+    virtual void run();
+
+    /// @brief Initiates the process's shutdown process.
+    ///
+    /// This is last step in the shutdown event callback chain, that is
+    /// intended to notify the process it is to begin its shutdown process.
+    ///
+    /// @param args an Element set of shutdown arguments (if any) that are
+    /// supported by the process derivation.
+    ///
+    /// @return an Element that contains the results of argument processing,
+    /// consisting of an integer status value (0 means successful,
+    /// non-zero means failure), and a string explanation of the outcome.
+    ///
+    /// @throw DProcessBaseError if an operational error is encountered.
+    virtual isc::data::ConstElementPtr
+    shutdown(isc::data::ConstElementPtr args);
+
+    /// @brief Processes the given configuration.
+    ///
+    /// This method may be called multiple times during the process lifetime.
+    /// Certainly once during process startup, and possibly later if the user
+    /// alters configuration. This method must not throw, it should catch any
+    /// processing errors and return a success or failure answer as described
+    /// below.
+    ///
+    /// @param config_set a new configuration (JSON) for the process
+    /// @param check_only true if configuration is to be verified only, not applied
+    /// @return an Element that contains the results of configuration composed
+    /// of an integer status value (0 means successful, non-zero means failure),
+    /// and a string explanation of the outcome.
+    virtual isc::data::ConstElementPtr
+    configure(isc::data::ConstElementPtr config_set,
+              bool check_only = false);
+
+    /// @brief Returns a pointer to the configuration manager.
+    NetconfCfgMgrPtr getNetconfCfgMgr();
+};
+
+/// @brief Defines a shared pointer to NetconfProcess.
+typedef boost::shared_ptr<NetconfProcess> NetconfProcessPtr;
+
+}; // namespace isc::netconf
+}; // namespace isc
+
+#endif // NETCONF_PROCESS_H
diff --git a/src/bin/netconf/simple_parser.cc b/src/bin/netconf/simple_parser.cc
new file mode 100644 (file)
index 0000000..f40391e
--- /dev/null
@@ -0,0 +1,86 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <netconf/simple_parser.h>
+#include <cc/data.h>
+#include <cc/dhcp_config_error.h>
+#include <hooks/hooks_parser.h>
+#include <boost/foreach.hpp>
+
+using namespace isc::data;
+
+namespace isc {
+namespace netconf {
+/// @brief This sets of arrays define the default values in various scopes
+///        of the Netconf Configuration.
+///
+/// Each of those is documented in @file netconf/simple_parser.cc. This
+/// is different than most other comments in Kea code. The reason
+/// for placing those in .cc rather than .h file is that it
+/// is expected to be one centralized place to look at for
+/// the default values. This is expected to be looked at also by
+/// people who are not skilled in C or C++, so they may be
+/// confused with the differences between declaration and definition.
+/// As such, there's one file to look at that hopefully is readable
+/// without any C or C++ skills.
+///
+/// @{
+
+/// @brief This table defines default values for global options.
+///
+/// These are global Netconf parameters.
+const SimpleDefaults NetconfSimpleParser::NETCONF_DEFAULTS = {
+};
+
+/// @}
+
+/// ---------------------------------------------------------------------------
+/// --- end of default values -------------------------------------------------
+/// ---------------------------------------------------------------------------
+
+size_t NetconfSimpleParser::setAllDefaults(const isc::data::ElementPtr& global) {
+    size_t cnt = 0;
+
+    // Set global defaults first.
+    cnt = setDefaults(global, NETCONF_DEFAULTS);
+
+    return (cnt);
+}
+
+void
+NetconfSimpleParser::parse(const NetconfCfgContextPtr& ctx,
+                           const isc::data::ConstElementPtr& config,
+                           bool check_only) {
+
+    // User context can be done at anytime.
+    ConstElementPtr user_context = config->get("user-context");
+    if (user_context) {
+        ctx->setContext(user_context);
+    }
+
+    // Finally, let's get the hook libs!
+    
+    using namespace isc::hooks;
+    HooksConfig& libraries = ctx->getHooksConfig();
+    ConstElementPtr hooks = config->get("hooks-libraries");
+    if (hooks) {
+        HooksLibrariesParser hooks_parser;
+        hooks_parser.parse(libraries, hooks);
+        libraries.verifyLibraries(hooks->getPosition());
+    }
+
+    if (!check_only) {
+        // This occurs last as if it succeeds, there is no easy way
+        // revert it.  As a result, the failure to commit a subsequent
+        // change causes problems when trying to roll back.
+        libraries.loadLibraries();
+    }
+}
+
+};
+};
diff --git a/src/bin/netconf/simple_parser.h b/src/bin/netconf/simple_parser.h
new file mode 100644 (file)
index 0000000..f2422e6
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef NETCONF_SIMPLE_PARSER_H
+#define NETCONF_SIMPLE_PARSER_H
+
+#include <cc/simple_parser.h>
+#include <netconf/netconf_cfg_mgr.h>
+
+namespace isc {
+namespace netconf {
+
+/// @brief SimpleParser specialized for Netconf
+///
+/// This class is a @ref isc::data::SimpleParser dedicated to Netconf.
+/// In particular, it contains all the default values for the whole
+/// netconf defaults.
+///
+/// For the actual values, see @file netconf/simple_parser.cc
+class NetconfSimpleParser : public isc::data::SimpleParser {
+public:
+    /// @brief Sets all defaults for Netconf configuration
+    ///
+    /// This method sets global, option data and option definitions defaults.
+    ///
+    /// @param global scope to be filled in with defaults.
+    /// @return number of default values added
+    static size_t setAllDefaults(const isc::data::ElementPtr& global);
+
+    /// @brief Parses the netconf configuration
+    ///
+    /// @param ctx - parsed information will be stored here
+    /// @param config - Element tree structure that holds configuration
+    /// @param check_only - if true the configuration is verified only, not applied
+    ///
+    /// @throw ConfigError if any issues are encountered.
+    void parse(const NetconfCfgContextPtr& ctx,
+               const isc::data::ConstElementPtr& config,
+               bool check_only);
+
+    // see simple_parser.cc for comments for those parameters
+    static const isc::data::SimpleDefaults NETCONF_DEFAULTS;
+};
+
+};
+};
+#endif
index cb326e8443fbf4628bd48cc5d2a8fda300c29f9c..ec9dcc472545cba28ba03eebde58a935eed7cc01 100644 (file)
@@ -18,6 +18,7 @@ check-local:
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_srcdir)/src -I$(top_builddir)/src
 AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/netconf/tests\"
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 
 CLEANFILES = *.json *.log
@@ -36,31 +37,38 @@ TESTS_ENVIRONMENT = \
 TESTS =
 if HAVE_GTEST
 
+noinst_LTLIBRARIES = libbasic.la
+
 TESTS += netconf_unittests
 
-netconf_unittests_SOURCES  = run_unittests.cc
+netconf_unittests_SOURCES  = netconf_cfg_mgr_unittests.cc
+netconf_unittests_SOURCES += netconf_controller_unittests.cc
 netconf_unittests_SOURCES += netconf_env_unittest.cc
+netconf_unittests_SOURCES += netconf_process_unittests.cc
+netconf_unittests_SOURCES += run_unittests.cc
 
 netconf_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 netconf_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
 
 
 netconf_unittests_LDADD = $(top_builddir)/src/bin/netconf/libnetconf.la
-#netconf_unittests_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/process/testutils/libprocesstest.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
 #netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
 #netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
 #netconf_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
 #netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
 #netconf_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
 #netconf_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
-#netconf_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
 #netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
 #netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
-#netconf_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
-#netconf_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
 #netconf_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
 #netconf_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
-#netconf_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
 netconf_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 netconf_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
 netconf_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
@@ -68,6 +76,17 @@ netconf_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
 netconf_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
 netconf_unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
 netconf_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD) $(SYSREPO_LIBS)
+
+# The basic callout library - contains standard callouts
+libbasic_la_SOURCES  = basic_library.cc
+libbasic_la_CXXFLAGS = $(AM_CXXFLAGS)
+libbasic_la_CPPFLAGS = $(AM_CPPFLAGS)
+libbasic_la_LIBADD   = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libbasic_la_LIBADD  += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libbasic_la_LIBADD  += $(top_builddir)/src/lib/log/libkea-log.la
+libbasic_la_LDFLAGS  = -avoid-version -export-dynamic -module -rpath /nowhere
+
+nodist_netconf_unittests_SOURCES = test_libraries.h
 endif
 
 noinst_EXTRA_DIST = configs-list.txt
diff --git a/src/bin/netconf/tests/basic_library.cc b/src/bin/netconf/tests/basic_library.cc
new file mode 100644 (file)
index 0000000..deab59a
--- /dev/null
@@ -0,0 +1,72 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file
+/// @brief Basic callout library
+///
+/// This is source of a test library for Control Agent.
+///
+/// - Only the "version" framework function is supplied.
+///
+/// - hookpt_one callout is supplied.
+
+#include <config.h>
+#include <hooks/hooks.h>
+
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+extern "C" {
+
+// Callouts.  All return their result through the "result" argument.
+
+int
+context_create(CalloutHandle& handle) {
+    handle.setContext("result", static_cast<int>(10));
+    handle.setArgument("result", static_cast<int>(10));
+    return (0);
+}
+
+// First callout adds the passed "integer" argument to the initialized context
+// value of 10. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+    int data;
+    handle.getArgument("integer", data);
+
+    int result;
+    handle.getArgument("result", result);
+
+    result += data;
+    handle.setArgument("result", result);
+
+    return (0);
+}
+
+// Framework functions.
+
+int
+version() {
+    return (KEA_HOOKS_VERSION);
+}
+
+// load() initializes the user library if the main image was statically linked.
+int
+load(isc::hooks::LibraryHandle&) {
+#ifdef USE_STATIC_LINK
+    hooksStaticLinkInit();
+#endif
+    return (0);
+}
+
+}
+}
+
diff --git a/src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc b/src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc
new file mode 100644 (file)
index 0000000..a494625
--- /dev/null
@@ -0,0 +1,299 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <netconf/netconf_cfg_mgr.h>
+#ifdef notyet
+#include <netconf/parser_context.h>
+#endif
+#include <exceptions/exceptions.h>
+#include <process/testutils/d_test_stubs.h>
+#include <process/d_cfg_mgr.h>
+#include <netconf/tests/test_libraries.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::netconf;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::process;
+
+namespace  {
+
+/// @brief Almost regular netconf CfgMgr with internal parse method exposed.
+class NakedNetconfCfgMgr : public NetconfCfgMgr {
+public:
+    using NetconfCfgMgr::parse;
+};
+
+// Tests construction of NetconfCfgMgr class.
+TEST(NetconfCfgMgr, construction) {
+    boost::scoped_ptr<NetconfCfgMgr> cfg_mgr;
+
+    // Verify that configuration manager constructions without error.
+    ASSERT_NO_THROW(cfg_mgr.reset(new NetconfCfgMgr()));
+
+    // Verify that the context can be retrieved and is not null.
+    NetconfCfgContextPtr context;
+    ASSERT_NO_THROW(context = cfg_mgr->getNetconfCfgContext());
+    EXPECT_TRUE(context);
+
+    // Verify that the manager can be destructed without error.
+    EXPECT_NO_THROW(cfg_mgr.reset());
+}
+
+// Tests if getContext can be retrieved.
+TEST(NetconfCfgMgr, getContext) {
+    NetconfCfgMgr cfg_mgr;
+
+    NetconfCfgContextPtr ctx;
+    ASSERT_NO_THROW(ctx = cfg_mgr.getNetconfCfgContext());
+    ASSERT_TRUE(ctx);
+}
+
+// Tests if copied context retains all parameters.
+TEST(NetconfCfgMgr, contextCopy) {
+
+    NetconfCfgContext ctx;
+
+    HooksConfig& libs = ctx.getHooksConfig();
+    string exp_name("testlib1.so");
+    ConstElementPtr exp_param(new StringElement("myparam"));
+    libs.add(exp_name, exp_param);
+
+    // Make a copy.
+    DCfgContextBasePtr copy_base(ctx.clone());
+    NetconfCfgContextPtr copy = boost::dynamic_pointer_cast<NetconfCfgContext>(copy_base);
+    ASSERT_TRUE(copy);
+
+    // Check hook libs
+    const HookLibsCollection& libs2 = copy->getHooksConfig().get();
+    ASSERT_EQ(1, libs2.size());
+    EXPECT_EQ(exp_name, libs2[0].first);
+    ASSERT_TRUE(libs2[0].second);
+    EXPECT_EQ(exp_param->str(), libs2[0].second->str());
+}
+
+
+// Tests if the context can store and retrieve hook libs information.
+TEST(NetconfCfgMgr, contextHookParams) {
+    NetconfCfgContext ctx;
+
+    // By default there should be no hooks.
+    HooksConfig& libs = ctx.getHooksConfig();
+    EXPECT_TRUE(libs.get().empty());
+
+    libs.add("libone.so", ConstElementPtr());
+    libs.add("libtwo.so", Element::fromJSON("{\"foo\": true}"));
+    libs.add("libthree.so", Element::fromJSON("{\"bar\": 42}"));
+
+    const HooksConfig& stored_libs = ctx.getHooksConfig();
+    EXPECT_EQ(3, stored_libs.get().size());
+
+    // @todo add a == operator to HooksConfig
+    EXPECT_EQ(libs.get(), stored_libs.get());
+}
+
+#ifdef notyet
+/// Netconf configurations used in tests.
+const char* NETCONF_CONFIGS[] = {
+
+    // configuration 0: empty (nothing specified)
+    "{ }",
+
+    // Configuration 1: http parameters only (no control sockets, not hooks)
+    "{  \"http-host\": \"betelgeuse\",\n"
+    "    \"http-port\": 8001\n"
+    "}",
+
+    // Configuration 2: http and 1 socket
+    "{\n"
+    "    \"http-host\": \"betelgeuse\",\n"
+    "    \"http-port\": 8001,\n"
+    "    \"control-sockets\": {\n"
+    "        \"dhcp4\": {\n"
+    "            \"socket-name\": \"/tmp/socket-v4\"\n"
+    "        }\n"
+    "    }\n"
+    "}",
+
+    // Configuration 3: http and all 3 sockets
+    "{\n"
+    "    \"http-host\": \"betelgeuse\",\n"
+    "    \"http-port\": 8001,\n"
+    "    \"control-sockets\": {\n"
+    "        \"dhcp4\": {\n"
+    "            \"socket-name\": \"/tmp/socket-v4\"\n"
+    "        },\n"
+    "        \"dhcp6\": {\n"
+    "            \"socket-name\": \"/tmp/socket-v6\"\n"
+    "        },\n"
+    "        \"d2\": {\n"
+    "            \"socket-name\": \"/tmp/socket-d2\"\n"
+    "        }\n"
+    "   }\n"
+    "}",
+
+    // Configuration 4: http, 1 socket and hooks
+    // CA is able to load hook libraries that augment its operation.
+    // The primary functionality is the ability to add new commands.
+    "{\n"
+    "    \"http-host\": \"betelgeuse\",\n"
+    "    \"http-port\": 8001,\n"
+    "    \"control-sockets\": {\n"
+    "        \"dhcp4\": {\n"
+    "            \"socket-name\": \"/tmp/socket-v4\"\n"
+    "        }\n"
+    "   },\n"
+    "    \"hooks-libraries\": ["
+    "        {"
+    "          \"library\": \"%LIBRARY%\","
+    "              \"parameters\": {\n"
+    "              \"param1\": \"foo\"\n"
+    "            }\n"
+    "        }\n"
+    "     ]\n"
+    "}",
+
+    // Configuration 5: http and 1 socket (d2 only)
+    "{\n"
+    "    \"http-host\": \"betelgeuse\",\n"
+    "    \"http-port\": 8001,\n"
+    "    \"control-sockets\": {\n"
+    "        \"d2\": {\n"
+    "            \"socket-name\": \"/tmp/socket-d2\"\n"
+    "        }\n"
+    "    }\n"
+    "}",
+
+    // Configuration 6: http and 1 socket (dhcp6 only)
+    "{\n"
+    "    \"http-host\": \"betelgeuse\",\n"
+    "    \"http-port\": 8001,\n"
+    "    \"control-sockets\": {\n"
+    "        \"dhcp6\": {\n"
+    "            \"socket-name\": \"/tmp/socket-v6\"\n"
+    "        }\n"
+    "    }\n"
+    "}",
+
+    // Configuration 7: http and 2 sockets with user contexts and comments
+    "{\n"
+    "    \"user-context\": { \"comment\": \"Indirect comment\" },\n"
+    "    \"http-host\": \"betelgeuse\",\n"
+    "    \"http-port\": 8001,\n"
+    "    \"control-sockets\": {\n"
+    "        \"dhcp4\": {\n"
+    "            \"comment\": \"dhcp4 socket\",\n"
+    "            \"socket-name\": \"/tmp/socket-v4\"\n"
+    "        },\n"
+    "        \"dhcp6\": {\n"
+    "            \"socket-name\": \"/tmp/socket-v6\",\n"
+    "            \"user-context\": { \"version\": 1 }\n"
+    "        }\n"
+    "   }\n"
+    "}"
+};
+
+/// @brief Class used for testing CfgMgr
+class NetconfParserTest : public isc::process::ConfigParseTest {
+public:
+
+    /// @brief Tries to load input text as a configuration
+    ///
+    /// @param config text containing input configuration
+    /// @param expected_answer expected result of configuration (0 = success)
+    void configParse(const char* config, int expected_answer) {
+        isc::netconf::ParserContext parser;
+        ConstElementPtr json = parser.parseString(config, ParserContext::PARSER_SUB_NETCONF);
+
+        EXPECT_NO_THROW(answer_ = cfg_mgr_.parse(json, false));
+        EXPECT_TRUE(checkAnswer(expected_answer));
+    }
+
+    /// @brief Replaces %LIBRARY% with specified library name
+    ///
+    /// @param config input config text (should contain "%LIBRARY%" string)
+    /// @param lib_name %LIBRARY% will be replaced with that name
+    /// @return configuration text with library name replaced
+    string pathReplacer(const char* config, const char* lib_name) {
+        string txt(config);
+        txt.replace(txt.find("%LIBRARY%"), strlen("%LIBRARY%"), string(lib_name));
+        return (txt);
+    }
+
+    /// Configuration Manager (used in tests)
+    NakedNetconfCfgMgr cfg_mgr_;
+};
+
+// This test verifies if an empty config is handled properly. In practice such
+// a config makes little sense, but perhaps it's ok for a default deployment.
+// Sadly, our bison parser requires at last one parameter to be present.
+// Until we determine whether we want the empty config to be allowed or not,
+// this test remains disabled.
+TEST_F(NetconfParserTest, DISABLED_configParseEmpty) {
+    configParse(NETCONF_CONFIGS[0], 0);
+}
+
+// This test checks that the config file with hook library specified can be
+// loaded. This one is a bit tricky, because the parser sanity checks the lib
+// name. In particular, it checks if such a library exists. Therefore we
+// can't use NETCONF_CONFIGS[4] as is, but need to run it through path replacer.
+TEST_F(NetconfParserTest, configParseHooks) {
+    // Create the configuration with proper lib path.
+    string cfg = pathReplacer(NETCONF_CONFIGS[4], BASIC_CALLOUT_LIBRARY);
+    // The configuration should be successful.
+    configParse(cfg.c_str(), 0);
+
+    // The context now should have the library specified.
+    NetconfCfgContextPtr ctx = cfg_mgr_.getNetconfCfgContext();
+    const HookLibsCollection libs = ctx->getHooksConfig().get();
+    ASSERT_EQ(1, libs.size());
+    EXPECT_EQ(string(BASIC_CALLOUT_LIBRARY), libs[0].first);
+    ASSERT_TRUE(libs[0].second);
+    EXPECT_EQ("{ \"param1\": \"foo\" }", libs[0].second->str());
+}
+
+// This test checks comments.
+TEST_F(NetconfParserTest, comments) {
+    configParse(NETCONF_CONFIGS[7], 0);
+    NetconfCfgContextPtr netconf_ctx = cfg_mgr_.getNetconfCfgContext();
+    ASSERT_TRUE(netconf_ctx);
+
+    // Check global user context.
+    ConstElementPtr ctx = netconf_ctx->getContext();
+    ASSERT_TRUE(ctx);
+    ASSERT_EQ(1, ctx->size());
+    ASSERT_TRUE(ctx->get("comment"));
+    EXPECT_EQ("\"Indirect comment\"", ctx->get("comment")->str());
+
+    // There is a DHCP4 control socket.
+    ConstElementPtr socket4 = netconf_ctx->getControlSocketInfo("dhcp4");
+    ASSERT_TRUE(socket4);
+
+    // Check DHCP4 control socket user context.
+    ConstElementPtr ctx4 = socket4->get("user-context");
+    ASSERT_TRUE(ctx4);
+    ASSERT_EQ(1, ctx4->size());
+    ASSERT_TRUE(ctx4->get("comment"));
+    EXPECT_EQ("\"dhcp4 socket\"", ctx4->get("comment")->str());
+
+    // There is a DHCP6 control socket.
+    ConstElementPtr socket6 = netconf_ctx->getControlSocketInfo("dhcp6");
+    ASSERT_TRUE(socket6);
+
+    // Check DHCP6 control socket user context.
+    ConstElementPtr ctx6 = socket6->get("user-context");
+    ASSERT_TRUE(ctx6);
+    ASSERT_EQ(1, ctx6->size());
+    ASSERT_TRUE(ctx6->get("version"));
+    EXPECT_EQ("1", ctx6->get("version")->str());
+}
+#endif
+
+}; // end of anonymous namespace
diff --git a/src/bin/netconf/tests/netconf_controller_unittests.cc b/src/bin/netconf/tests/netconf_controller_unittests.cc
new file mode 100644 (file)
index 0000000..b9bb5ea
--- /dev/null
@@ -0,0 +1,225 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <netconf/netconf_controller.h>
+#include <netconf/netconf_process.h>
+#include <cc/data.h>
+#include <process/testutils/d_test_stubs.h>
+#include <boost/pointer_cast.hpp>
+#include <sstream>
+
+using namespace std;
+using namespace isc::netconf;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::process;
+using namespace boost::posix_time;
+
+namespace {
+
+#ifdef notyet
+/// @brief Valid Netconf Config used in tests.
+const char* valid_netconf_config =
+    "{"
+    "  \"control-sockets\": {"
+    "    \"dhcp4\": {"
+    "      \"socket-type\": \"unix\","
+    "      \"socket-name\": \"/first/dhcp4/socket\""
+    "    },"
+    "    \"dhcp6\": {"
+    "      \"socket-type\": \"unix\","
+    "      \"socket-name\": \"/first/dhcp6/socket\""
+    "    }"
+    "  }"
+    "}";
+#endif
+
+/// @brief test fixture class for testing NetconfController class. This
+/// class derives from DControllerTest and wraps NetconfController. Much
+/// of the underlying functionality is in the DControllerBase class which
+/// has extensive set of unit tests that are independent from Netconf.
+class NetconfControllerTest : public DControllerTest {
+public:
+
+    /// @brief Constructor.
+    NetconfControllerTest()
+        : DControllerTest(NetconfController::instance) {
+    }
+
+    /// @brief Returns pointer to NetconfProcess instance.
+    NetconfProcessPtr getNetconfProcess() {
+        return (boost::dynamic_pointer_cast<NetconfProcess>(getProcess()));
+    }
+
+    /// @brief Returns pointer to NetconfCfgMgr instance for a process.
+    NetconfCfgMgrPtr getNetconfCfgMgr() {
+        NetconfCfgMgrPtr p;
+        if (getNetconfProcess()) {
+            p = getNetconfProcess()->getNetconfCfgMgr();
+        }
+        return (p);
+    }
+
+    /// @brief Returns a pointer to the configuration context.
+    NetconfCfgContextPtr getNetconfCfgContext() {
+        NetconfCfgContextPtr p;
+        if (getNetconfCfgMgr()) {
+            p = getNetconfCfgMgr()->getNetconfCfgContext();
+        }
+        return (p);
+    }
+
+    /// @brief Compares the status in the given parse result to a given value.
+    ///
+    /// @param answer Element set containing an integer response and string
+    /// comment.
+    /// @param exp_status is an integer against which to compare the status.
+    /// @param exp_txt is expected text (not checked if "")
+    ///
+    void checkAnswer(isc::data::ConstElementPtr answer,
+                     int exp_status,
+                     string exp_txt = "") {
+
+        // Get rid of the outer list.
+        ASSERT_TRUE(answer);
+        ASSERT_EQ(Element::list, answer->getType());
+        ASSERT_LE(1, answer->size());
+        answer = answer->get(0);
+
+        int rcode = 0;
+        isc::data::ConstElementPtr comment;
+        comment = isc::config::parseAnswer(rcode, answer);
+
+        if (rcode != exp_status) {
+            ADD_FAILURE() << "Expected status code " << exp_status
+                          << " but received " << rcode << ", comment: "
+                          << (comment ? comment->str() : "(none)");
+        }
+
+        // Ok, parseAnswer interface is weird. If there are no arguments,
+        // it returns content of text. But if there is an argument,
+        // it returns the argument and it's not possible to retrieve
+        // "text" (i.e. comment).
+        if (comment->getType() != Element::string) {
+            comment = answer->get("text");
+        }
+
+        if (!exp_txt.empty()) {
+            EXPECT_EQ(exp_txt, comment->stringValue());
+        }
+    }
+
+};
+
+// Basic Controller instantiation testing.
+// Verifies that the controller singleton gets created and that the
+// basic derivation from the base class is intact.
+TEST_F(NetconfControllerTest, basicInstanceTesting) {
+    // Verify the we can the singleton instance can be fetched and that
+    // it is the correct type.
+    DControllerBasePtr& controller = DControllerTest::getController();
+    ASSERT_TRUE(controller);
+    ASSERT_NO_THROW(boost::dynamic_pointer_cast<NetconfController>(controller));
+
+    // Verify that controller's app name is correct.
+    EXPECT_TRUE(checkAppName(NetconfController::netconf_app_name_));
+
+    // Verify that controller's bin name is correct.
+    EXPECT_TRUE(checkBinName(NetconfController::netconf_bin_name_));
+
+    // Verify that controller's IOService exists.
+    EXPECT_TRUE(checkIOService());
+
+    // Verify that the Process does NOT exist.
+    EXPECT_FALSE(checkProcess());
+}
+
+
+// Tests basic command line processing.
+// Verifies that:
+// 1. Standard command line options are supported.
+// 2. Invalid options are detected.
+TEST_F(NetconfControllerTest, commandLineArgs) {
+    char* argv[] = { const_cast<char*>("progName"),
+                     const_cast<char*>("-c"),
+                     const_cast<char*>(DControllerTest::CFG_TEST_FILE),
+                     const_cast<char*>("-d") };
+    int argc = 4;
+
+    // Verify that verbose flag is false initially.
+    EXPECT_TRUE(checkVerbose(false));
+
+    // Verify that standard options can be parsed without error.
+    EXPECT_NO_THROW(parseArgs(argc, argv));
+
+    // Verify that verbose flag is true.
+    EXPECT_TRUE(checkVerbose(true));
+
+    // Verify configuration file name is correct.
+    EXPECT_TRUE(checkConfigFileName(DControllerTest::CFG_TEST_FILE));
+
+    // Verify that an unknown option is detected.
+    char* argv2[] = { const_cast<char*>("progName"),
+                      const_cast<char*>("-x") };
+    argc = 2;
+    EXPECT_THROW(parseArgs(argc, argv2), InvalidUsage);
+}
+
+// Tests application process creation and initialization.
+// Verifies that the process can be successfully created and initialized.
+TEST_F(NetconfControllerTest, initProcessTesting) {
+    ASSERT_NO_THROW(initProcess());
+    EXPECT_TRUE(checkProcess());
+}
+
+#ifdef notyet
+// Tests launch and normal shutdown (stand alone mode).
+// This creates an interval timer to generate a normal shutdown and then
+// launches with a valid, stand-alone command line and no simulated errors.
+TEST_F(NetconfControllerTest, launchNormalShutdown) {
+    // Write valid_netconf_config and then run launch() for 1000 ms.
+    time_duration elapsed_time;
+    runWithConfig(valid_netconf_config, 1000, elapsed_time);
+
+    // Give a generous margin to accommodate slower test environs.
+    EXPECT_TRUE(elapsed_time.total_milliseconds() >= 800 &&
+                elapsed_time.total_milliseconds() <= 1300);
+}
+
+// Tests that the SIGINT triggers a normal shutdown.
+TEST_F(NetconfControllerTest, sigintShutdown) {
+    // Setup to raise SIGHUP in 1 ms.
+    TimedSignal sighup(*getIOService(), SIGINT, 1);
+
+    // Write valid_netconf_config and then run launch() for a maximum
+    // of 1000 ms.
+    time_duration elapsed_time;
+    runWithConfig(valid_netconf_config, 1000, elapsed_time);
+
+    // Signaled shutdown should make our elapsed time much smaller than
+    // the maximum run time.  Give generous margin to accommodate slow
+    // test environs.
+    EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
+}
+
+// Tests that the SIGTERM triggers a normal shutdown.
+TEST_F(NetconfControllerTest, sigtermShutdown) {
+    // Setup to raise SIGHUP in 1 ms.
+    TimedSignal sighup(*getIOService(), SIGTERM, 1);
+
+    // Write valid_netconf_config and then run launch() for a maximum of 1 s.
+    time_duration elapsed_time;
+    runWithConfig(valid_netconf_config, 1000, elapsed_time);
+
+    // Signaled shutdown should make our elapsed time much smaller than
+    // the maximum run time.  Give generous margin to accommodate slow
+    // test environs.
+    EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
+}
+#endif
+
+}
diff --git a/src/bin/netconf/tests/netconf_process_unittests.cc b/src/bin/netconf/tests/netconf_process_unittests.cc
new file mode 100644 (file)
index 0000000..67b6ebd
--- /dev/null
@@ -0,0 +1,87 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <netconf/netconf_cfg_mgr.h>
+#include <netconf/netconf_process.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <process/testutils/d_test_stubs.h>
+#include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+using namespace boost::posix_time;
+using namespace isc;
+using namespace isc::netconf;
+using namespace isc::asiolink;
+using namespace isc::process;
+
+namespace {
+
+/// @brief NetconfProcess test fixture class.
+class NetconfProcessTest : public NetconfProcess, public ::testing::Test  {
+public:
+    /// @brief Constructor
+    NetconfProcessTest() :
+        NetconfProcess("netconf-test",
+                      IOServicePtr(new isc::asiolink::IOService())) {
+        NetconfCfgContextPtr ctx = getNetconfCfgMgr()->getNetconfCfgContext();
+    }
+
+    /// @brief Destructor
+    virtual ~NetconfProcessTest() {
+    }
+
+    /// @brief Callback that will invoke shutdown method.
+    void genShutdownCallback() {
+        shutdown(isc::data::ConstElementPtr());
+    }
+};
+
+// Test construction of the NetconfProcess object.
+TEST(NetconfProcess, construction) {
+    // Verify that the constructor will fail if given an empty
+    // io service.
+    IOServicePtr lcl_io_service;
+    EXPECT_THROW(NetconfProcess("TestProcess", lcl_io_service),
+                 DProcessBaseError);
+
+    // Verify that the constructor succeeds with a valid io_service
+    lcl_io_service.reset(new IOService());
+    ASSERT_NO_THROW(NetconfProcess("TestProcess", lcl_io_service));
+
+    // Verify tha the configuration is accessible after construction.
+    NetconfProcess netconf_process("TestProcess", lcl_io_service);
+    NetconfCfgMgrPtr cfg_mgr = netconf_process.getNetconfCfgMgr();
+    ASSERT_TRUE(cfg_mgr);
+}
+
+// Verifies that en external call to shutdown causes the run method to
+// exit gracefully. 
+TEST_F(NetconfProcessTest, shutdown) {
+    // Use an asiolink IntervalTimer and callback to generate the
+    // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+    IntervalTimer timer(*getIoService());
+    timer.setup(boost::bind(&NetconfProcessTest::genShutdownCallback, this),
+                2 * 1000);
+
+    // Record start time, and invoke run().
+    ptime start = microsec_clock::universal_time();
+    EXPECT_NO_THROW(run());
+
+    // Record stop time.
+    ptime stop = microsec_clock::universal_time();
+
+    // Verify that duration of the run invocation is the same as the
+    // timer duration.  This demonstrates that the shutdown was driven
+    // by an io_service event and callback.
+    time_duration elapsed = stop - start;
+    EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+                elapsed.total_milliseconds() <= 2200);
+}
+
+}
index c0847cad146034f3393e3a15649128bf0707b0ee..77dbb04902eb235003d8a8e81768e0473e4b5ad0 100644 (file)
@@ -14,7 +14,14 @@ int
 main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
 
+    // See the documentation of the KEA_* environment variables in
+    // src/lib/log/README for info on how to tweak logging
     isc::log::initLogger();
 
-    return (isc::util::unittests::run_all());
+    // Override --localstatedir value for PID files
+    setenv("KEA_PIDFILE_DIR", TEST_DATA_BUILDDIR, 1);
+
+    int result = RUN_ALL_TESTS();
+
+    return (result);
 }
diff --git a/src/bin/netconf/tests/test_libraries.h.in b/src/bin/netconf/tests/test_libraries.h.in
new file mode 100644 (file)
index 0000000..4602c4c
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef AGENT_TEST_LIBRARIES_H
+#define AGENT_TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+// Names of the libraries used in these tests.  These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// .so file.  Note that we access the .so file - libtool creates this as a
+// like to the real shared library.
+
+// Basic library with context_create and three "standard" callouts.
+static const char* BASIC_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libbasic.so";
+
+} // anonymous namespace
+
+#endif // TEST_LIBRARIES_H