Commit f88448a8 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[5134] Configuration parsing implemented in CA

parent 4649117b
......@@ -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
......
......@@ -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
......@@ -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,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.
......@@ -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);
}
......
......@@ -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.
///
......
// 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();
}
}
};
};
// 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
......@@ -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);;
......
......@@ -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.
///
......
......@@ -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.
......
......@@ -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();