]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[128-netconf-config] Added netconf config code from kea-yang
authorFrancis Dupont <fdupont@isc.org>
Thu, 4 Oct 2018 11:52:37 +0000 (13:52 +0200)
committerFrancis Dupont <fdupont@isc.org>
Sat, 6 Oct 2018 00:47:02 +0000 (20:47 -0400)
18 files changed:
configure.ac
src/bin/netconf/Makefile.am
src/bin/netconf/netconf_cfg_mgr.cc
src/bin/netconf/netconf_cfg_mgr.h
src/bin/netconf/netconf_config.cc [new file with mode: 0644]
src/bin/netconf/netconf_config.h [new file with mode: 0644]
src/bin/netconf/netconf_controller.cc
src/bin/netconf/simple_parser.cc
src/bin/netconf/simple_parser.h
src/bin/netconf/tests/Makefile.am
src/bin/netconf/tests/basic_library.cc
src/bin/netconf/tests/get_config_unittest.cc [new file with mode: 0644]
src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc
src/bin/netconf/tests/netconf_controller_unittests.cc
src/bin/netconf/tests/netconf_process_unittests.cc
src/bin/netconf/tests/test_data_files_config.h.in [new file with mode: 0644]
src/bin/netconf/tests/test_libraries.h.in
src/bin/netconf/tests/testdata/get_config.json [new file with mode: 0644]

index 0470c642149ad5bf82590dd70090bfbf107af2f9..e87315f4c17525ab94f976afb5c3a6578667d31f 100644 (file)
@@ -1513,6 +1513,7 @@ AC_CONFIG_FILES([Makefile
                  src/bin/netconf/Makefile
                  src/bin/netconf/tests/Makefile
                  src/bin/netconf/tests/netconf_tests.sh
+                 src/bin/netconf/tests/test_data_files_config.h
                  src/bin/netconf/tests/test_libraries.h
                  src/bin/perfdhcp/Makefile
                  src/bin/perfdhcp/tests/Makefile
index bad9d750738f374f050835eddd077da50b63ecd2..70c29377d2476f23897ed6576a06f79614b92e18 100644 (file)
@@ -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.
index 7b61123336339bf55ab92e6468360c7c3853d749..90a6c67fac8b945cfd63496745fd8b3669b03cee 100644 (file)
@@ -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);
index 0a56d0c820bca86991f5f74d4ae5660524514c89..af437eca7a20290870e4bab278249f10187ddfe4 100644 (file)
@@ -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 (file)
index 0000000..e05bf7d
--- /dev/null
@@ -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 (file)
index 0000000..100f6a9
--- /dev/null
@@ -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
index 9d7a338955aa7d141da6c01166d66798b86d7ead..62c0dc43e40ae9a0633e9fa2fa601ba52860657f 100644 (file)
@@ -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()
index bee5dd8b1ab05fcfc91b128f57553149253ea5d6..8edc689658a9192c0f82ac85e238aea0dfb4df81 100644 (file)
@@ -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();
index a0216fea7d5a3dedbbc825f251e4d6d99195fc08..eb13983e11d967e20b521bbf9a93c0e5d1ad7af6 100644 (file)
@@ -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;
 };
 
 };
index 4cb7cf5f8d50762cd702cf8b541f86d6b08ad353..0c14ae51e88bf3ccad4e0d03fbfb94bda19aed91 100644 (file)
@@ -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
index deab59a6925eb97c8ce9c845d27f0aaa0ef87597..f2186e085beb0ed6cbf94815f3b6e19454813f1c 100644 (file)
@@ -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 (file)
index 0000000..8789242
--- /dev/null
@@ -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));
+}
index 038ae2f61b5f5ca6aadb70fea27f809fceac2d76..ad9ac744ae0560469ad9705c14fc67dbb695bdbd 100644 (file)
@@ -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>
 
 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
index d984b8f3fc42aa4994ca5de04e7491b14deb2180..5fe294a0f279ff1fadb3c4a682cf0594d8b431a9 100644 (file)
@@ -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);
+}
+
 }
index e75b38323fbae7d9c921ed2de448dd9dc85e0989..956f6725111824a9b707ffa17635c9586fca049a 100644 (file)
@@ -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 (file)
index 0000000..78f5c60
--- /dev/null
@@ -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"
index 4602c4c81fd9309c09a7b8669bb503a65e73f281..f2a5a6597bfd0839bd447f35409ff1170228ce46 100644 (file)
@@ -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 (file)
index 0000000..02185fa
--- /dev/null
@@ -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"
+            }
+        }
+    }
+}