diff options
author | Francis Dupont <fdupont@isc.org> | 2018-10-04 13:52:37 +0200 |
---|---|---|
committer | Francis Dupont <fdupont@isc.org> | 2018-10-06 02:47:02 +0200 |
commit | 73cafb6aaa2a05822888f48f243ba929cb045eec (patch) | |
tree | b0045acd78bc118f5b72f63d3b27f5836d7a21a1 /src | |
parent | [65-libyang-pool_rebased] Rebase on last master before merge (diff) | |
download | kea-73cafb6aaa2a05822888f48f243ba929cb045eec.tar.xz kea-73cafb6aaa2a05822888f48f243ba929cb045eec.zip |
[128-netconf-config] Added netconf config code from kea-yang
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/netconf/Makefile.am | 3 | ||||
-rw-r--r-- | src/bin/netconf/netconf_cfg_mgr.cc | 28 | ||||
-rw-r--r-- | src/bin/netconf/netconf_cfg_mgr.h | 18 | ||||
-rw-r--r-- | src/bin/netconf/netconf_config.cc | 202 | ||||
-rw-r--r-- | src/bin/netconf/netconf_config.h | 255 | ||||
-rw-r--r-- | src/bin/netconf/netconf_controller.cc | 5 | ||||
-rw-r--r-- | src/bin/netconf/simple_parser.cc | 73 | ||||
-rw-r--r-- | src/bin/netconf/simple_parser.h | 15 | ||||
-rw-r--r-- | src/bin/netconf/tests/Makefile.am | 14 | ||||
-rw-r--r-- | src/bin/netconf/tests/basic_library.cc | 2 | ||||
-rw-r--r-- | src/bin/netconf/tests/get_config_unittest.cc | 291 | ||||
-rw-r--r-- | src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc | 499 | ||||
-rw-r--r-- | src/bin/netconf/tests/netconf_controller_unittests.cc | 64 | ||||
-rw-r--r-- | src/bin/netconf/tests/netconf_process_unittests.cc | 4 | ||||
-rw-r--r-- | src/bin/netconf/tests/test_data_files_config.h.in | 9 | ||||
-rw-r--r-- | src/bin/netconf/tests/test_libraries.h.in | 2 | ||||
-rw-r--r-- | src/bin/netconf/tests/testdata/get_config.json | 50 |
17 files changed, 1520 insertions, 14 deletions
diff --git a/src/bin/netconf/Makefile.am b/src/bin/netconf/Makefile.am index bad9d75073..70c29377d2 100644 --- a/src/bin/netconf/Makefile.am +++ b/src/bin/netconf/Makefile.am @@ -45,6 +45,7 @@ BUILT_SOURCES = netconf_messages.h netconf_messages.cc noinst_LTLIBRARIES = libnetconf.la libnetconf_la_SOURCES = netconf_cfg_mgr.cc netconf_cfg_mgr.h +libnetconf_la_SOURCES += netconf_config.cc netconf_config.h libnetconf_la_SOURCES += netconf_controller.cc netconf_controller.h libnetconf_la_SOURCES += netconf_log.cc netconf_log.h libnetconf_la_SOURCES += netconf_parser.cc netconf_parser.h @@ -86,7 +87,7 @@ kea_netconfdir = $(pkgdatadir) if GENERATE_PARSER parser: netconf_lexer.cc location.hh position.hh stack.hh netconf_parser.cc netconf_parser.h -# @echo "Flex/bison files regenerated" + @echo "Flex/bison files regenerated" # --- Flex/Bison stuff below -------------------------------------------------- # When debugging grammar issues, it's useful to add -v to bison parameters. diff --git a/src/bin/netconf/netconf_cfg_mgr.cc b/src/bin/netconf/netconf_cfg_mgr.cc index 7b61123336..90a6c67fac 100644 --- a/src/bin/netconf/netconf_cfg_mgr.cc +++ b/src/bin/netconf/netconf_cfg_mgr.cc @@ -20,11 +20,13 @@ using namespace isc::data; namespace isc { namespace netconf { -NetconfConfig::NetconfConfig() { +NetconfConfig::NetconfConfig() + : servers_map_(new ServersMap()) { } NetconfConfig::NetconfConfig(const NetconfConfig& orig) - : ConfigBase(), hooks_config_(orig.hooks_config_) { + : ConfigBase(), servers_map_(orig.servers_map_), + hooks_config_(orig.hooks_config_) { } NetconfCfgMgr::NetconfCfgMgr() @@ -39,8 +41,21 @@ NetconfCfgMgr::getConfigSummary(const uint32_t /*selection*/) { NetconfConfigPtr ctx = getNetconfConfig(); + // No globals to print. std::ostringstream s; + // Then print managed servers. + for (auto serv : *ctx->getServersMap()) { + if (s.tellp() != 0) { + s << " "; + } + s << serv.first; + } + + if (s.tellp() == 0) { + s << "none"; + } + // Finally, print the hook libraries names const isc::hooks::HookLibsCollection libs = ctx->getHooksConfig().get(); s << ", " << libs.size() << " lib(s):"; @@ -106,6 +121,8 @@ NetconfCfgMgr::parse(isc::data::ConstElementPtr config_set, return (answer); } + + ElementPtr NetconfConfig::toElement() const { ElementPtr netconf = Element::createMap(); @@ -113,6 +130,13 @@ NetconfConfig::toElement() const { contextToElement(netconf); // Set hooks-libraries netconf->set("hooks-libraries", hooks_config_.toElement()); + // Set managed-servers + ElementPtr servers = Element::createMap(); + for (auto serv : *servers_map_) { + ConstElementPtr server = serv.second->toElement(); + servers->set(serv.first, server); + } + netconf->set("managed-servers", servers); // Set Netconf ElementPtr result = Element::createMap(); result->set("Netconf", netconf); diff --git a/src/bin/netconf/netconf_cfg_mgr.h b/src/bin/netconf/netconf_cfg_mgr.h index 0a56d0c820..af437eca7a 100644 --- a/src/bin/netconf/netconf_cfg_mgr.h +++ b/src/bin/netconf/netconf_cfg_mgr.h @@ -10,6 +10,7 @@ #include <cc/data.h> #include <hooks/hooks_config.h> #include <process/d_cfg_mgr.h> +#include <netconf/netconf_config.h> #include <boost/pointer_cast.hpp> #include <map> #include <string> @@ -34,6 +35,20 @@ public: /// @brief Default constructor NetconfConfig(); + /// @brief Returns non-const reference to the managed servers map. + /// + /// @return non-const reference to the managed servers map. + ServersMapPtr& getServersMap() { + return (servers_map_); + } + + /// @brief Returns const reference to the managed servers map. + /// + /// @return const reference to the managed servers map. + const ServersMapPtr& getServersMap() const { + return (servers_map_); + } + /// @brief Returns non-const reference to configured hooks libraries. /// /// @return non-const reference to configured hooks libraries. @@ -73,6 +88,9 @@ private: /// @param rhs Context to be assigned. NetconfConfig& operator=(const NetconfConfig& rhs); + /// @brief Servers map. + ServersMapPtr servers_map_; + /// @brief Configured hooks libraries. isc::hooks::HooksConfig hooks_config_; }; diff --git a/src/bin/netconf/netconf_config.cc b/src/bin/netconf/netconf_config.cc new file mode 100644 index 0000000000..e05bf7d5ba --- /dev/null +++ b/src/bin/netconf/netconf_config.cc @@ -0,0 +1,202 @@ +// 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_log.h> +#include <netconf/netconf_cfg_mgr.h> +#include <exceptions/exceptions.h> +#include <asiolink/io_error.h> + +#include <boost/foreach.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/algorithm/string/predicate.hpp> + +#include <sstream> +#include <string> + +using namespace std; +using namespace isc::process; +using namespace isc::data; +using namespace isc::http; + +namespace isc { +namespace netconf { + +// *********************** ControlSocket ************************* + +ControlSocket::ControlSocket(Type type, const string& name, const Url& url) + : type_(type), name_(name), url_(url) { +} + +ControlSocket::~ControlSocket() { +} + +ControlSocket::Type +ControlSocket::stringToType(const string& type) { + if (type == "unix") { + return (ControlSocket::Type::UNIX); + } else if (type == "http") { + return (ControlSocket::Type::HTTP); + } else if (type == "stdout") { + return (ControlSocket::Type::STDOUT); + } + + isc_throw(BadValue, "Unknown control socket type: " << type); +} + +const string +ControlSocket::typeToString(ControlSocket::Type type) { + switch (type) { + case ControlSocket::Type::UNIX: + return ("unix"); + case ControlSocket::Type::HTTP: + return ("http"); + case ControlSocket::Type::STDOUT: + return ("stdout"); + } + /*UNREACHED*/ +} + +ElementPtr +ControlSocket::toElement() const { + ElementPtr result = Element::createMap(); + // Set user-context + contextToElement(result); + // Set type + result->set("socket-type", Element::create(typeToString(type_))); + // Set name + result->set("socket-name", Element::create(name_)); + // Set url + result->set("socket-url", Element::create(url_.toText())); + return (result); +} + +// *********************** Server ************************* +Server::Server(const string& model, ControlSocketPtr ctrl_sock) + : model_(model), control_socket_(ctrl_sock) { +} + +Server::~Server() { +} + +string +Server::toText() const { + ostringstream s; + s << "model: " << model_ << ", control socker: "; + if (!control_socket_) { + s << "none"; + } else { + switch (control_socket_->getType()) { + case ControlSocket::Type::UNIX: + s << "UNIX:'" << control_socket_->getName() << "'"; + break; + case ControlSocket::Type::HTTP: + s << "HTTP:'" << control_socket_->getUrl().toText() << "'"; + break; + case ControlSocket::Type::STDOUT: + s << "STDOUT"; + break; + } + } + return (s.str()); +} + +ElementPtr +Server::toElement() const { + ElementPtr result = Element::createMap(); + // Set user-context + contextToElement(result); + // Set model + result->set("model", Element::create(model_)); + // Set control-socket + if (control_socket_) { + result->set("control-socket", control_socket_->toElement()); + } + return (result); +} + +ostream& +operator<<(ostream& os, const Server& server) { + os << server.toText(); + return (os); +} + +// *************************** PARSERS *********************************** + +// *********************** ControlSocketParser ************************* + +ControlSocketPtr +ControlSocketParser::parse(ConstElementPtr ctrl_sock_config) { + ControlSocketPtr result; + string type_str = getString(ctrl_sock_config, "socket-type"); + string name = getString(ctrl_sock_config, "socket-name"); + string url_str = getString(ctrl_sock_config, "socket-url"); + ConstElementPtr user_context = ctrl_sock_config->get("user-context"); + + // Type must be valid. + ControlSocket::Type type; + try { + type = ControlSocket::stringToType(type_str); + } catch (const std::exception& ex) { + isc_throw(NetconfCfgError, ex.what() << " '" << type_str << "' (" + << getPosition("socket-type", ctrl_sock_config) << ")"); + } + + // Url must be valid. + Url url(url_str); + if (!url.isValid()) { + isc_throw(NetconfCfgError, "invalid control socket url: " + << url.getErrorMessage() << " '" << url_str << "' (" + << getPosition("socket-url", ctrl_sock_config) << ")"); + } + + // Create the control socket. + try { + result.reset(new ControlSocket(type, name, url)); + } catch (const std::exception& ex) { + isc_throw(NetconfCfgError, ex.what() << " (" + << ctrl_sock_config->getPosition() << ")"); + } + + // Add user-context. + if (user_context) { + result->setContext(user_context); + } + + return (result); +} + +// *********************** ServerParser ************************* + +ServerPtr +ServerParser::parse(ConstElementPtr server_config) { + ServerPtr result; + string model = getString(server_config, "model"); + ConstElementPtr user_context = server_config->get("user-context"); + ConstElementPtr ctrl_sock_config = server_config->get("control-socket"); + ControlSocketPtr ctrl_sock; + if (ctrl_sock_config) { + ControlSocketParser parser; + ctrl_sock = parser.parse(ctrl_sock_config); + } + try { + result.reset(new Server(model, ctrl_sock)); + } catch (const std::exception& ex) { + isc_throw(NetconfCfgError, ex.what() << " (" + << server_config->getPosition() << ")"); + } + + // Add user-context. + if (user_context) { + result->setContext(user_context); + } + + return (result); +} + +}; // end of isc::netconf namespace +}; // end of isc namespace diff --git a/src/bin/netconf/netconf_config.h b/src/bin/netconf/netconf_config.h new file mode 100644 index 0000000000..100f6a9466 --- /dev/null +++ b/src/bin/netconf/netconf_config.h @@ -0,0 +1,255 @@ +// 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_CONFIG_H +#define NETCONF_CONFIG_H + +#include <cc/data.h> +#include <cc/cfg_to_element.h> +#include <cc/user_context.h> +#include <cc/simple_parser.h> +#include <http/url.h> +#include <exceptions/exceptions.h> + +#include <boost/foreach.hpp> + +#include <stdint.h> +#include <string> + +namespace isc { +namespace netconf { + +/// @file netconf_config.h +/// @brief A collection of classes for housing and parsing the application +/// configuration necessary for the Netconf application. +/// +/// @note NetconfConfig is not here: this file contains component of +/// this class but not the class itself. +/// +/// This file contains the class declarations for the class hierarchy created +/// from the Netconf configuration and the parser classes used to create it. +/// The application configuration consists of a list of managed server. +/// +/// The parsing class hierarchy reflects this same scheme. Working top down: +/// +/// A ServerMapParser handles the managed servers map invoking a ServerParser +/// to parse each server. +/// +/// A ServerParser handles the scalars which belong to the server as well as +/// creating and invoking a CtrlSocketParser to parse its control socket. +/// +/// A CtrlSocketParser handles the scalars which belong to the control socket. +/// +/// The following is sample configuration in JSON form with extra spacing +/// for clarity: +/// +/// @code +/// { +/// "managed-servers" : +/// { +/// "dhcp4": +/// { +/// "model": "kea-dhcp4-server", +/// "control-socket": +/// { +/// "socket-type": "unix", +/// "socket-name": "/tmp/server-v4.sock" +/// } +/// } +/// } +/// } +/// @endcode + +/// @brief Exception thrown when the error during configuration handling +/// occurs. +class NetconfCfgError : public isc::Exception { +public: + NetconfCfgError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Represents a Control Socket. +/// +/// Acts as a storage class containing the basic attributes which +/// describe a Control Socket. +class ControlSocket : public isc::data::UserContext, + public isc::data::CfgToElement { +public: + /// @brief Defines the list of possible constrol socket types. + enum Type { + UNIX, //< Unix socket. + HTTP, //< HTTP socket. + STDOUT //< standard output. + }; + + /// @brief Constructor. + /// + /// @param type The socket type. + /// @param name The Unix socket name. + /// @param url The HTTP server URL. + ControlSocket(Type type, const std::string& name, + const isc::http::Url& url); + + /// @brief Destructor (doing nothing). + virtual ~ControlSocket(); + + /// @brief Getter which returns the socket type. + /// + /// @return returns the socket type as a ControlSocket::Type. + Type getType() const { + return (type_); + } + + /// @brief Getter which returns the Unix socket name. + /// + /// @return returns the Unix socket name as a std::string. + const std::string getName() const { + return (name_); + } + + /// @brief Getter which returns the HTTP server URL. + /// + /// @return returns the HTTP server URL as an isc::http::Url. + const isc::http::Url getUrl() const { + return (url_); + } + + /// @brief Converts socket type name to ControlSocket::Type. + /// + /// @param type The type name. + /// Currently supported values are "unix", "http" and "stdout". + /// + /// @return The ControlSocket::Type corresponding to the type name. + /// @throw BadValue if the type name isn't recognized. + static Type stringToType(const std::string& type); + + /// @brief Converts ControlSocket::Type to string. + /// + /// @param type The ControlSocket::Type type. + /// @return The type name corresponding to the enumeration element. + static const std::string typeToString(ControlSocket::Type type); + + /// @brief Unparse a configuration object + /// + /// @return a pointer to a configuration + virtual isc::data::ElementPtr toElement() const; + +private: + /// @brief The socket type. + Type type_; + + /// @brief The UNIX socket name. + const std::string name_; + + /// @brief The HTTP server URL. + const isc::http::Url url_; +}; + +/// @brief Defines a pointer for ControlSocket instances. +typedef boost::shared_ptr<ControlSocket> ControlSocketPtr; + +/// @brief Represents a Managed Server. +/// +/// Acts as a storage class containing the basic attributes and +/// the Control Socket which describe a Managed Server. +class Server : public isc::data::UserContext, public isc::data::CfgToElement { +public: + /// @brief Constructor. + /// + /// @param model The model name. + /// @param ctrl_sock The control socket. + Server(const std::string& model, ControlSocketPtr ctrl_sock); + + /// @brief Destructor (doing nothing). + virtual ~Server(); + + /// @brief Getter which returns the model name. + /// + /// @return returns the model name as a std::string + const std::string getModel() const { + return (model_); + } + + /// @brief Getter which returns the control socket. + /// + /// @return returns the control socket as a ControlSocketPtr. + const ControlSocketPtr& getControlSocket() const { + return (control_socket_); + } + + /// @brief Returns a text representation for the server. + std::string toText() const; + + /// @brief Unparse a configuration object + /// + /// @return a pointer to a configuration + virtual isc::data::ElementPtr toElement() const; + +private: + /// @brief The model name. + const std::string model_; + + /// @brief The control socket. + ControlSocketPtr control_socket_; +}; + +/// @brief Defines a pointer for Server instances. +typedef boost::shared_ptr<Server> ServerPtr; + +/// @brief Defines a map of Servers, keyed by the name. +typedef std::map<std::string, ServerPtr> ServersMap; + +/// @brief Defines a iterator pairing of name and Server +typedef std::pair<std::string, ServerPtr> ServersMapPair; + +/// @brief Defines a pointer to map of Servers. +typedef boost::shared_ptr<ServersMap> ServersMapPtr; + +/// @brief Dumps the contents of a Server as text to a output stream. +/// +/// @param os The output stream to which text should be sent. +/// @param server The Server instance to dump. +std::ostream& operator<<(std::ostream& os, const Server& server); + +/// @brief Parser for ControlSocket. +/// +/// This class parses the configuration element "control-socket" +/// and creates an instance of a ControlSocket. +class ControlSocketParser : public data::SimpleParser { +public: + /// @brief Performs the actual parsing of the given "control-socket" element. + /// + /// Parses a configuration for the elements needed to instantiate a + /// ControlSocket, validates those entries, creates a ControlSocket + /// instance. + /// + /// @param ctrl_sock_config is the "control-socket" configuration to parse. + /// + /// @return pointer to the new ControlSocket instance. + ControlSocketPtr parse(data::ConstElementPtr ctrl_sock_config); +}; + +/// @brief Parser for Server. +/// +/// This class parses the configuration value from the "managed-servers" map +/// and creates an instance of a Server. +class ServerParser : public data::SimpleParser { +public: + /// @brief Performs the actual parsing of the given value from + /// the "managed-servers" map. + /// + /// Parses a configuration for the elements needed to instantiate a + /// Server, validates those entries, creates a Server instance. + /// + /// @param server_config is the value from the "managed-servers" map to parse. + /// @return pointer to the new Server instance. + ServerPtr parse(data::ConstElementPtr server_config); +}; + +}; // end of isc::netconf namespace +}; // end of isc namespace + +#endif // NETCONF_CONFIG_H diff --git a/src/bin/netconf/netconf_controller.cc b/src/bin/netconf/netconf_controller.cc index 9d7a338955..62c0dc43e4 100644 --- a/src/bin/netconf/netconf_controller.cc +++ b/src/bin/netconf/netconf_controller.cc @@ -8,6 +8,7 @@ #include <netconf/netconf_controller.h> #include <netconf/netconf_process.h> +#include <netconf/parser_context.h> using namespace isc::process; @@ -42,8 +43,8 @@ NetconfController::createProcess() { isc::data::ConstElementPtr NetconfController::parseFile(const std::string& name) { - isc_throw(NotImplemented, "NetconfController::parseFile(" - << name << ")"); + ParserContext parser; + return (parser.parseFile(name, ParserContext::PARSER_NETCONF)); } NetconfController::NetconfController() diff --git a/src/bin/netconf/simple_parser.cc b/src/bin/netconf/simple_parser.cc index bee5dd8b1a..8edc689658 100644 --- a/src/bin/netconf/simple_parser.cc +++ b/src/bin/netconf/simple_parser.cc @@ -7,6 +7,7 @@ #include <config.h> #include <netconf/simple_parser.h> +#include <netconf/netconf_config.h> #include <cc/data.h> #include <cc/dhcp_config_error.h> #include <hooks/hooks_parser.h> @@ -37,6 +38,33 @@ namespace netconf { const SimpleDefaults NetconfSimpleParser::NETCONF_DEFAULTS = { }; +/// Supplies defaults for control-socket elements +const SimpleDefaults NetconfSimpleParser::CTRL_SOCK_DEFAULTS = { + { "socket-type", Element::string, "stdout" }, + { "socket-name", Element::string, "" }, + { "socket-url" , Element::string, "http://127.0.0.1:8000/" } +}; + +/// Supplies defaults for dhcp4 managed server +const SimpleDefaults NetconfSimpleParser::DHCP4_DEFAULTS = { + { "model", Element::string, "kea-dhcp4-server" } +}; + +/// Supplies defaults for dhcp6 managed server +const SimpleDefaults NetconfSimpleParser::DHCP6_DEFAULTS = { + { "model", Element::string, "kea-dhcp6-server" } +}; + +/// Supplies defaults for d2 managed server +const SimpleDefaults NetconfSimpleParser::D2_DEFAULTS = { + { "model", Element::string, "kea-dhcp-ddns" } +}; + +/// Supplies defaults for ca managed server +const SimpleDefaults NetconfSimpleParser::CA_DEFAULTS = { + { "model", Element::string, "kea-ctrl-agent" } +}; + /// @} /// --------------------------------------------------------------------------- @@ -49,6 +77,41 @@ size_t NetconfSimpleParser::setAllDefaults(const isc::data::ElementPtr& global) // Set global defaults first. cnt = setDefaults(global, NETCONF_DEFAULTS); + ConstElementPtr servers = global->get("managed-servers"); + if (servers) { + for (auto it : servers->mapValue()) { + cnt += setServerDefaults(it.first, it.second); + } + } + + return (cnt); +} + +size_t +NetconfSimpleParser::setServerDefaults(const std::string name, + isc::data::ConstElementPtr server) { + size_t cnt = 0; + + isc::data::ElementPtr mutable_server = + boost::const_pointer_cast<Element>(server); + if (name == "dhcp4") { + cnt += setDefaults(mutable_server, DHCP4_DEFAULTS); + } else if (name == "dhcp6") { + cnt += setDefaults(mutable_server, DHCP6_DEFAULTS); + } else if (name == "d2") { + cnt += setDefaults(mutable_server, D2_DEFAULTS); + } else if (name == "ca") { + cnt += setDefaults(mutable_server, CA_DEFAULTS); + } + + isc::data::ConstElementPtr ctrl_sock = server->get("control-socket"); + if (!ctrl_sock) { + return (cnt); + } + isc::data::ElementPtr mutable_ctrl_sock = + boost::const_pointer_cast<Element>(ctrl_sock); + cnt += setDefaults(mutable_ctrl_sock, CTRL_SOCK_DEFAULTS); + return (cnt); } @@ -63,6 +126,16 @@ NetconfSimpleParser::parse(const NetconfConfigPtr& ctx, ctx->setContext(user_context); } + // get managed servers. + ConstElementPtr servers = config->get("managed-servers"); + if (servers) { + for (auto it : servers->mapValue()) { + ServerParser server_parser; + ServerPtr server = server_parser.parse(it.second); + ctx->getServersMap()->insert(make_pair(it.first, server)); + } + } + // Finally, let's get the hook libs! using namespace isc::hooks; HooksConfig& libraries = ctx->getHooksConfig(); diff --git a/src/bin/netconf/simple_parser.h b/src/bin/netconf/simple_parser.h index a0216fea7d..eb13983e11 100644 --- a/src/bin/netconf/simple_parser.h +++ b/src/bin/netconf/simple_parser.h @@ -30,6 +30,16 @@ public: /// @return number of default values added static size_t setAllDefaults(const isc::data::ElementPtr& global); + /// @brief Adds default values to a Managed server entry. + /// + /// Adds server specific defaults, e.g. the default model. + /// + /// @param name server name / entry key + /// @param server server element / entry value + /// @return returns the number of default values added + static size_t setServerDefaults(const std::string name, + isc::data::ConstElementPtr server); + /// @brief Parses the netconf configuration /// /// @param ctx - parsed information will be stored here @@ -43,6 +53,11 @@ public: // see simple_parser.cc for comments for those parameters static const isc::data::SimpleDefaults NETCONF_DEFAULTS; + static const isc::data::SimpleDefaults CTRL_SOCK_DEFAULTS; + static const isc::data::SimpleDefaults DHCP4_DEFAULTS; + static const isc::data::SimpleDefaults DHCP6_DEFAULTS; + static const isc::data::SimpleDefaults D2_DEFAULTS; + static const isc::data::SimpleDefaults CA_DEFAULTS; }; }; diff --git a/src/bin/netconf/tests/Makefile.am b/src/bin/netconf/tests/Makefile.am index 4cb7cf5f8d..0c14ae51e8 100644 --- a/src/bin/netconf/tests/Makefile.am +++ b/src/bin/netconf/tests/Makefile.am @@ -5,6 +5,7 @@ SHTESTS += netconf_tests.sh noinst_SCRIPTS = netconf_tests.sh EXTRA_DIST = netconf_tests.sh.in +EXTRA_DIST += testdata/get_config.json # test using command-line arguments, so use check-local target instead of TESTS check-local: @@ -19,12 +20,13 @@ 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) AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/netconf\" +AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/netconf/tests\" +AM_CPPFLAGS += $(BOOST_INCLUDES) CLEANFILES = *.json *.log -DISTCLEANFILES = netconf_tests.sh test_libraries.h +DISTCLEANFILES = netconf_tests.sh test_data_files_config.h test_libraries.h AM_CXXFLAGS = $(KEA_CXXFLAGS) @@ -42,7 +44,8 @@ noinst_LTLIBRARIES = libbasic.la TESTS += netconf_unittests -netconf_unittests_SOURCES = netconf_cfg_mgr_unittests.cc +netconf_unittests_SOURCES = get_config_unittest.cc +netconf_unittests_SOURCES += netconf_cfg_mgr_unittests.cc netconf_unittests_SOURCES += netconf_controller_unittests.cc netconf_unittests_SOURCES += netconf_process_unittests.cc netconf_unittests_SOURCES += parser_unittests.cc @@ -61,8 +64,9 @@ netconf_unittests_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.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/yang/testutils/libyangtest.la netconf_unittests_LDADD += $(top_builddir)/src/lib/yang/libkea-yang.la -netconf_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.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/dhcp/tests/libdhcptest.la netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la @@ -89,7 +93,7 @@ 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 +nodist_netconf_unittests_SOURCES = test_data_files_config.h 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 index deab59a692..f2186e085b 100644 --- a/src/bin/netconf/tests/basic_library.cc +++ b/src/bin/netconf/tests/basic_library.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// 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 diff --git a/src/bin/netconf/tests/get_config_unittest.cc b/src/bin/netconf/tests/get_config_unittest.cc new file mode 100644 index 0000000000..8789242ff3 --- /dev/null +++ b/src/bin/netconf/tests/get_config_unittest.cc @@ -0,0 +1,291 @@ +// 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 <cc/data.h> +#include <cc/command_interpreter.h> +#include <testutils/user_context_utils.h> +#include <process/testutils/d_test_stubs.h> +#include <netconf/netconf_cfg_mgr.h> +#include <netconf/parser_context.h> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <fstream> +#include <string> +#include <sstream> + +#include "test_data_files_config.h" +#include "test_libraries.h" + +using namespace isc::netconf; +using namespace isc::config; +using namespace isc::data; +using namespace isc::process; +using namespace isc::test; + +namespace { + +/// @name How to generate the testdata/get_config.json file +/// +/// Define GENERATE_ACTION and recompile. Run netconf_unittests on +/// NetconfGetCfgTest redirecting the standard error to a temporary +/// file, e.g. by +/// @code +/// ./netconf_unittests --gtest_filter="NetconfGetCfg*" > /dev/null 2> u +/// @endcode +/// +/// Update testdata/get_config.json using the temporary file content, +/// (removing head comment and restoring hook library path), +/// recompile without GENERATE_ACTION. + +/// @brief the generate action +/// false means do nothing, true means unparse extracted configurations +#ifdef GENERATE_ACTION +const bool generate_action = true; +#else +const bool generate_action = false; +#endif + +/// @brief Read a file into a string +std::string +readFile(const std::string& file_path) { + std::ifstream ifs(file_path); + if (!ifs.is_open()) { + ADD_FAILURE() << "readFile cannot open " << file_path; + isc_throw(isc::Unexpected, "readFile cannot open " << file_path); + } + std::string lines; + std::string line; + while (std::getline(ifs, line)) { + lines += line + "\n"; + } + ifs.close(); + return (lines); +} + +/// @brief Runs parser in JSON mode +ElementPtr +parseJSON(const std::string& in, bool verbose = false) { + try { + ParserContext ctx; + return (ctx.parseString(in, ParserContext::PARSER_JSON)); + } catch (const std::exception& ex) { + if (verbose) { + std::cout << "EXCEPTION: " << ex.what() << std::endl; + } + throw; + } +} + +/// @brief Runs parser in NETCONF mode +ElementPtr +parseNETCONF(const std::string& in, bool verbose = false) { + try { + ParserContext ctx; + return (ctx.parseString(in, ParserContext::PARSER_NETCONF)); + } catch (const std::exception& ex) { + if (verbose) { + std::cout << "EXCEPTION: " << ex.what() << std::endl; + } + throw; + } +} + +/// @brief Replace the library path +void +pathReplacer(ConstElementPtr netconf_cfg) { + ConstElementPtr hooks_libs = netconf_cfg->get("hooks-libraries"); + if (!hooks_libs || hooks_libs->empty()) { + return; + } + ElementPtr first_lib = hooks_libs->getNonConst(0); + std::string lib_path(BASIC_CALLOUT_LIBRARY); + first_lib->set("library", Element::create(lib_path)); +} + +/// @brief Almost regular netconf CfgMgr with internal parse method exposed. +class NakedNetconfCfgMgr : public NetconfCfgMgr { +public: + using NetconfCfgMgr::parse; +}; + +} + +/// Test fixture class +class NetconfGetCfgTest : public ConfigParseTest { +public: + NetconfGetCfgTest() + : rcode_(-1) { + srv_.reset(new NakedNetconfCfgMgr()); + // Create fresh context. + resetConfiguration(); + } + + ~NetconfGetCfgTest() { + resetConfiguration(); + } + + /// @brief Parse and Execute configuration + /// + /// Parses a configuration and executes a configuration of the server. + /// If the operation fails, the current test will register a failure. + /// + /// @param config Configuration to parse + /// @param operation Operation being performed. In the case of an error, + /// the error text will include the string "unable to <operation>.". + /// + /// @return true if the configuration succeeded, false if not. + bool + executeConfiguration(const std::string& config, const char* operation) { + // try JSON parser + ConstElementPtr json; + try { + json = parseJSON(config, true); + } catch (const std::exception& ex) { + ADD_FAILURE() << "invalid JSON for " << operation + << " failed with " << ex.what() + << " on\n" << config << "\n"; + return (false); + } + + // try NETCONF parser + try { + json = parseNETCONF(config, true); + } catch (...) { + ADD_FAILURE() << "parsing failed for " << operation + << " on\n" << prettyPrint(json) << "\n"; + return (false); + } + + // get Netconf element + ConstElementPtr ca = json->get("Netconf"); + if (!ca) { + ADD_FAILURE() << "cannot get Netconf for " << operation + << " on\n" << prettyPrint(json) << "\n"; + return (false); + } + + // update hooks-libraries + pathReplacer(ca); + + // try NETCONF configure + ConstElementPtr status; + try { + status = srv_->parse(ca, true); + } catch (const std::exception& ex) { + ADD_FAILURE() << "configure for " << operation + << " failed with " << ex.what() + << " on\n" << prettyPrint(json) << "\n"; + return (false); + } + + // The status object must not be NULL + if (!status) { + ADD_FAILURE() << "configure for " << operation + << " returned null on\n" + << prettyPrint(json) << "\n"; + return (false); + } + + // Returned value should be 0 (configuration success) + comment_ = parseAnswer(rcode_, status); + if (rcode_ != 0) { + string reason = ""; + if (comment_) { + reason = string(" (") + comment_->stringValue() + string(")"); + } + ADD_FAILURE() << "configure for " << operation + << " returned error code " + << rcode_ << reason << " on\n" + << prettyPrint(json) << "\n"; + return (false); + } + return (true); + } + + /// @brief Reset configuration database. + /// + /// This function resets configuration data base by + /// removing managed servers and hooks. Reset must + /// be performed after each test to make sure that + /// contents of the database do not affect result of + /// subsequent tests. + void resetConfiguration() { + string config = "{ \"Netconf\": { } }"; + EXPECT_TRUE(executeConfiguration(config, "reset config")); + } + + boost::scoped_ptr<NakedNetconfCfgMgr> srv_; ///< CA server under test + int rcode_; ///< Return code from element parsing + ConstElementPtr comment_; ///< Reason for parse fail +}; + +/// Test a configuration +TEST_F(NetconfGetCfgTest, simple) { + + // get the simple configuration + std::string simple_file = string(CFG_EXAMPLES) + "/" + "simple.json"; + std::string config; + ASSERT_NO_THROW(config = readFile(simple_file)); + + // get the expected configuration + std::string expected_file = + std::string(NETCONF_TEST_DATA_DIR) + "/" + "get_config.json"; + std::string expected; + ASSERT_NO_THROW(expected = readFile(expected_file)); + + // execute the sample configuration + ASSERT_TRUE(executeConfiguration(config, "simple config")); + + // unparse it + NetconfConfigPtr context = srv_->getNetconfConfig(); + ConstElementPtr unparsed; + ASSERT_NO_THROW(unparsed = context->toElement()); + + // dump if wanted else check + if (generate_action) { + std::cerr << "// Generated Configuration (remove this line)\n"; + ASSERT_NO_THROW(expected = prettyPrint(unparsed)); + prettyPrint(unparsed, std::cerr, 0, 4); + std::cerr << "\n"; + } else { + // get the expected config using the netconf syntax parser + ElementPtr jsond; + ASSERT_NO_THROW(jsond = parseNETCONF(expected, true)); + // get the expected config using the generic JSON syntax parser + ElementPtr jsonj; + ASSERT_NO_THROW(jsonj = parseJSON(expected)); + // the generic JSON parser does not handle comments + EXPECT_TRUE(isEquivalent(jsond, moveComments(jsonj))); + // replace the path by its actual value + ConstElementPtr ca; + ASSERT_NO_THROW(ca = jsonj->get("Netconf")); + ASSERT_TRUE(ca); + pathReplacer(ca); + // check that unparsed and updated expected values match + EXPECT_TRUE(isEquivalent(unparsed, jsonj)); + // check on pretty prints too + std::string current = prettyPrint(unparsed, 0, 4); + std::string expected2 = prettyPrint(jsonj, 0, 4); + EXPECT_EQ(expected2, current); + if (expected2 != current) { + expected = current + "\n"; + } + } + + // execute the netconft configuration + EXPECT_TRUE(executeConfiguration(expected, "unparsed config")); + + // is it a fixed point? + NetconfConfigPtr context2 = srv_->getNetconfConfig(); + ConstElementPtr unparsed2; + ASSERT_NO_THROW(unparsed2 = context2->toElement()); + ASSERT_TRUE(unparsed2); + EXPECT_TRUE(isEquivalent(unparsed, unparsed2)); +} diff --git a/src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc b/src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc index 038ae2f61b..ad9ac744ae 100644 --- a/src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc +++ b/src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc @@ -6,7 +6,9 @@ #include <config.h> #include <netconf/netconf_cfg_mgr.h> +#include <netconf/parser_context.h> #include <exceptions/exceptions.h> +#include <cc/command_interpreter.h> #include <process/testutils/d_test_stubs.h> #include <process/d_cfg_mgr.h> #include <netconf/tests/test_libraries.h> @@ -15,8 +17,10 @@ using namespace std; using namespace isc::netconf; +using namespace isc::config; using namespace isc::data; using namespace isc::hooks; +using namespace isc::http; using namespace isc::process; namespace { @@ -52,6 +56,61 @@ TEST(NetconfCfgMgr, getContext) { ASSERT_TRUE(ctx); } +// Tests if context can store and retrieve managed server information. +TEST(NetconfCfgMgr, contextServer) { + + NetconfConfig ctx; + + // Check managed server parameters. + // By default, there are no server stored. + ASSERT_TRUE(ctx.getServersMap()); + EXPECT_EQ(0, ctx.getServersMap()->size()); + + ControlSocketPtr socket1(new ControlSocket(ControlSocket::Type::UNIX, + "socket1", + Url("http://127.0.0.1:8000/"))); + ServerPtr server1(new Server("model1", socket1)); + ControlSocketPtr socket2(new ControlSocket(ControlSocket::Type::UNIX, + "socket2", + Url("http://127.0.0.1:8000/"))); + ServerPtr server2(new Server("model2", socket2)); + ControlSocketPtr socket3(new ControlSocket(ControlSocket::Type::UNIX, + "socket3", + Url("http://127.0.0.1:8000/"))); + ServerPtr server3(new Server("model3", socket3)); + ControlSocketPtr socket4(new ControlSocket(ControlSocket::Type::UNIX, + "socket4", + Url("http://127.0.0.1:8000/"))); + ServerPtr server4(new Server("model4", socket4)); + + // Ok, now set the server for D2 + EXPECT_NO_THROW(ctx.getServersMap()->insert(make_pair("d2", server1))); + + // Now check the values returned + EXPECT_EQ(1, ctx.getServersMap()->size()); + ASSERT_NO_THROW(ctx.getServersMap()->at("d2")); + EXPECT_EQ(server1, ctx.getServersMap()->at("d2")); + EXPECT_THROW(ctx.getServersMap()->at("dhcp4"), std::out_of_range); + + // Now set the v6 server and sanity check again + EXPECT_NO_THROW(ctx.getServersMap()->insert(make_pair("dhcp6", server2))); + + // Should be possible to retrieve two servers + EXPECT_EQ(2, ctx.getServersMap()->size()); + ASSERT_NO_THROW(ctx.getServersMap()->at("dhcp6")); + EXPECT_EQ(server1, ctx.getServersMap()->at("d2")); + EXPECT_EQ(server2, ctx.getServersMap()->at("dhcp6")); + + // Finally, set all servers. + EXPECT_NO_THROW(ctx.getServersMap()->insert(make_pair("dhcp4", server3))); + EXPECT_NO_THROW(ctx.getServersMap()->insert(make_pair("ca", server4))); + EXPECT_EQ(4, ctx.getServersMap()->size()); + ASSERT_NO_THROW(ctx.getServersMap()->at("dhcp4")); + ASSERT_NO_THROW(ctx.getServersMap()->at("ca")); + EXPECT_EQ(server3, ctx.getServersMap()->at("dhcp4")); + EXPECT_EQ(server4, ctx.getServersMap()->at("ca")); +} + // Tests if the context can store and retrieve hook libs information. TEST(NetconfCfgMgr, contextHookParams) { NetconfConfig ctx; @@ -70,4 +129,444 @@ TEST(NetconfCfgMgr, contextHookParams) { EXPECT_EQ(libs.get(), stored_libs.get()); } +/// Netconf configurations used in tests. +const char* NETCONF_CONFIGS[] = { + + // configuration 0: empty (nothing specified) + "{ }", + + // Configuration 1: global parameters only (no server, not hooks) + "{\n" + "}", + + // Configuration 2: 1 server + "{\n" + " \"managed-servers\": {\n" + " \"dhcp4\": {\n" + " \"control-socket\": {\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " }\n" + " }\n" + " }\n" + "}", + + // Configuration 3: all 4 servers + "{\n" + " \"managed-servers\": {\n" + " \"dhcp4\": {\n" + " \"control-socket\": {\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " }\n" + " },\n" + " \"dhcp6\": {\n" + " \"control-socket\": {\n" + " \"socket-name\": \"/tmp/socket-v6\"\n" + " }\n" + " },\n" + " \"d2\": {\n" + " \"control-socket\": {\n" + " \"socket-name\": \"/tmp/socket-d2\"\n" + " }\n" + " },\n" + " \"ca\": {\n" + " \"control-socket\": {\n" + " \"socket-name\": \"/tmp/socket-ca\"\n" + " }\n" + " }\n" + " }\n" + "}", + + // Configuration 4: 1 server and hooks + // Netconf is able to load hook libraries that augment its operation. + // The primary functionality is the ability to add new commands. + "{\n" + " \"managed-servers\": {\n" + " \"dhcp4\": {\n" + " \"control-socket\": {\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " }\n" + " }\n" + " },\n" + " \"hooks-libraries\": [" + " {" + " \"library\": \"%LIBRARY%\"," + " \"parameters\": {\n" + " \"param1\": \"foo\"\n" + " }\n" + " }\n" + " ]\n" + "}", + + // Configuration 5: 1 server (d2 only) + "{\n" + " \"managed-servers\": {\n" + " \"d2\": {\n" + " \"control-socket\": {\n" + " \"socket-name\": \"/tmp/socket-d2\"\n" + " }\n" + " }\n" + " }\n" + "}", + + // Configuration 6: 1 server (dhcp6 only) + "{\n" + " \"managed-servers\": {\n" + " \"dhcp6\": {\n" + " \"control-socket\": {\n" + " \"socket-name\": \"/tmp/socket-v6\"\n" + " }\n" + " }\n" + " }\n" + "}", + + // Configuration 7: 2 servers with user contexts and comments + "{\n" + " \"user-context\": { \"comment\": \"Indirect comment\" },\n" + " \"managed-servers\": {\n" + " \"dhcp4\": {\n" + " \"comment\": \"dhcp4 server\",\n" + " \"control-socket\": {\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " }\n" + " },\n" + " \"dhcp6\": {\n" + " \"control-socket\": {\n" + " \"socket-name\": \"/tmp/socket-v6\",\n" + " \"user-context\": { \"version\": 1 }\n" + " }\n" + " }\n" + " }\n" + "}", + + // Configuration 8: empty server with no control socket + "{\n" + " \"managed-servers\": {\n" + " \"dhcp4\": {\n" + " \"comment\": \"empty map not allowed\"\n" + " }\n" + " }\n" + "}", + + // Configuration 9: empty control socket + "{\n" + " \"managed-servers\": {\n" + " \"dhcp4\": {\n" + " \"control-socket\": {\n" + " \"comment\": \"empty map not allowed\"\n" + " }\n" + " }\n" + " }\n" + "}", + + // Configuration 10: bad socket type + "{\n" + " \"managed-servers\": {\n" + " \"dhcp6\": {\n" + " \"control-socket\": {\n" + " \"socket-type\": \"tcp\"\n" + " }\n" + " }\n" + " }\n" + "}", + + // Configuration 11: invalid socket Url + "{\n" + " \"managed-servers\": {\n" + " \"dhcp6\": {\n" + " \"control-socket\": {\n" + " \"socket-url\": \"bad\"\n" + " }\n" + " }\n" + " }\n" + "}" +}; + +// Tests the handling of bad socket type. Can't use the fixture class +// because the Netconf parser does not allow bad socket types. +TEST(NetconfParser, badSocketType) { + ConstElementPtr json; + ParserContext parser; + EXPECT_NO_THROW(json = parser.parseString(NETCONF_CONFIGS[10], + ParserContext::PARSER_JSON)); + ConstElementPtr answer; + NakedNetconfCfgMgr cfg_mgr; + EXPECT_NO_THROW(answer = cfg_mgr.parse(json, false)); + int rcode = 0; + string expected = + "\"Unknown control socket type: tcp 'tcp' (<string>:5:32)\""; + EXPECT_EQ(expected, parseAnswer(rcode, answer)->str()); + EXPECT_EQ(1, rcode); +} + +/// @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. +TEST_F(NetconfParserTest, configParseEmpty) { + configParse(NETCONF_CONFIGS[0], 0); + + NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig(); + ASSERT_TRUE(ctx); + ASSERT_TRUE(ctx->getServersMap()); + EXPECT_EQ(0, ctx->getServersMap()->size()); +} + +// This test verifies if a config with only globals is handled properly. +TEST_F(NetconfParserTest, configParseGlobalOnly) { + configParse(NETCONF_CONFIGS[1], 0); + + NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig(); + ASSERT_TRUE(ctx); + ASSERT_TRUE(ctx->getServersMap()); + EXPECT_EQ(0, ctx->getServersMap()->size()); +} + +// Tests if an empty (i.e. without a control socket) can be configured. +// Note that the syntax required the server map to not be really empty. +TEST_F(NetconfParserTest, configParseEmptyServer) { + configParse(NETCONF_CONFIGS[8], 0); + + NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig(); + ASSERT_TRUE(ctx); + ASSERT_TRUE(ctx->getServersMap()); + EXPECT_EQ(1, ctx->getServersMap()->size()); + ASSERT_NO_THROW(ctx->getServersMap()->at("dhcp4")); + ServerPtr server = ctx->getServersMap()->at("dhcp4"); + ASSERT_TRUE(server); + EXPECT_EQ("kea-dhcp4-server", server->getModel()); + ControlSocketPtr socket = server->getControlSocket(); + EXPECT_FALSE(socket); +} + +// This tests default values using a server with empty control socket +// Note that the syntax required the control socket map to not be really empty. +TEST_F(NetconfParserTest, configParseDefaults) { + configParse(NETCONF_CONFIGS[9], 0); + + NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig(); + ASSERT_TRUE(ctx); + ASSERT_TRUE(ctx->getServersMap()); + EXPECT_EQ(1, ctx->getServersMap()->size()); + ASSERT_NO_THROW(ctx->getServersMap()->at("dhcp4")); + ServerPtr server = ctx->getServersMap()->at("dhcp4"); + ASSERT_TRUE(server); + EXPECT_EQ("kea-dhcp4-server", server->getModel()); + ControlSocketPtr socket = server->getControlSocket(); + ASSERT_TRUE(socket); + + // Checking default. + EXPECT_EQ(ControlSocket::Type::STDOUT, socket->getType()); + EXPECT_EQ("", socket->getName()); + EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText()); +} + +// Tests if a single DHCPv4 server can be configured. +TEST_F(NetconfParserTest, configParseServerDhcp4) { + configParse(NETCONF_CONFIGS[2], 0); + + NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig(); + ASSERT_TRUE(ctx); + ASSERT_TRUE(ctx->getServersMap()); + EXPECT_EQ(1, ctx->getServersMap()->size()); + ASSERT_NO_THROW(ctx->getServersMap()->at("dhcp4")); + ServerPtr server = ctx->getServersMap()->at("dhcp4"); + ASSERT_TRUE(server); + EXPECT_EQ("kea-dhcp4-server", server->getModel()); + ControlSocketPtr socket = server->getControlSocket(); + ASSERT_TRUE(socket); + EXPECT_EQ(ControlSocket::Type::STDOUT, socket->getType()); + EXPECT_EQ("/tmp/socket-v4", socket->getName()); + EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText()); +} + +// Tests if a single D2 server can be configured. +TEST_F(NetconfParserTest, configParseServerD2) { + configParse(NETCONF_CONFIGS[5], 0); + + NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig(); + ASSERT_TRUE(ctx); + ASSERT_TRUE(ctx->getServersMap()); + EXPECT_EQ(1, ctx->getServersMap()->size()); + ASSERT_NO_THROW(ctx->getServersMap()->at("d2")); + ServerPtr server = ctx->getServersMap()->at("d2"); + ASSERT_TRUE(server); + EXPECT_EQ("kea-dhcp-ddns", server->getModel()); + ControlSocketPtr socket = server->getControlSocket(); + ASSERT_TRUE(socket); + EXPECT_EQ(ControlSocket::Type::STDOUT, socket->getType()); + EXPECT_EQ("/tmp/socket-d2", socket->getName()); + EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText()); +} + +// Tests if a single DHCPv6 server can be configured. +TEST_F(NetconfParserTest, configParseServerDhcp6) { + configParse(NETCONF_CONFIGS[6], 0); + + NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig(); + ASSERT_TRUE(ctx); + ASSERT_TRUE(ctx->getServersMap()); + EXPECT_EQ(1, ctx->getServersMap()->size()); + ASSERT_NO_THROW(ctx->getServersMap()->at("dhcp6")); + ServerPtr server = ctx->getServersMap()->at("dhcp6"); + ASSERT_TRUE(server); + EXPECT_EQ("kea-dhcp6-server", server->getModel()); + ControlSocketPtr socket = server->getControlSocket(); + ASSERT_TRUE(socket); + EXPECT_EQ(ControlSocket::Type::STDOUT, socket->getType()); + EXPECT_EQ("/tmp/socket-v6", socket->getName()); + EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText()); +} + +// This tests if all 4 servers can be configured and makes sure the parser +// doesn't confuse them. +TEST_F(NetconfParserTest, configParse4Servers) { + configParse(NETCONF_CONFIGS[3], 0); + + NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig(); + ASSERT_TRUE(ctx); + ASSERT_TRUE(ctx->getServersMap()); + EXPECT_EQ(4, ctx->getServersMap()->size()); + + ASSERT_NO_THROW(ctx->getServersMap()->at("dhcp4")); + ServerPtr server = ctx->getServersMap()->at("dhcp4"); + ASSERT_TRUE(server); + EXPECT_EQ("kea-dhcp4-server", server->getModel()); + ControlSocketPtr socket = server->getControlSocket(); + ASSERT_TRUE(socket); + EXPECT_EQ(ControlSocket::Type::STDOUT, socket->getType()); + EXPECT_EQ("/tmp/socket-v4", socket->getName()); + EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText()); + + ASSERT_NO_THROW(ctx->getServersMap()->at("dhcp6")); + server = ctx->getServersMap()->at("dhcp6"); + ASSERT_TRUE(server); + EXPECT_EQ("kea-dhcp6-server", server->getModel()); + socket = server->getControlSocket(); + ASSERT_TRUE(socket); + EXPECT_EQ(ControlSocket::Type::STDOUT, socket->getType()); + EXPECT_EQ("/tmp/socket-v6", socket->getName()); + EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText()); + + ASSERT_NO_THROW(ctx->getServersMap()->at("d2")); + server = ctx->getServersMap()->at("d2"); + ASSERT_TRUE(server); + EXPECT_EQ("kea-dhcp-ddns", server->getModel()); + socket = server->getControlSocket(); + ASSERT_TRUE(socket); + EXPECT_EQ(ControlSocket::Type::STDOUT, socket->getType()); + EXPECT_EQ("/tmp/socket-d2", socket->getName()); + EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText()); + + ASSERT_NO_THROW(ctx->getServersMap()->at("ca")); + server = ctx->getServersMap()->at("ca"); + ASSERT_TRUE(server); + EXPECT_EQ("kea-ctrl-agent", server->getModel()); + socket = server->getControlSocket(); + ASSERT_TRUE(socket); + EXPECT_EQ(ControlSocket::Type::STDOUT, socket->getType()); + EXPECT_EQ("/tmp/socket-ca", socket->getName()); + EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText()); +} + +// Tests the handling of invalid socket URL. +TEST_F(NetconfParserTest, configParseInvalidSocketUrl) { + configParse(NETCONF_CONFIGS[11], 1); + int rcode = 0; + string expected = + "\"invalid control socket url: url bad lacks http or https scheme " + "'bad' (<string>:5:31)\""; + EXPECT_EQ(expected, parseAnswer(rcode, answer_)->str()); +} + +// 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. + NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig(); + 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); + NetconfConfigPtr netconf_ctx = cfg_mgr_.getNetconfConfig(); + 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 server. + ASSERT_TRUE(netconf_ctx->getServersMap()); + ASSERT_NO_THROW(netconf_ctx->getServersMap()->at("dhcp4")); + ServerPtr server = netconf_ctx->getServersMap()->at("dhcp4"); + ASSERT_TRUE(server); + + // Check DHCP4 server user context. + ConstElementPtr ctx4 = server->getContext(); + ASSERT_TRUE(ctx4); + ASSERT_EQ(1, ctx4->size()); + ASSERT_TRUE(ctx4->get("comment")); + EXPECT_EQ("\"dhcp4 server\"", ctx4->get("comment")->str()); + + // There is a DHCP6 server. + ASSERT_NO_THROW(netconf_ctx->getServersMap()->at("dhcp6")); + server = netconf_ctx->getServersMap()->at("dhcp6"); + ASSERT_TRUE(server); + + // There is a DHCP6 control socket. + ControlSocketPtr socket = server->getControlSocket(); + ASSERT_TRUE(socket); + + // Check DHCP6 control socket user context. + ConstElementPtr ctx6 = socket->getContext(); + ASSERT_TRUE(ctx6); + ASSERT_EQ(1, ctx6->size()); + ASSERT_TRUE(ctx6->get("version")); + EXPECT_EQ("1", ctx6->get("version")->str()); +} + }; // end of anonymous namespace diff --git a/src/bin/netconf/tests/netconf_controller_unittests.cc b/src/bin/netconf/tests/netconf_controller_unittests.cc index d984b8f3fc..5fe294a0f2 100644 --- a/src/bin/netconf/tests/netconf_controller_unittests.cc +++ b/src/bin/netconf/tests/netconf_controller_unittests.cc @@ -21,6 +21,25 @@ using namespace boost::posix_time; namespace { +/// @brief Valid Netconf Config used in tests. +const char* valid_netconf_config = + "{" + " \"managed-servers\": {" + " \"dhcp4\": {" + " \"control-socket\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/first/dhcp4/socket\"" + " }" + " }," + " \"dhcp6\": {" + " \"control-socket\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/first/dhcp4/socket\"" + " }" + " }" + " }" + "}"; + /// @brief test fixture class for testing NetconfController class. /// /// This class derives from DControllerTest and wraps NetconfController. Much @@ -119,4 +138,49 @@ TEST_F(NetconfControllerTest, initProcessTesting) { EXPECT_TRUE(checkProcess()); } +// 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 200 ms. + time_duration elapsed_time; + runWithConfig(valid_netconf_config, 200, elapsed_time); + + // Give a generous margin to accommodate slower test environs. + EXPECT_TRUE(elapsed_time.total_milliseconds() >= 100 && + elapsed_time.total_milliseconds() <= 500); +} + +// 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 500 ms. + time_duration elapsed_time; + runWithConfig(valid_netconf_config, 500, 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 500 ms. + time_duration elapsed_time; + runWithConfig(valid_netconf_config, 500, 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); +} + } diff --git a/src/bin/netconf/tests/netconf_process_unittests.cc b/src/bin/netconf/tests/netconf_process_unittests.cc index e75b38323f..956f672511 100644 --- a/src/bin/netconf/tests/netconf_process_unittests.cc +++ b/src/bin/netconf/tests/netconf_process_unittests.cc @@ -28,7 +28,7 @@ public: /// @brief Constructor NetconfProcessTest() : NetconfProcess("netconf-test", - IOServicePtr(new isc::asiolink::IOService())) { + IOServicePtr(new isc::asiolink::IOService())) { NetconfConfigPtr ctx = getNetconfCfgMgr()->getNetconfConfig(); } @@ -61,7 +61,7 @@ TEST(NetconfProcess, construction) { } // Verifies that en external call to shutdown causes the run method to -// exit gracefully. +// exit gracefully. TEST_F(NetconfProcessTest, shutdown) { // Use an asiolink IntervalTimer and callback to generate the // shutdown invocation. (Note IntervalTimer setup is in milliseconds). diff --git a/src/bin/netconf/tests/test_data_files_config.h.in b/src/bin/netconf/tests/test_data_files_config.h.in new file mode 100644 index 0000000000..78f5c60f2d --- /dev/null +++ b/src/bin/netconf/tests/test_data_files_config.h.in @@ -0,0 +1,9 @@ +// 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/. + +/// @brief Path to netconf source dir +#define NETCONF_SRC_DIR "@abs_top_srcdir@/src/bin/netconf" +#define NETCONF_TEST_DATA_DIR "@abs_top_srcdir@/src/bin/netconf/tests/testdata" diff --git a/src/bin/netconf/tests/test_libraries.h.in b/src/bin/netconf/tests/test_libraries.h.in index 4602c4c81f..f2a5a6597b 100644 --- a/src/bin/netconf/tests/test_libraries.h.in +++ b/src/bin/netconf/tests/test_libraries.h.in @@ -1,4 +1,4 @@ -// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// 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 diff --git a/src/bin/netconf/tests/testdata/get_config.json b/src/bin/netconf/tests/testdata/get_config.json new file mode 100644 index 0000000000..02185fa42f --- /dev/null +++ b/src/bin/netconf/tests/testdata/get_config.json @@ -0,0 +1,50 @@ +{ + "Netconf": { + "hooks-libraries": [ + { + "library": "/tmp/ky/src/bin/netconf/tests/.libs/libbasic.so", + "parameters": { + "param1": "foo" + } + } + ], + "managed-servers": { + "ca": { + "control-socket": { + "socket-name": "", + "socket-type": "http", + "socket-url": "http://127.0.0.1:8000/" + }, + "model": "kea-ctrl-agent" + }, + "d2": { + "control-socket": { + "socket-name": "", + "socket-type": "stdout", + "socket-url": "http://127.0.0.1:8000/", + "user-context": { + "in-use": false + } + }, + "model": "kea-dhcp-ddns" + }, + "dhcp4": { + "comment": "DHCP4 server", + "control-socket": { + "socket-name": "/path/to/the/unix/socket-v4", + "socket-type": "unix", + "socket-url": "http://127.0.0.1:8000/" + }, + "model": "kea-dhcp4-server" + }, + "dhcp6": { + "control-socket": { + "socket-name": "/path/to/the/unix/socket-v6", + "socket-type": "unix", + "socket-url": "http://127.0.0.1:8000/" + }, + "model": "kea-dhcp6-server" + } + } + } +} |