summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bin/netconf/Makefile.am6
-rw-r--r--src/bin/netconf/main.cc143
-rw-r--r--src/bin/netconf/netconf_cfg_mgr.cc123
-rw-r--r--src/bin/netconf/netconf_cfg_mgr.h147
-rw-r--r--src/bin/netconf/netconf_controller.cc70
-rw-r--r--src/bin/netconf/netconf_controller.h78
-rw-r--r--src/bin/netconf/netconf_messages.mes27
-rw-r--r--src/bin/netconf/netconf_process.cc107
-rw-r--r--src/bin/netconf/netconf_process.h95
-rw-r--r--src/bin/netconf/simple_parser.cc86
-rw-r--r--src/bin/netconf/simple_parser.h50
-rw-r--r--src/bin/netconf/tests/Makefile.am31
-rw-r--r--src/bin/netconf/tests/basic_library.cc72
-rw-r--r--src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc299
-rw-r--r--src/bin/netconf/tests/netconf_controller_unittests.cc225
-rw-r--r--src/bin/netconf/tests/netconf_process_unittests.cc87
-rw-r--r--src/bin/netconf/tests/run_unittests.cc9
-rw-r--r--src/bin/netconf/tests/test_libraries.h.in24
18 files changed, 1543 insertions, 136 deletions
diff --git a/src/bin/netconf/Makefile.am b/src/bin/netconf/Makefile.am
index a52ceafbc3..ce81c68e56 100644
--- a/src/bin/netconf/Makefile.am
+++ b/src/bin/netconf/Makefile.am
@@ -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
diff --git a/src/bin/netconf/main.cc b/src/bin/netconf/main.cc
index 14015e75b6..30155b0909 100644
--- a/src/bin/netconf/main.cc
+++ b/src/bin/netconf/main.cc
@@ -5,135 +5,38 @@
// 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
index 0000000000..c80b8a76a3
--- /dev/null
+++ b/src/bin/netconf/netconf_cfg_mgr.cc
@@ -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
index 0000000000..796f118377
--- /dev/null
+++ b/src/bin/netconf/netconf_cfg_mgr.h
@@ -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
index 0000000000..bccc5dfb15
--- /dev/null
+++ b/src/bin/netconf/netconf_controller.cc
@@ -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
index 0000000000..015a135fd8
--- /dev/null
+++ b/src/bin/netconf/netconf_controller.h
@@ -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
diff --git a/src/bin/netconf/netconf_messages.mes b/src/bin/netconf/netconf_messages.mes
index d7ef00d6d1..2c90f74b2c 100644
--- a/src/bin/netconf/netconf_messages.mes
+++ b/src/bin/netconf/netconf_messages.mes
@@ -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
index 0000000000..fc233640eb
--- /dev/null
+++ b/src/bin/netconf/netconf_process.cc
@@ -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
index 0000000000..ca02faf5d2
--- /dev/null
+++ b/src/bin/netconf/netconf_process.h
@@ -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
index 0000000000..f40391e59d
--- /dev/null
+++ b/src/bin/netconf/simple_parser.cc
@@ -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
index 0000000000..f2422e6528
--- /dev/null
+++ b/src/bin/netconf/simple_parser.h
@@ -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
diff --git a/src/bin/netconf/tests/Makefile.am b/src/bin/netconf/tests/Makefile.am
index cb326e8443..ec9dcc4725 100644
--- a/src/bin/netconf/tests/Makefile.am
+++ b/src/bin/netconf/tests/Makefile.am
@@ -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
index 0000000000..deab59a692
--- /dev/null
+++ b/src/bin/netconf/tests/basic_library.cc
@@ -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
index 0000000000..a4946252cb
--- /dev/null
+++ b/src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc
@@ -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
index 0000000000..b9bb5eab19
--- /dev/null
+++ b/src/bin/netconf/tests/netconf_controller_unittests.cc
@@ -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
index 0000000000..67b6ebdd75
--- /dev/null
+++ b/src/bin/netconf/tests/netconf_process_unittests.cc
@@ -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);
+}
+
+}
diff --git a/src/bin/netconf/tests/run_unittests.cc b/src/bin/netconf/tests/run_unittests.cc
index c0847cad14..77dbb04902 100644
--- a/src/bin/netconf/tests/run_unittests.cc
+++ b/src/bin/netconf/tests/run_unittests.cc
@@ -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
index 0000000000..4602c4c81f
--- /dev/null
+++ b/src/bin/netconf/tests/test_libraries.h.in
@@ -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