summaryrefslogtreecommitdiffstats
path: root/src/bin
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin')
-rw-r--r--src/bin/netconf/Makefile.am3
-rw-r--r--src/bin/netconf/netconf_cfg_mgr.cc28
-rw-r--r--src/bin/netconf/netconf_cfg_mgr.h18
-rw-r--r--src/bin/netconf/netconf_config.cc202
-rw-r--r--src/bin/netconf/netconf_config.h255
-rw-r--r--src/bin/netconf/netconf_controller.cc5
-rw-r--r--src/bin/netconf/simple_parser.cc73
-rw-r--r--src/bin/netconf/simple_parser.h15
-rw-r--r--src/bin/netconf/tests/Makefile.am14
-rw-r--r--src/bin/netconf/tests/basic_library.cc2
-rw-r--r--src/bin/netconf/tests/get_config_unittest.cc291
-rw-r--r--src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc499
-rw-r--r--src/bin/netconf/tests/netconf_controller_unittests.cc64
-rw-r--r--src/bin/netconf/tests/netconf_process_unittests.cc4
-rw-r--r--src/bin/netconf/tests/test_data_files_config.h.in9
-rw-r--r--src/bin/netconf/tests/test_libraries.h.in2
-rw-r--r--src/bin/netconf/tests/testdata/get_config.json50
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"
+ }
+ }
+ }
+}