diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/netconf/Makefile.am | 6 | ||||
-rw-r--r-- | src/bin/netconf/main.cc | 143 | ||||
-rw-r--r-- | src/bin/netconf/netconf_cfg_mgr.cc | 123 | ||||
-rw-r--r-- | src/bin/netconf/netconf_cfg_mgr.h | 147 | ||||
-rw-r--r-- | src/bin/netconf/netconf_controller.cc | 70 | ||||
-rw-r--r-- | src/bin/netconf/netconf_controller.h | 78 | ||||
-rw-r--r-- | src/bin/netconf/netconf_messages.mes | 27 | ||||
-rw-r--r-- | src/bin/netconf/netconf_process.cc | 107 | ||||
-rw-r--r-- | src/bin/netconf/netconf_process.h | 95 | ||||
-rw-r--r-- | src/bin/netconf/simple_parser.cc | 86 | ||||
-rw-r--r-- | src/bin/netconf/simple_parser.h | 50 | ||||
-rw-r--r-- | src/bin/netconf/tests/Makefile.am | 31 | ||||
-rw-r--r-- | src/bin/netconf/tests/basic_library.cc | 72 | ||||
-rw-r--r-- | src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc | 299 | ||||
-rw-r--r-- | src/bin/netconf/tests/netconf_controller_unittests.cc | 225 | ||||
-rw-r--r-- | src/bin/netconf/tests/netconf_process_unittests.cc | 87 | ||||
-rw-r--r-- | src/bin/netconf/tests/run_unittests.cc | 9 | ||||
-rw-r--r-- | src/bin/netconf/tests/test_libraries.h.in | 24 |
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 |