Browse Source

[5134] Configuration parsing implemented in CA

Tomek Mrugalski 8 years ago
parent
commit
f88448a8c7

+ 1 - 0
src/bin/agent/Makefile.am

@@ -46,6 +46,7 @@ libagent_la_SOURCES  = ctrl_agent_cfg_mgr.cc ctrl_agent_cfg_mgr.h
 libagent_la_SOURCES += ctrl_agent_controller.cc ctrl_agent_controller.h
 libagent_la_SOURCES += ctrl_agent_controller.cc ctrl_agent_controller.h
 libagent_la_SOURCES += ctrl_agent_log.cc ctrl_agent_log.h
 libagent_la_SOURCES += ctrl_agent_log.cc ctrl_agent_log.h
 libagent_la_SOURCES += ctrl_agent_process.cc ctrl_agent_process.h
 libagent_la_SOURCES += ctrl_agent_process.cc ctrl_agent_process.h
+libagent_la_SOURCES += simple_parser.cc simple_parser.h
 libagent_la_SOURCES += agent_parser.cc agent_parser.h
 libagent_la_SOURCES += agent_parser.cc agent_parser.h
 libagent_la_SOURCES += parser_context.cc parser_context.h parser_context_decl.h
 libagent_la_SOURCES += parser_context.cc parser_context.h parser_context_decl.h
 libagent_la_SOURCES += agent_lexer.ll
 libagent_la_SOURCES += agent_lexer.ll

+ 95 - 6
src/bin/agent/ctrl_agent_cfg_mgr.cc

@@ -6,13 +6,23 @@
 
 
 #include <config.h>
 #include <config.h>
 #include <agent/ctrl_agent_cfg_mgr.h>
 #include <agent/ctrl_agent_cfg_mgr.h>
+#include <agent/ctrl_agent_log.h>
+#include <agent/simple_parser.h>
+#include <cc/simple_parser.h>
+#include <cc/command_interpreter.h>
 
 
 using namespace isc::dhcp;
 using namespace isc::dhcp;
 using namespace isc::process;
 using namespace isc::process;
+using namespace isc::data;
 
 
 namespace isc {
 namespace isc {
 namespace agent {
 namespace agent {
 
 
+CtrlAgentCfgContext::CtrlAgentCfgContext()
+    :http_host_(""), http_port_(0) {
+
+}
+
 CtrlAgentCfgMgr::CtrlAgentCfgMgr()
 CtrlAgentCfgMgr::CtrlAgentCfgMgr()
     : DCfgMgrBase(DCfgContextBasePtr(new CtrlAgentCfgContext())) {
     : DCfgMgrBase(DCfgContextBasePtr(new CtrlAgentCfgContext())) {
 }
 }
@@ -22,16 +32,47 @@ CtrlAgentCfgMgr::~CtrlAgentCfgMgr() {
 
 
 std::string
 std::string
 CtrlAgentCfgMgr::getConfigSummary(const uint32_t /*selection*/) {
 CtrlAgentCfgMgr::getConfigSummary(const uint32_t /*selection*/) {
-    return ("Control Agent is currently not configurable.");
+
+    CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
+
+    // First print the http stuff.
+    std::ostringstream s;
+    s << "listening on " << ctx->getHost() << ", port " << ctx->getPort()
+      << ", control sockets: ";
+
+    // Then print the control-sockets
+    bool socks = false;
+    if (ctx->getControlSocketInfo(CtrlAgentCfgContext::TYPE_D2)) {
+        s << "d2 ";
+        socks = true;
+    }
+    if (ctx->getControlSocketInfo(CtrlAgentCfgContext::TYPE_DHCP4)) {
+        s << "dhcp4 ";
+        socks = true;
+    }
+    if (ctx->getControlSocketInfo(CtrlAgentCfgContext::TYPE_DHCP6)) {
+        s << "dhcp6 ";
+        socks = true;
+    }
+    if (!socks) {
+        // That's weird
+        s << "none";
+    }
+
+    // Finally, print the hook libraries names
+    const hooks::HookLibsCollection libs = ctx->getLibraries();
+    s << ", " << libs.size() << " lib(s):";
+    for (auto lib = libs.begin(); lib != libs.end(); ++lib) {
+        s << lib->first << " ";
+    }
+
+    return (s.str());
 }
 }
 
 
 isc::dhcp::ParserPtr
 isc::dhcp::ParserPtr
-CtrlAgentCfgMgr::createConfigParser(const std::string& element_id,
+CtrlAgentCfgMgr::createConfigParser(const std::string& /*element_id*/,
                                     const isc::data::Element::Position& /*pos*/) {
                                     const isc::data::Element::Position& /*pos*/) {
-    // Create dummy parser, so as we don't return null pointer.
+    isc_throw(NotImplemented, "We don't use parser pointers anymore");
-    isc::dhcp::ParserPtr parser;
-    parser.reset(new Uint32Parser(element_id, getContext()->getUint32Storage()));
-    return (parser);
 }
 }
 
 
 DCfgContextBasePtr
 DCfgContextBasePtr
@@ -39,5 +80,53 @@ CtrlAgentCfgMgr::createNewContext() {
     return (DCfgContextBasePtr(new CtrlAgentCfgContext()));
     return (DCfgContextBasePtr(new CtrlAgentCfgContext()));
 }
 }
 
 
+isc::data::ConstElementPtr
+CtrlAgentCfgMgr::parse(isc::data::ConstElementPtr config_set, bool check_only) {
+    if (!config_set) {
+        isc_throw(DhcpConfigError, "Mandatory config parameter not provided");
+    }
+
+    CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
+
+    // Set the defaults
+    ElementPtr cfg = boost::const_pointer_cast<Element>(config_set);
+    AgentSimpleParser::setAllDefaults(cfg);
+
+    ConstElementPtr answer;
+    std::string excuse;
+    try {
+        // Do the actual parsing
+        AgentSimpleParser parser;
+        parser.parse(ctx, cfg, check_only);
+    } catch (const isc::Exception& ex) {
+        excuse = ex.what();
+        answer = isc::config::createAnswer(2, excuse);
+    } catch (...) {
+        excuse = "undefined configuration parsing error";
+        answer = isc::config::createAnswer(2, excuse);
+    }
+
+    if (answer) {
+        if (check_only) {
+            LOG_ERROR(agent_logger, CTRL_AGENT_CONFIG_CHECK_FAIL).arg(excuse);
+        } else {
+            LOG_ERROR(agent_logger, CTRL_AGENT_CONFIG_FAIL).arg(excuse);
+        }
+        return (answer);
+    }
+
+    if (check_only) {
+        answer = isc::config::createAnswer(0, "Configuration check successful");
+        LOG_INFO(agent_logger, CTRL_AGENT_CONFIG_CHECK_COMPLETE)
+            .arg(getConfigSummary(0));
+    } else {
+        answer = isc::config::createAnswer(0, "Configuration applied successfully.");
+        LOG_INFO(agent_logger, CTRL_AGENT_CONFIG_COMPLETE)
+            .arg(getConfigSummary(0));
+    }
+
+    return (answer);
+}
+
 } // namespace isc::agent
 } // namespace isc::agent
 } // namespace isc
 } // namespace isc

+ 99 - 12
src/bin/agent/ctrl_agent_cfg_mgr.h

@@ -7,8 +7,10 @@
 #ifndef CTRL_AGENT_CFG_MGR_H
 #ifndef CTRL_AGENT_CFG_MGR_H
 #define CTRL_AGENT_CFG_MGR_H
 #define CTRL_AGENT_CFG_MGR_H
 
 
+#include <cc/data.h>
 #include <process/d_cfg_mgr.h>
 #include <process/d_cfg_mgr.h>
 #include <boost/pointer_cast.hpp>
 #include <boost/pointer_cast.hpp>
+#include <hooks/libinfo.h>
 
 
 namespace isc {
 namespace isc {
 namespace agent {
 namespace agent {
@@ -26,6 +28,20 @@ typedef boost::shared_ptr<CtrlAgentCfgContext> CtrlAgentCfgContextPtr;
 /// It is derived from the context base class, DCfgContextBase.
 /// It is derived from the context base class, DCfgContextBase.
 class CtrlAgentCfgContext : public process::DCfgContextBase {
 class CtrlAgentCfgContext : public process::DCfgContextBase {
 public:
 public:
+
+    /// @brief Default constructor
+    CtrlAgentCfgContext();
+
+    /// @brief Specifies type of the server being controlled.
+    enum ServerType {
+        TYPE_DHCP4 = 0, ///< kea-dhcp4
+        TYPE_DHCP6 = 1, ///< kea-dhcp6
+        TYPE_D2 = 2     ///< kea-dhcp-ddns
+    };
+
+    /// @brief Used check that specified ServerType is within valid range.
+    static const uint32_t MAX_TYPE_SUPPORTED = TYPE_D2;
+
     /// @brief Creates a clone of this context object.
     /// @brief Creates a clone of this context object.
     ///
     ///
     /// @return A pointer to the new clone.
     /// @return A pointer to the new clone.
@@ -33,9 +49,84 @@ public:
         return (process::DCfgContextBasePtr(new CtrlAgentCfgContext(*this)));
         return (process::DCfgContextBasePtr(new CtrlAgentCfgContext(*this)));
     }
     }
 
 
+    /// @brief Returns information about control socket
+    ///
+    /// @param type type of the server being controlled
+    /// @return pointer to the Element that holds control-socket map
+    const data::ConstElementPtr getControlSocketInfo(ServerType type) const {
+        if (type > MAX_TYPE_SUPPORTED) {
+            isc_throw(BadValue, "Invalid server type");
+        }
+        return (ctrl_sockets_[static_cast<uint8_t>(type)]);
+    }
+
+    /// @brief Sets information about the control socket
+    ///
+    /// @param control_socket Element that holds control-socket map
+    /// @param type type of the server being controlled
+    void setControlSocketInfo(const isc::data::ConstElementPtr& control_socket,
+                              ServerType type) {
+        if (type > MAX_TYPE_SUPPORTED) {
+            isc_throw(BadValue, "Invalid server type");
+        }
+        ctrl_sockets_[static_cast<uint8_t>(type)] = control_socket;
+    }
+
+    /// @brief sets http-host parameter
+    ///
+    /// @param host hostname to be used during http socket creation
+    void setHost(const std::string& host) {
+        http_host_ = host;
+    }
+
+    /// @brief returns http-host parameter
+    ///
+    /// @return name of the http-host parameter
+    std::string getHost() const {
+        return (http_host_);
+    }
+
+    /// @brief Sets http port
+    ///
+    /// @param port sets the TCP port the http server will listen on
+    void setPort(const uint16_t port) {
+        http_port_ = port;
+    }
+
+    /// @brief Returns the TCP post the http server will listen on
+    uint16_t getPort() const {
+        return (http_port_);
+    }
+
+    /// @brief Returns a list of hook libraries
+    /// @return a list of hook libraries
+    const hooks::HookLibsCollection& getLibraries() const {
+        return (libraries_);
+    }
+
+    /// @brief Sets the list of hook libraries
+    ///
+    /// @params libs a coolection of libraries to remember.
+    void setLibraries(const hooks::HookLibsCollection& libs) {
+        libraries_ = libs;
+    }
+
+
 private:
 private:
     /// @brief Private assignment operator to avoid potential for slicing.
     /// @brief Private assignment operator to avoid potential for slicing.
     CtrlAgentCfgContext& operator=(const CtrlAgentCfgContext& rhs);
     CtrlAgentCfgContext& operator=(const CtrlAgentCfgContext& rhs);
+
+    /// Socket information will be stored here (for all supported servers)
+    data::ConstElementPtr ctrl_sockets_[MAX_TYPE_SUPPORTED + 1];
+
+    /// Hostname the CA should listen on.
+    std::string http_host_;
+
+    /// TCP port the CA should listen on.
+    uint16_t http_port_;
+
+    /// List of hook libraries.
+    hooks::HookLibsCollection libraries_;
 };
 };
 
 
 /// @brief Ctrl Agent Configuration Manager.
 /// @brief Ctrl Agent Configuration Manager.
@@ -69,20 +160,16 @@ public:
 
 
 protected:
 protected:
 
 
-    /// @brief  Create a parser instance based on an element id.
+    virtual isc::data::ConstElementPtr
-    ///
+    parse(isc::data::ConstElementPtr config, bool check_only);
-    /// Given an element_id returns an instance of the appropriate parser.
+
-    ///
+    /// @brief This is no longer used.
-    /// @param element_id is the string name of the element as it will appear
-    /// in the configuration set.
-    /// @param pos position within the configuration text (or file) of element
-    /// to be parsed.  This is passed for error messaging.
     ///
     ///
-    /// @return returns a ParserPtr to the parser instance.
+    /// @throw NotImplemented
+    /// @return nothing, always throws
     virtual isc::dhcp::ParserPtr
     virtual isc::dhcp::ParserPtr
-    createConfigParser(const std::string& element_id,
+    createConfigParser(const std::string&,
-                       const isc::data::Element::Position& pos
+                       const isc::data::Element::Position& pos);
-                       = isc::data::Element::ZERO_POSITION());
 
 
     /// @brief Creates a new, blank CtrlAgentCfgContext context.
     /// @brief Creates a new, blank CtrlAgentCfgContext context.
     ///
     ///

+ 18 - 0
src/bin/agent/ctrl_agent_messages.mes

@@ -18,3 +18,21 @@ event loop.
 This informational message indicates that the DHCP-DDNS server has
 This informational message indicates that the DHCP-DDNS server has
 processed all configuration information and is ready to begin processing.
 processed all configuration information and is ready to begin processing.
 The version is also printed.
 The version is also printed.
+
+% CTRL_AGENT_CONFIG_COMPLETE Control Agent configuration complete: %1
+This informational message indicates that the CA had completed its
+configuration.
+
+% CTRL_AGENT_CONFIG_FAIL Control Agent configuration failed: %1
+This error message indicates that the CA had failed configuration
+attempt. Details are provided. Additional details may be available
+in earlier log entries, possibly on lower levels.
+
+% CTRL_AGENT_CONFIG_CHECK_COMPLETE Control Agent configuration check complete: %1
+This informationnal message indicates that the CA had completed the
+configuration check. The outcome of this check is part of the message.
+
+% CTRL_AGENT_CONFIG_CHECK_FAIL Control Agent configuration check failed: %1
+This error message indicates that the CA had failed configuration
+check. Details are provided. Additional details may be available
+in earlier log entries, possibly on lower levels.

+ 4 - 2
src/bin/agent/ctrl_agent_process.cc

@@ -57,9 +57,11 @@ CtrlAgentProcess::shutdown(isc::data::ConstElementPtr /*args*/) {
 }
 }
 
 
 isc::data::ConstElementPtr
 isc::data::ConstElementPtr
-CtrlAgentProcess::configure(isc::data::ConstElementPtr config_set) {
+CtrlAgentProcess::configure(isc::data::ConstElementPtr config_set,
+                            bool check_only) {
     int rcode = 0;
     int rcode = 0;
-    isc::data::ConstElementPtr answer = getCfgMgr()->parseConfig(config_set);
+    isc::data::ConstElementPtr answer = getCfgMgr()->simpleParseConfig(config_set,
+                                                                       check_only);
     config::parseAnswer(rcode, answer);
     config::parseAnswer(rcode, answer);
     return (answer);
     return (answer);
 }
 }

+ 2 - 1
src/bin/agent/ctrl_agent_process.h

@@ -79,11 +79,12 @@ public:
     /// below.
     /// below.
     ///
     ///
     /// @param config_set a new configuration (JSON) for the process
     /// @param config_set a new configuration (JSON) for the process
+    /// @param check_only true if configuration is to be verified only, not applied
     /// @return an Element that contains the results of configuration composed
     /// @return an Element that contains the results of configuration composed
     /// of an integer status value (0 means successful, non-zero means failure),
     /// of an integer status value (0 means successful, non-zero means failure),
     /// and a string explanation of the outcome.
     /// and a string explanation of the outcome.
     virtual isc::data::ConstElementPtr
     virtual isc::data::ConstElementPtr
-    configure(isc::data::ConstElementPtr config_set);
+    configure(isc::data::ConstElementPtr config_set, bool check_only);
 
 
     /// @brief Processes the given command.
     /// @brief Processes the given command.
     ///
     ///

+ 125 - 0
src/bin/agent/simple_parser.cc

@@ -0,0 +1,125 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <agent/simple_parser.h>
+#include <cc/data.h>
+#include <cc/dhcp_config_error.h>
+#include <hooks/hooks_parser.h>
+#include <boost/foreach.hpp>
+
+using namespace isc::data;
+
+namespace isc {
+namespace agent {
+/// @brief This sets of arrays define the default values in various scopes
+///        of the Control Agent Configuration.
+///
+/// Each of those is documented in @file agent/simple_parser.cc. This
+/// is different than most other comments in Kea code. The reason
+/// for placing those in .cc rather than .h file is that it
+/// is expected to be one centralized place to look at for
+/// the default values. This is expected to be looked at also by
+/// people who are not skilled in C or C++, so they may be
+/// confused with the differences between declaration and definition.
+/// As such, there's one file to look at that hopefully is readable
+/// without any C or C++ skills.
+///
+/// @{
+
+/// @brief This table defines default values for global options.
+///
+/// These are global Control Agent parameters.
+const SimpleDefaults AgentSimpleParser::AGENT_DEFAULTS = {
+    { "http-post",    Element::string,  "localhost"},
+    { "http-port",    Element::integer,  "8000"}
+};
+
+/// @brief This table defines default values for control sockets.
+///
+const SimpleDefaults AgentSimpleParser::SOCKET_DEFAULTS = {
+    { "socket-type",  Element::string,  "unix"}
+};
+
+/// @}
+
+/// ---------------------------------------------------------------------------
+/// --- end of default values -------------------------------------------------
+/// ---------------------------------------------------------------------------
+
+size_t AgentSimpleParser::setAllDefaults(isc::data::ElementPtr global) {
+    size_t cnt = 0;
+
+    // Set global defaults first.
+    cnt = setDefaults(global, AGENT_DEFAULTS);
+
+    // Now set the defaults for control-sockets, if any.
+    ConstElementPtr sockets = global->get("control-sockets");
+    if (sockets) {
+        ElementPtr d2 = boost::const_pointer_cast<Element>(sockets->get("d2-server"));
+        if (d2) {
+            cnt += SimpleParser::setDefaults(d2, SOCKET_DEFAULTS);
+        }
+
+        ElementPtr d4 = boost::const_pointer_cast<Element>(sockets->get("dhcp4-server"));
+        if (d4) {
+            cnt += SimpleParser::setDefaults(d4, SOCKET_DEFAULTS);
+        }
+
+        ElementPtr d6 = boost::const_pointer_cast<Element>(sockets->get("dhcp6-server"));
+        if (d2) {
+            cnt += SimpleParser::setDefaults(d6, SOCKET_DEFAULTS);
+        }
+    }
+
+    return (cnt);
+}
+
+void
+AgentSimpleParser::parse(CtrlAgentCfgContextPtr ctx, isc::data::ConstElementPtr config,
+                         bool check_only) {
+
+    ctx->setHost(SimpleParser::getString(config, "http-host"));
+    ctx->setPort(SimpleParser::getIntType<uint16_t>(config, "http-port"));
+
+    ConstElementPtr ctrl_sockets = config->get("control-sockets");
+    if (!ctrl_sockets) {
+        isc_throw(ConfigError, "Missing mandatory parameter 'control-sockets'");
+    }
+
+    ConstElementPtr d2_socket = ctrl_sockets->get("d2-server");
+    ConstElementPtr d4_socket = ctrl_sockets->get("dhcp4-server");
+    ConstElementPtr d6_socket = ctrl_sockets->get("dhcp6-server");
+
+    if (d2_socket) {
+        ctx->setControlSocketInfo(d2_socket, CtrlAgentCfgContext::TYPE_D2);
+    }
+
+    if (d4_socket) {
+        ctx->setControlSocketInfo(d4_socket, CtrlAgentCfgContext::TYPE_DHCP4);
+    }
+
+    if (d6_socket) {
+        ctx->setControlSocketInfo(d6_socket, CtrlAgentCfgContext::TYPE_DHCP6);
+    }
+
+    hooks::HooksLibrariesParser hooks_parser;
+    
+    ConstElementPtr hooks = config->get("hooks-libraries");
+    if (hooks) {
+        hooks_parser.parse(hooks);
+        hooks_parser.verifyLibraries();
+    }
+
+    if (!check_only) {
+        // This occurs last as if it succeeds, there is no easy way
+        // revert it.  As a result, the failure to commit a subsequent
+        // change causes problems when trying to roll back.
+        hooks_parser.loadLibraries();
+    }
+}
+
+};
+};

+ 49 - 0
src/bin/agent/simple_parser.h

@@ -0,0 +1,49 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef AGENT_SIMPLE_PARSER_H
+#define AGENT_SIMPLE_PARSER_H
+
+#include <cc/simple_parser.h>
+#include <agent/ctrl_agent_cfg_mgr.h>
+
+namespace isc {
+namespace agent {
+
+/// @brief SimpleParser specialized for DHCPv4
+///
+/// This class is a @ref isc::data::SimpleParser dedicated to DHCPv4 parser.
+/// In particular, it contains all the default values and names of the
+/// parameters that are to be derived (inherited) between scopes.
+/// For the actual values, see @file agent/simple_parser.cc
+class AgentSimpleParser : public isc::data::SimpleParser {
+public:
+    /// @brief Sets all defaults for Control Agent configuration
+    ///
+    /// This method sets global, option data and option definitions defaults.
+    ///
+    /// @param global scope to be filled in with defaults.
+    /// @return number of default values added
+    static size_t setAllDefaults(isc::data::ElementPtr global);
+
+    /// @brief Parses the control agent configuration
+    ///
+    /// @param ctx - parsed information will be stored here
+    /// @param config - Element tree structure that holds configuration
+    /// @param check_only - if true the configuration is verified only, not applied
+    ///
+    /// @throw ConfigError if any issues are encountered.
+    void parse(CtrlAgentCfgContextPtr ctx, isc::data::ConstElementPtr config,
+               bool check_only);
+
+    // see simple_parser.cc for comments for those parameters
+    static const isc::data::SimpleDefaults AGENT_DEFAULTS;
+    static const isc::data::SimpleDefaults SOCKET_DEFAULTS;
+};
+
+};
+};
+#endif

+ 6 - 1
src/bin/d2/d2_process.cc

@@ -191,10 +191,15 @@ D2Process::shutdown(isc::data::ConstElementPtr args) {
 }
 }
 
 
 isc::data::ConstElementPtr
 isc::data::ConstElementPtr
-D2Process::configure(isc::data::ConstElementPtr config_set) {
+D2Process::configure(isc::data::ConstElementPtr config_set, bool check_only) {
     LOG_DEBUG(d2_logger, DBGLVL_TRACE_BASIC,
     LOG_DEBUG(d2_logger, DBGLVL_TRACE_BASIC,
               DHCP_DDNS_CONFIGURE).arg(config_set->str());
               DHCP_DDNS_CONFIGURE).arg(config_set->str());
 
 
+    /// @todo: Implement this eventually.
+    if (check_only) {
+        return (isc::config::createAnswer(0, "Configuration check is not supported by D2."));
+    }
+
     int rcode = 0;
     int rcode = 0;
     isc::data::ConstElementPtr comment;
     isc::data::ConstElementPtr comment;
     isc::data::ConstElementPtr answer = getCfgMgr()->parseConfig(config_set);;
     isc::data::ConstElementPtr answer = getCfgMgr()->parseConfig(config_set);;

+ 3 - 2
src/bin/d2/d2_process.h

@@ -153,11 +153,12 @@ public:
     /// is retained and a failure response is returned as described below.
     /// is retained and a failure response is returned as described below.
     ///
     ///
     /// @param config_set a new configuration (JSON) for the process
     /// @param config_set a new configuration (JSON) for the process
+    /// @param check_only true if configuration is to be verified only, not applied
     /// @return an Element that contains the results of configuration composed
     /// @return an Element that contains the results of configuration composed
     /// of an integer status value (0 means successful, non-zero means failure),
     /// of an integer status value (0 means successful, non-zero means failure),
     /// and a string explanation of the outcome.
     /// and a string explanation of the outcome.
-    virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
+    virtual isc::data::ConstElementPtr
-                                                 config_set);
+    configure(isc::data::ConstElementPtr config_set, bool check_only);
 
 
     /// @brief Processes the given command.
     /// @brief Processes the given command.
     ///
     ///

+ 5 - 5
src/bin/d2/tests/d2_process_unittests.cc

@@ -94,7 +94,7 @@ public:
             return res;
             return res;
         }
         }
 
 
-        isc::data::ConstElementPtr answer = configure(config_set_);
+        isc::data::ConstElementPtr answer = configure(config_set_, false);
         isc::data::ConstElementPtr comment;
         isc::data::ConstElementPtr comment;
         comment = isc::config::parseAnswer(rcode, answer);
         comment = isc::config::parseAnswer(rcode, answer);
 
 
@@ -181,7 +181,7 @@ TEST_F(D2ProcessTest, configure) {
     ASSERT_TRUE(fromJSON(valid_d2_config));
     ASSERT_TRUE(fromJSON(valid_d2_config));
 
 
     // Invoke configure() with a valid D2 configuration.
     // Invoke configure() with a valid D2 configuration.
-    isc::data::ConstElementPtr answer = configure(config_set_);
+    isc::data::ConstElementPtr answer = configure(config_set_, false);
 
 
     // Verify that configure result is success and reconfigure queue manager
     // Verify that configure result is success and reconfigure queue manager
     // flag is true.
     // flag is true.
@@ -199,7 +199,7 @@ TEST_F(D2ProcessTest, configure) {
     ASSERT_TRUE(fromJSON("{ \"bogus\": 1000 } "));
     ASSERT_TRUE(fromJSON("{ \"bogus\": 1000 } "));
 
 
     // Invoke configure() with the invalid configuration.
     // Invoke configure() with the invalid configuration.
-    answer = configure(config_set_);
+    answer = configure(config_set_, false);
 
 
     // Verify that configure result is failure, the reconfigure flag is
     // Verify that configure result is failure, the reconfigure flag is
     // false, and that the queue manager is still running.
     // false, and that the queue manager is still running.
@@ -360,7 +360,7 @@ TEST_F(D2ProcessTest, badConfigureRecovery) {
 
 
     // Invoke configure() with a valid config that contains an unusable IP
     // Invoke configure() with a valid config that contains an unusable IP
     ASSERT_TRUE(fromJSON(bad_ip_d2_config));
     ASSERT_TRUE(fromJSON(bad_ip_d2_config));
-    isc::data::ConstElementPtr answer = configure(config_set_);
+    isc::data::ConstElementPtr answer = configure(config_set_, false);
 
 
     // Verify that configure result is success and reconfigure queue manager
     // Verify that configure result is success and reconfigure queue manager
     // flag is true.
     // flag is true.
@@ -377,7 +377,7 @@ TEST_F(D2ProcessTest, badConfigureRecovery) {
 
 
     // Verify we can recover given a valid config with an usable IP address.
     // Verify we can recover given a valid config with an usable IP address.
     ASSERT_TRUE(fromJSON(valid_d2_config));
     ASSERT_TRUE(fromJSON(valid_d2_config));
-    answer = configure(config_set_);
+    answer = configure(config_set_, false);
 
 
     // Verify that configure result is success and reconfigure queue manager
     // Verify that configure result is success and reconfigure queue manager
     // flag is true.
     // flag is true.

+ 61 - 1
src/lib/process/d_cfg_mgr.cc

@@ -255,6 +255,62 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
     return (answer);
     return (answer);
 }
 }
 
 
+isc::data::ConstElementPtr
+DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set,
+                               bool check_only) {
+    LOG_DEBUG(dctl_logger, DBGLVL_COMMAND,
+                DCTL_CONFIG_START).arg(config_set->str());
+
+    if (!config_set) {
+        return (isc::config::createAnswer(1,
+                                    std::string("Can't parse NULL config")));
+    }
+
+    // The parsers implement data inheritance by directly accessing
+    // configuration context. For this reason the data parsers must store
+    // the parsed data into context immediately. This may cause data
+    // inconsistency if the parsing operation fails after the context has been
+    // modified. We need to preserve the original context here
+    // so as we can rollback changes when an error occurs.
+    DCfgContextBasePtr original_context = context_;
+    resetContext();
+
+    // Answer will hold the result returned to the caller.
+    ConstElementPtr answer;
+
+    try {
+        // Let's call the actual implementation
+        answer = parse(config_set, check_only);
+
+        // Everything was fine. Configuration set processed successfully.
+        if (!check_only) {
+            LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg(getConfigSummary(0));
+            answer = isc::config::createAnswer(0, "Configuration committed.");
+        } else {
+            LOG_INFO(dctl_logger, DCTL_CONFIG_CHECK_COMPLETE)
+                .arg(getConfigSummary(0))
+                .arg(config::answerToText(answer));
+        }
+
+    } catch (const std::exception& ex) {
+        LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(ex.what());
+        answer = isc::config::createAnswer(1, ex.what());
+
+        // An error occurred, so make sure that we restore original context.
+        context_ = original_context;
+        return (answer);
+    }
+
+    if (check_only) {
+        // If this is a configuration check only, then don't actually apply
+        // the configuration and reverse to the previous one.
+        context_ = original_context;
+    }
+
+    return (answer);
+}
+
+
 void
 void
 DCfgMgrBase::buildParams(isc::data::ConstElementPtr params_config) {
 DCfgMgrBase::buildParams(isc::data::ConstElementPtr params_config) {
     // Loop through scalars parsing them and committing them to storage.
     // Loop through scalars parsing them and committing them to storage.
@@ -288,6 +344,10 @@ void DCfgMgrBase::buildAndCommit(std::string& element_id,
     parser->commit();
     parser->commit();
 }
 }
 
 
+isc::data::ConstElementPtr
+DCfgMgrBase::parse(isc::data::ConstElementPtr, bool) {
+    isc_throw(DCfgMgrBaseError, "This class does not implement simple parser paradigm yet");
+}
+
 }; // end of isc::dhcp namespace
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
 }; // end of isc namespace
-

+ 63 - 1
src/lib/process/d_cfg_mgr.h

@@ -210,6 +210,10 @@ typedef std::vector<std::string> ElementIdList;
 /// class which is handed a set of configuration values to process by upper
 /// class which is handed a set of configuration values to process by upper
 /// application management layers.
 /// application management layers.
 ///
 ///
+/// This class allows two configuration methods:
+///
+/// 1. classic method
+///
 /// The class presents a public method for receiving new configurations,
 /// The class presents a public method for receiving new configurations,
 /// parseConfig.  This method coordinates the parsing effort as follows:
 /// parseConfig.  This method coordinates the parsing effort as follows:
 ///
 ///
@@ -256,6 +260,25 @@ typedef std::vector<std::string> ElementIdList;
 ///
 ///
 /// In the event that an error occurs, parsing is halted and the
 /// In the event that an error occurs, parsing is halted and the
 /// configuration context is restored from backup.
 /// configuration context is restored from backup.
+///
+/// See @ref isc::d2::D2CfgMgr and @ref isc::d2::D2Process for example use of
+/// this approach.
+///
+/// 2. simple configuration method
+///
+/// This approach assumes usage of @ref isc::data::SimpleParser paradigm. It
+/// does not use any intermediate storage, does not use parser pointers, does
+/// not enforce parsing order.
+///
+/// Here's the expected control flow order:
+/// 1. implementation calls simpleParseConfig from its configure method.
+/// 2. simpleParseConfig makes a configuration context
+/// 3. parse method from the derived class is called
+/// 4. if the configuration was unsuccessful of this is only a check, the
+///    old context is reinstantiated. If not, the configuration is kept.
+///
+/// See @ref isc::agent::CtrlAgentCfgMgr and @ref isc::agent::CtrlAgentProcess
+/// for example use of this approach.
 class DCfgMgrBase {
 class DCfgMgrBase {
 public:
 public:
     /// @brief Constructor
     /// @brief Constructor
@@ -272,7 +295,7 @@ public:
     /// @brief Acts as the receiver of new configurations and coordinates
     /// @brief Acts as the receiver of new configurations and coordinates
     /// the parsing as described in the class brief.
     /// the parsing as described in the class brief.
     ///
     ///
-    /// @param config_set is a set of configuration elements to parsed.
+    /// @param config_set is a set of configuration elements to be parsed.
     ///
     ///
     /// @return an Element that contains the results of configuration composed
     /// @return an Element that contains the results of configuration composed
     /// of an integer status value (0 means successful, non-zero means failure),
     /// of an integer status value (0 means successful, non-zero means failure),
@@ -280,6 +303,27 @@ public:
     isc::data::ConstElementPtr parseConfig(isc::data::ConstElementPtr
     isc::data::ConstElementPtr parseConfig(isc::data::ConstElementPtr
                                            config_set);
                                            config_set);
 
 
+
+    /// @brief Acts as the receiver of new configurations.
+    ///
+    /// This method is similar to what @ref parseConfig does, execept it employs
+    /// the simple parser paradigm: no intermediate storage, no parser pointers
+    /// no distinction between params_map and objects_map, parse order (if needed)
+    /// can be enforced in the actual implementation by calling specific
+    /// parsers first. See @ref isc::agent::CtrlAgentCfgMgr::parse for example.
+    ///
+    /// If check_only is true, the actual parsing is done to check if the configuration
+    /// is sane, but is then reverted.
+    ///
+    /// @param config set of configuration elements to be parsed
+    /// @param check_only true if the config is to be checked only, but not applied
+    /// @return an Element that contains the results of configuration composed
+    /// of an integer status value (0 means successful, non-zero means failure),
+    /// and a string explanation of the outcome.
+    isc::data::ConstElementPtr
+    simpleParseConfig(isc::data::ConstElementPtr config,
+                      bool check_only = false);
+
     /// @brief Adds a given element id to the end of the parse order list.
     /// @brief Adds a given element id to the end of the parse order list.
     ///
     ///
     /// The order in which object elements are retrieved from this is the
     /// The order in which object elements are retrieved from this is the
@@ -373,6 +417,24 @@ protected:
     /// @throw DCfgMgrBaseError if context is NULL.
     /// @throw DCfgMgrBaseError if context is NULL.
     void setContext(DCfgContextBasePtr& context);
     void setContext(DCfgContextBasePtr& context);
 
 
+    /// @brief Parses actual configuration.
+    ///
+    /// This method is expected to be implemented in derived classes that employ
+    /// SimpleParser paradigm (i.e. they call simpleParseConfig rather than
+    /// parseConfig from their configure method).
+    ///
+    /// Implementations that do not employ this method may provide dummy
+    /// implementation.
+    ///
+    /// @param config the Element tree structure that decribes the configuration.
+    /// @param check_only false for normal configuration, true when verifying only
+    ///
+    /// @return an Element that contains the results of configuration composed
+    /// of an integer status value (0 means successful, non-zero means failure),
+    /// and a string explanation of the outcome.
+    virtual isc::data::ConstElementPtr parse(isc::data::ConstElementPtr config,
+                                             bool check_only);
+
 private:
 private:
 
 
     /// @brief Parse a configuration element.
     /// @brief Parse a configuration element.

+ 1 - 1
src/lib/process/d_controller.cc

@@ -320,7 +320,7 @@ DControllerBase::runProcess() {
 // Instance method for handling new config
 // Instance method for handling new config
 isc::data::ConstElementPtr
 isc::data::ConstElementPtr
 DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
 DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
-    return (process_->configure(new_config));
+    return (process_->configure(new_config, false));
 }
 }
 
 
 
 

+ 3 - 2
src/lib/process/d_process.h

@@ -113,11 +113,12 @@ public:
     /// below.
     /// below.
     ///
     ///
     /// @param config_set a new configuration (JSON) for the process
     /// @param config_set a new configuration (JSON) for the process
+    /// @param check_only true if configuration is to be verified only, not applied
     /// @return an Element that contains the results of configuration composed
     /// @return an Element that contains the results of configuration composed
     /// of an integer status value (0 means successful, non-zero means failure),
     /// of an integer status value (0 means successful, non-zero means failure),
     /// and a string explanation of the outcome.
     /// and a string explanation of the outcome.
-    virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
+    virtual isc::data::ConstElementPtr
-                                                 config_set) = 0;
+    configure(isc::data::ConstElementPtr config_set, bool check_only) = 0;
 
 
     /// @brief Processes the given command.
     /// @brief Processes the given command.
     ///
     ///

+ 5 - 0
src/lib/process/process_messages.mes

@@ -28,6 +28,11 @@ to establish a session with the Kea control channel.
 A debug message listing the command (and possible arguments) received
 A debug message listing the command (and possible arguments) received
 from the Kea control system by the controller.
 from the Kea control system by the controller.
 
 
+% DCTL_CONFIG_CHECK_COMPLETE server has completed configuration check: %1, result: %2
+This is an informational message announcing the successful processing of a
+new configuration check is complete. The result of that check is printed.
+This informational message is printed when configuration check is requested.
+
 % DCTL_CONFIG_COMPLETE server has completed configuration: %1
 % DCTL_CONFIG_COMPLETE server has completed configuration: %1
 This is an informational message announcing the successful processing of a
 This is an informational message announcing the successful processing of a
 new configuration. It is output during server startup, and when an updated
 new configuration. It is output during server startup, and when an updated

+ 6 - 1
src/lib/process/testutils/d_test_stubs.cc

@@ -90,7 +90,12 @@ DStubProcess::shutdown(isc::data::ConstElementPtr /* args */) {
 }
 }
 
 
 isc::data::ConstElementPtr
 isc::data::ConstElementPtr
-DStubProcess::configure(isc::data::ConstElementPtr config_set) {
+DStubProcess::configure(isc::data::ConstElementPtr config_set, bool check_only) {
+    if (check_only) {
+        return (isc::config::createAnswer(1,
+                "Configuration checking is not supported."));
+    }
+
     if (SimFailure::shouldFailOn(SimFailure::ftProcessConfigure)) {
     if (SimFailure::shouldFailOn(SimFailure::ftProcessConfigure)) {
         // Simulates a process configure failure.
         // Simulates a process configure failure.
         return (isc::config::createAnswer(1,
         return (isc::config::createAnswer(1,

+ 3 - 2
src/lib/process/testutils/d_test_stubs.h

@@ -150,11 +150,12 @@ public:
     /// of the inbound configuration.
     /// of the inbound configuration.
     ///
     ///
     /// @param config_set a new configuration (JSON) for the process
     /// @param config_set a new configuration (JSON) for the process
+    /// @param check_only true if configuration is to be verified only, not applied
     /// @return an Element that contains the results of configuration composed
     /// @return an Element that contains the results of configuration composed
     /// of an integer status value (0 means successful, non-zero means failure),
     /// of an integer status value (0 means successful, non-zero means failure),
     /// and a string explanation of the outcome.
     /// and a string explanation of the outcome.
-    virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
+    virtual isc::data::ConstElementPtr
-                                                 config_set);
+    configure(isc::data::ConstElementPtr config_set, bool check_only);
 
 
     /// @brief Executes the given command.
     /// @brief Executes the given command.
     ///
     ///