From f88448a8c7555fec1e7c7aeb7a90d95553c96ee1 Mon Sep 17 00:00:00 2001 From: Tomek Mrugalski Date: Thu, 16 Feb 2017 02:01:31 +0100 Subject: [PATCH] [5134] Configuration parsing implemented in CA --- src/bin/agent/Makefile.am | 1 + src/bin/agent/ctrl_agent_cfg_mgr.cc | 101 +++++++++++++++-- src/bin/agent/ctrl_agent_cfg_mgr.h | 111 ++++++++++++++++--- src/bin/agent/ctrl_agent_messages.mes | 18 ++++ src/bin/agent/ctrl_agent_process.cc | 6 +- src/bin/agent/ctrl_agent_process.h | 3 +- src/bin/agent/simple_parser.cc | 125 ++++++++++++++++++++++ src/bin/agent/simple_parser.h | 49 +++++++++ src/bin/d2/d2_process.cc | 7 +- src/bin/d2/d2_process.h | 5 +- src/bin/d2/tests/d2_process_unittests.cc | 10 +- src/lib/process/d_cfg_mgr.cc | 62 ++++++++++- src/lib/process/d_cfg_mgr.h | 64 ++++++++++- src/lib/process/d_controller.cc | 2 +- src/lib/process/d_process.h | 5 +- src/lib/process/process_messages.mes | 5 + src/lib/process/testutils/d_test_stubs.cc | 7 +- src/lib/process/testutils/d_test_stubs.h | 5 +- 18 files changed, 549 insertions(+), 37 deletions(-) create mode 100644 src/bin/agent/simple_parser.cc create mode 100644 src/bin/agent/simple_parser.h diff --git a/src/bin/agent/Makefile.am b/src/bin/agent/Makefile.am index f5f11e194e..b30b02291e 100644 --- a/src/bin/agent/Makefile.am +++ b/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 diff --git a/src/bin/agent/ctrl_agent_cfg_mgr.cc b/src/bin/agent/ctrl_agent_cfg_mgr.cc index 87f9f0c87c..a7430f64f7 100644 --- a/src/bin/agent/ctrl_agent_cfg_mgr.cc +++ b/src/bin/agent/ctrl_agent_cfg_mgr.cc @@ -6,13 +6,23 @@ #include #include +#include +#include +#include +#include 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(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 diff --git a/src/bin/agent/ctrl_agent_cfg_mgr.h b/src/bin/agent/ctrl_agent_cfg_mgr.h index ebe9ccefcc..166077ecf0 100644 --- a/src/bin/agent/ctrl_agent_cfg_mgr.h +++ b/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 #include #include +#include namespace isc { namespace agent { @@ -26,6 +28,20 @@ typedef boost::shared_ptr 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(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(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. /// diff --git a/src/bin/agent/ctrl_agent_messages.mes b/src/bin/agent/ctrl_agent_messages.mes index db6d17e9b1..ed3bc16aa8 100644 --- a/src/bin/agent/ctrl_agent_messages.mes +++ b/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. diff --git a/src/bin/agent/ctrl_agent_process.cc b/src/bin/agent/ctrl_agent_process.cc index 51e2377f2b..162e83ee94 100644 --- a/src/bin/agent/ctrl_agent_process.cc +++ b/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); } diff --git a/src/bin/agent/ctrl_agent_process.h b/src/bin/agent/ctrl_agent_process.h index 05f54c30c7..43d61d474e 100644 --- a/src/bin/agent/ctrl_agent_process.h +++ b/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. /// diff --git a/src/bin/agent/simple_parser.cc b/src/bin/agent/simple_parser.cc new file mode 100644 index 0000000000..9363530aee --- /dev/null +++ b/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 +#include +#include +#include +#include + +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(sockets->get("d2-server")); + if (d2) { + cnt += SimpleParser::setDefaults(d2, SOCKET_DEFAULTS); + } + + ElementPtr d4 = boost::const_pointer_cast(sockets->get("dhcp4-server")); + if (d4) { + cnt += SimpleParser::setDefaults(d4, SOCKET_DEFAULTS); + } + + ElementPtr d6 = boost::const_pointer_cast(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(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(); + } +} + +}; +}; diff --git a/src/bin/agent/simple_parser.h b/src/bin/agent/simple_parser.h new file mode 100644 index 0000000000..d7bf236c0e --- /dev/null +++ b/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 +#include + +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 diff --git a/src/bin/d2/d2_process.cc b/src/bin/d2/d2_process.cc index e4089024c5..05cd038df7 100644 --- a/src/bin/d2/d2_process.cc +++ b/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);; diff --git a/src/bin/d2/d2_process.h b/src/bin/d2/d2_process.h index cddc67df6f..b053083bb4 100644 --- a/src/bin/d2/d2_process.h +++ b/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. /// diff --git a/src/bin/d2/tests/d2_process_unittests.cc b/src/bin/d2/tests/d2_process_unittests.cc index f5271a885e..b08abc54bb 100644 --- a/src/bin/d2/tests/d2_process_unittests.cc +++ b/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. diff --git a/src/lib/process/d_cfg_mgr.cc b/src/lib/process/d_cfg_mgr.cc index 21475912c1..5ba2e6e22a 100644 --- a/src/lib/process/d_cfg_mgr.cc +++ b/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 - diff --git a/src/lib/process/d_cfg_mgr.h b/src/lib/process/d_cfg_mgr.h index 34453ccadf..fe7f96f352 100644 --- a/src/lib/process/d_cfg_mgr.h +++ b/src/lib/process/d_cfg_mgr.h @@ -210,6 +210,10 @@ typedef std::vector 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 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. diff --git a/src/lib/process/d_controller.cc b/src/lib/process/d_controller.cc index 851b97fe15..28e36a606c 100644 --- a/src/lib/process/d_controller.cc +++ b/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)); } diff --git a/src/lib/process/d_process.h b/src/lib/process/d_process.h index aae2612de9..6a4e53cd7b 100644 --- a/src/lib/process/d_process.h +++ b/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. /// diff --git a/src/lib/process/process_messages.mes b/src/lib/process/process_messages.mes index dbb269778c..c405fed980 100644 --- a/src/lib/process/process_messages.mes +++ b/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 diff --git a/src/lib/process/testutils/d_test_stubs.cc b/src/lib/process/testutils/d_test_stubs.cc index c94725a5e5..d6ceac68bc 100644 --- a/src/lib/process/testutils/d_test_stubs.cc +++ b/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, diff --git a/src/lib/process/testutils/d_test_stubs.h b/src/lib/process/testutils/d_test_stubs.h index cca69a5242..44d9792ecd 100644 --- a/src/lib/process/testutils/d_test_stubs.h +++ b/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. /// -- 2.47.3