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_log.cc ctrl_agent_log.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 += parser_context.cc parser_context.h parser_context_decl.h
 libagent_la_SOURCES += agent_lexer.ll

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

@@ -6,13 +6,23 @@
 
 #include <config.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::process;
+using namespace isc::data;
 
 namespace isc {
 namespace agent {
 
+CtrlAgentCfgContext::CtrlAgentCfgContext()
+    :http_host_(""), http_port_(0) {
+
+}
+
 CtrlAgentCfgMgr::CtrlAgentCfgMgr()
     : DCfgMgrBase(DCfgContextBasePtr(new CtrlAgentCfgContext())) {
 }
@@ -22,16 +32,47 @@ CtrlAgentCfgMgr::~CtrlAgentCfgMgr() {
 
 std::string
 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
-CtrlAgentCfgMgr::createConfigParser(const std::string& element_id,
+CtrlAgentCfgMgr::createConfigParser(const std::string& /*element_id*/,
                                     const isc::data::Element::Position& /*pos*/) {
-    // Create dummy parser, so as we don't return null pointer.
-    isc::dhcp::ParserPtr parser;
-    parser.reset(new Uint32Parser(element_id, getContext()->getUint32Storage()));
-    return (parser);
+    isc_throw(NotImplemented, "We don't use parser pointers anymore");
 }
 
 DCfgContextBasePtr
@@ -39,5 +80,53 @@ CtrlAgentCfgMgr::createNewContext() {
     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

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

@@ -7,8 +7,10 @@
 #ifndef CTRL_AGENT_CFG_MGR_H
 #define CTRL_AGENT_CFG_MGR_H
 
+#include <cc/data.h>
 #include <process/d_cfg_mgr.h>
 #include <boost/pointer_cast.hpp>
+#include <hooks/libinfo.h>
 
 namespace isc {
 namespace agent {
@@ -26,6 +28,20 @@ typedef boost::shared_ptr<CtrlAgentCfgContext> CtrlAgentCfgContextPtr;
 /// It is derived from the context base class, DCfgContextBase.
 class CtrlAgentCfgContext : public process::DCfgContextBase {
 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.
     ///
     /// @return A pointer to the new clone.
@@ -33,9 +49,84 @@ public:
         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:
     /// @brief Private assignment operator to avoid potential for slicing.
     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.
@@ -69,20 +160,16 @@ public:
 
 protected:
 
-    /// @brief  Create a parser instance based on an element id.
-    ///
-    /// Given an element_id returns an instance of the appropriate parser.
-    ///
-    /// @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.
+    virtual isc::data::ConstElementPtr
+    parse(isc::data::ConstElementPtr config, bool check_only);
+
+    /// @brief This is no longer used.
     ///
-    /// @return returns a ParserPtr to the parser instance.
+    /// @throw NotImplemented
+    /// @return nothing, always throws
     virtual isc::dhcp::ParserPtr
-    createConfigParser(const std::string& element_id,
-                       const isc::data::Element::Position& pos
-                       = isc::data::Element::ZERO_POSITION());
+    createConfigParser(const std::string&,
+                       const isc::data::Element::Position& pos);
 
     /// @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
 processed all configuration information and is ready to begin processing.
 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
-CtrlAgentProcess::configure(isc::data::ConstElementPtr config_set) {
+CtrlAgentProcess::configure(isc::data::ConstElementPtr config_set,
+                            bool check_only) {
     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);
     return (answer);
 }

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

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

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

@@ -94,7 +94,7 @@ public:
             return res;
         }
 
-        isc::data::ConstElementPtr answer = configure(config_set_);
+        isc::data::ConstElementPtr answer = configure(config_set_, false);
         isc::data::ConstElementPtr comment;
         comment = isc::config::parseAnswer(rcode, answer);
 
@@ -181,7 +181,7 @@ TEST_F(D2ProcessTest, configure) {
     ASSERT_TRUE(fromJSON(valid_d2_config));
 
     // 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
     // flag is true.
@@ -199,7 +199,7 @@ TEST_F(D2ProcessTest, configure) {
     ASSERT_TRUE(fromJSON("{ \"bogus\": 1000 } "));
 
     // 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
     // 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
     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
     // flag is true.
@@ -377,7 +377,7 @@ TEST_F(D2ProcessTest, badConfigureRecovery) {
 
     // Verify we can recover given a valid config with an usable IP address.
     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
     // 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);
 }
 
+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
 DCfgMgrBase::buildParams(isc::data::ConstElementPtr params_config) {
     // Loop through scalars parsing them and committing them to storage.
@@ -288,6 +344,10 @@ void DCfgMgrBase::buildAndCommit(std::string& element_id,
     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 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
 /// application management layers.
 ///
+/// This class allows two configuration methods:
+///
+/// 1. classic method
+///
 /// The class presents a public method for receiving new configurations,
 /// 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
 /// 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 {
 public:
     /// @brief Constructor
@@ -272,7 +295,7 @@ public:
     /// @brief Acts as the receiver of new configurations and coordinates
     /// 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
     /// of an integer status value (0 means successful, non-zero means failure),
@@ -280,6 +303,27 @@ public:
     isc::data::ConstElementPtr parseConfig(isc::data::ConstElementPtr
                                            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.
     ///
     /// The order in which object elements are retrieved from this is the
@@ -373,6 +417,24 @@ protected:
     /// @throw DCfgMgrBaseError if context is NULL.
     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:
 
     /// @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
 isc::data::ConstElementPtr
 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.
     ///
     /// @param config_set a new configuration (JSON) for the process
+    /// @param check_only true if configuration is to be verified only, not applied
     /// @return an Element that contains the results of configuration composed
     /// of an integer status value (0 means successful, non-zero means failure),
     /// and a string explanation of the outcome.
-    virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
-                                                 config_set) = 0;
+    virtual isc::data::ConstElementPtr
+    configure(isc::data::ConstElementPtr config_set, bool check_only) = 0;
 
     /// @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
 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
 This is an informational message announcing the successful processing of a
 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
-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)) {
         // Simulates a process configure failure.
         return (isc::config::createAnswer(1,

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

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