Commit 86da25a3 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[3796] Changes after review:

 - a lot of dead code removed from command_interpreter.cc|h|tests
 - unit-tests an el() function now have comments
 - comments improved (added for constants, cleaned up for functions)
 - copyright years updated
 - unnecessary includes removed
parent 4d6afc7a
......@@ -18,6 +18,7 @@
#include <gtest/gtest.h>
#include <config/command_interpreter.h>
#include <config/module_spec.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/json_config_parser.h>
#include <dhcp/option4_addrlst.h>
......
......@@ -15,6 +15,7 @@
#include <config.h>
#include <config/command_interpreter.h>
#include <config/module_spec.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_ia.h>
#include <dhcp/iface_mgr.h>
......
// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2009,2015 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -14,34 +14,10 @@
#include <config.h>
#include <stdexcept>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <ctype.h>
#include <algorithm>
#include <cerrno>
#include <fstream>
#include <iostream>
#include <set>
#include <sstream>
#include <string>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <cc/data.h>
#include <config/module_spec.h>
#include <exceptions/exceptions.h>
#include <config/config_log.h>
#include <config/command_interpreter.h>
#include <log/logger_support.h>
#include <log/logger_specification.h>
#include <log/logger_manager.h>
#include <log/logger_name.h>
#include <string>
#include <cc/data.h>
using namespace std;
......@@ -53,11 +29,10 @@ using isc::data::JSONError;
namespace isc {
namespace config {
const char *CONTROL_COMMAND="command";
const char *CONTROL_RESULT="result";
const char *CONTROL_TEXT="text";
const char *CONTROL_ARGUMENTS="arguments";
const char *CONTROL_COMMAND = "command";
const char *CONTROL_RESULT = "result";
const char *CONTROL_TEXT = "text";
const char *CONTROL_ARGUMENTS = "arguments";
// Full version, with status, text and arguments
ConstElementPtr
......@@ -167,241 +142,5 @@ parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
return (cmd->stringValue());
}
/// @todo: The code below should be reviewed whether it's still in use. If it is,
/// it should be moved to a separate file.
namespace {
// Temporary workaround functions for missing functionality in
// getValue() (main problem described in ticket #993)
// This returns either the value set for the given relative id,
// or its default value
// (intentionally defined here so this interface does not get
// included in ConfigData as it is)
ConstElementPtr getValueOrDefault(ConstElementPtr config_part,
const std::string& relative_id,
const ConfigData& config_data,
const std::string& full_id) {
if (config_part->contains(relative_id)) {
return config_part->get(relative_id);
} else {
return config_data.getDefaultValue(full_id);
}
}
/// @brief Prefix name with "kea-".
///
/// In BIND 10, modules had names taken from the .spec file, which are typically
/// names starting with a capital letter (e.g. "Resolver", "Auth" etc.). The
/// names of the associated binaries are derived from the module names, being
/// prefixed "b10-" and having the first letter of the module name lower-cased
/// (e.g. "b10-resolver", "b10-auth"). (It is a required convention that there
/// be this relationship between the names.)
///
/// In Kea we're not using module names, but we do still keep some capability to
/// run Kea servers in Bundy framework. For that reason the whole discussion here
/// applies only to case when Kea is compiled with Bundy configuration backend.
///
/// Within the binaries the root loggers are named after the binaries themselves.
/// (The reason for this is that the name of the logger is included in the
/// message logged, so making it clear which message comes from which Kea
/// process.) As logging is configured using module names, the configuration code
/// has to match these with the corresponding logger names. This function
/// converts a module name to a root logger name by lowercasing the first letter
/// of the module name and prepending "kea-".
///
/// \param instring String to convert. (This may be empty, in which case
/// "kea-" will be returned.)
///
/// \return Converted string.
std::string
keaPrefix(const std::string& instring) {
std::string result = instring;
if (!result.empty()) {
result[0] = tolower(result[0]);
}
return (std::string("kea-") + result);
}
// Reads a output_option subelement of a logger configuration,
// and sets the values thereing to the given OutputOption struct,
// or defaults values if they are not provided (from config_data).
void
readOutputOptionConf(isc::log::OutputOption& output_option,
ConstElementPtr output_option_el,
const ConfigData& config_data)
{
ConstElementPtr destination_el = getValueOrDefault(output_option_el,
"destination", config_data,
"loggers/output_options/destination");
output_option.destination = isc::log::getDestination(destination_el->stringValue());
ConstElementPtr output_el = getValueOrDefault(output_option_el,
"output", config_data,
"loggers/output_options/output");
if (output_option.destination == isc::log::OutputOption::DEST_CONSOLE) {
output_option.stream = isc::log::getStream(output_el->stringValue());
} else if (output_option.destination == isc::log::OutputOption::DEST_FILE) {
output_option.filename = output_el->stringValue();
} else if (output_option.destination == isc::log::OutputOption::DEST_SYSLOG) {
output_option.facility = output_el->stringValue();
}
output_option.flush = getValueOrDefault(output_option_el,
"flush", config_data,
"loggers/output_options/flush")->boolValue();
output_option.maxsize = getValueOrDefault(output_option_el,
"maxsize", config_data,
"loggers/output_options/maxsize")->intValue();
output_option.maxver = getValueOrDefault(output_option_el,
"maxver", config_data,
"loggers/output_options/maxver")->intValue();
}
// Reads a full 'loggers' configuration, and adds the loggers therein
// to the given vector, fills in blanks with defaults from config_data
void
readLoggersConf(std::vector<isc::log::LoggerSpecification>& specs,
ConstElementPtr logger,
const ConfigData& config_data)
{
// Read name, adding prefix as required.
std::string lname = logger->get("name")->stringValue();
ConstElementPtr severity_el = getValueOrDefault(logger,
"severity", config_data,
"loggers/severity");
isc::log::Severity severity = isc::log::getSeverity(
severity_el->stringValue());
int dbg_level = getValueOrDefault(logger, "debuglevel",
config_data,
"loggers/debuglevel")->intValue();
bool additive = getValueOrDefault(logger, "additive", config_data,
"loggers/additive")->boolValue();
isc::log::LoggerSpecification logger_spec(
lname, severity, dbg_level, additive
);
if (logger->contains("output_options")) {
BOOST_FOREACH(ConstElementPtr output_option_el,
logger->get("output_options")->listValue()) {
// create outputoptions
isc::log::OutputOption output_option;
readOutputOptionConf(output_option,
output_option_el,
config_data);
logger_spec.addOutputOption(output_option);
}
}
specs.push_back(logger_spec);
}
// Copies the map for a logger, changing the name of the logger in the process.
// This is used because the map being copied is "const", so in order to
// change the name we need to create a new one.
//
// \param cur_logger Logger being copied.
// \param new_name New value of the "name" element at the top level.
//
// \return Pointer to the map with the updated element.
ConstElementPtr
copyLogger(ConstElementPtr& cur_logger, const std::string& new_name) {
// Since we'll only be updating one first-level element and subsequent
// use won't change the contents of the map, a shallow map copy is enough.
ElementPtr new_logger(Element::createMap());
new_logger->setValue(cur_logger->mapValue());
new_logger->set("name", Element::create(new_name));
return (new_logger);
}
} // end anonymous namespace
ConstElementPtr
getRelatedLoggers(ConstElementPtr loggers) {
// Keep a list of names for easier lookup later
std::set<std::string> our_names;
const std::string& root_name = isc::log::getRootLoggerName();
ElementPtr result = isc::data::Element::createList();
BOOST_FOREACH(ConstElementPtr cur_logger, loggers->listValue()) {
// Need to add the kea- prefix to names ready from the spec file.
const std::string cur_name = cur_logger->get("name")->stringValue();
const std::string mod_name = keaPrefix(cur_name);
if (mod_name == root_name || mod_name.find(root_name + ".") == 0) {
// Note this name so that we don't add a wildcard that matches it.
our_names.insert(mod_name);
// We want to store the logger with the modified name (i.e. with
// the kea- prefix). As we are dealing with const loggers, we
// store a modified copy of the data.
result->add(copyLogger(cur_logger, mod_name));
LOG_DEBUG(config_logger, DBG_CONFIG_PROCESS, CONFIG_LOG_EXPLICIT)
.arg(cur_name);
} else if (!cur_name.empty() && (cur_name[0] != '*')) {
// Not a wildcard logger and we are ignoring it.
LOG_DEBUG(config_logger, DBG_CONFIG_PROCESS,
CONFIG_LOG_IGNORE_EXPLICIT).arg(cur_name);
}
}
// Now find the wildcard names (the one that start with "*").
BOOST_FOREACH(ConstElementPtr cur_logger, loggers->listValue()) {
const std::string cur_name = cur_logger->get("name")->stringValue();
// If name is '*', or starts with '*.', replace * with root
// logger name.
if (cur_name == "*" || (cur_name.length() > 1 &&
cur_name[0] == '*' && cur_name[1] == '.')) {
// Substitute the "*" with the root name
std::string mod_name = cur_name;
mod_name.replace(0, 1, root_name);
// Now add it to the result list, but only if a logger with
// that name was not configured explicitly.
if (our_names.find(mod_name) == our_names.end()) {
// We substitute the name here, but as we are dealing with
// consts, we need to copy the data.
result->add(copyLogger(cur_logger, mod_name));
LOG_DEBUG(config_logger, DBG_CONFIG_PROCESS,
CONFIG_LOG_WILD_MATCH).arg(cur_name);
} else if (!cur_name.empty() && (cur_name[0] == '*')) {
// Is a wildcard and we are ignoring it (because the wildcard
// expands to a specification that we already encountered when
// processing explicit names).
LOG_DEBUG(config_logger, DBG_CONFIG_PROCESS,
CONFIG_LOG_IGNORE_WILD).arg(cur_name);
}
}
}
return (result);
}
void
default_logconfig_handler(const std::string& module_name,
ConstElementPtr new_config,
const ConfigData& config_data) {
config_data.getModuleSpec().validateConfig(new_config, true);
std::vector<isc::log::LoggerSpecification> specs;
if (new_config->contains("loggers")) {
ConstElementPtr loggers = getRelatedLoggers(new_config->get("loggers"));
BOOST_FOREACH(ConstElementPtr logger,
loggers->listValue()) {
readLoggersConf(specs, logger, config_data);
}
}
isc::log::LoggerManager logger_manager;
logger_manager.process(specs.begin(), specs.end());
}
}
}
// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2009,2015 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -12,30 +12,48 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef CCSESSION_H
#define CCSESSION_H 1
#include <config/config_data.h>
#include <config/module_spec.h>
#ifndef COMMAND_INTERPRETER_H
#define COMMAND_INTERPRETER_H
#include <cc/data.h>
#include <string>
#include <list>
#include <boost/function.hpp>
/// @file command_interpreter.h
///
/// This file contains several functions and constants that are used for
/// handling commands and responses sent over control channel. The design
/// is described here: http://kea.isc.org/wiki/StatsDesign, but also
/// in @ref ctrlSocket section in the Developer's Guide.
namespace isc {
namespace config {
/// @brief String used for commands ("command")
extern const char *CONTROL_COMMAND;
/// @brief String used for result, i.e. integer status ("result")
extern const char *CONTROL_RESULT;
/// @brief String used for storing textual description ("text")
extern const char *CONTROL_TEXT;
/// @brief String used for arguments map ("arguments")
extern const char *CONTROL_ARGUMENTS;
/// @brief Status code indicating a successful operation
const int CONTROL_RESULT_SUCCESS = 0;
/// @brief Status code indicating a general failure
const int CONTROL_RESULT_ERROR = 1;
/// @brief A standard control channel exception that is thrown if a function
/// is there is a problem with one of the messages
class CtrlChannelError : public isc::Exception {
public:
CtrlChannelError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
/// @brief Creates a standard config/command level success answer message
/// (i.e. of the form { "result": 0 }
/// @return Standard command/config success answer message
......@@ -54,19 +72,16 @@ isc::data::ConstElementPtr createAnswer(const int status_code,
/// (i.e. of the form { "result": status_code, "arguments": arg }
///
/// @param status_code The return code (0 for success)
/// @param status_text A string to put into the "text" argument
/// @param arg argument (any data to be passed in the response, may be null)
/// @return Standard command/config answer message
isc::data::ConstElementPtr createAnswer(const int status_code,
const isc::data::ConstElementPtr& arg);
/// @brief Creates a standard config/command level answer message
/// (i.e. of the form { "result": X, "[ rcode, arg ] }
/// If rcode != 0, arg must be a StringElement
///
/// @param status_code The return code (0 for success)
/// @param arg For status_code == 0, this is an optional argument of any
/// Element type. For status_code == 1, this argument is mandatory,
/// and may be any type of ElementPtr.
/// @param status textual represenation of the status (used mostly for errors)
/// @param arg argument (any data to be passed in the response, may be null)
/// @return Standard command/config answer message
isc::data::ConstElementPtr createAnswer(const int status_code,
const std::string& status,
......@@ -77,14 +92,12 @@ isc::data::ConstElementPtr createAnswer(const int status_code,
/// @param status_code This value will be set to the return code contained in
/// the message
/// @param msg The message to parse
/// @return The optional argument in the message, or an empty ElementPtr
/// if there was no argument. If rcode != 0, this contains a
/// StringElement with the error description.
/// @return The optional argument in the message.
isc::data::ConstElementPtr parseAnswer(int &status_code,
const isc::data::ConstElementPtr& msg);
/// @brief Creates a standard config/command command message with no
/// argument (of the form { "command": "my_command" }
/// argument (of the form { "command": "my_command" })
///
/// @param command The command string
/// @return The created message
......@@ -109,117 +122,11 @@ isc::data::ConstElementPtr createCommand(const std::string& command,
/// the argument, or to an empty Map (ElementPtr) if there was none.
/// @param command The command message containing the command (as made
/// by createCommand()
/// \return The command name
/// @return The command name
std::string parseCommand(isc::data::ConstElementPtr& arg,
isc::data::ConstElementPtr command);
/// @brief A standard control channel exception that is thrown if a function
/// is there is a problem with one of the messages
class CtrlChannelError : public isc::Exception {
public:
CtrlChannelError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
/**
* Set a command handler; the function that is passed takes an
* ElementPtr, pointing to a list element, containing
* [ module_name, command_name, arg1, arg2, ... ]
* The returned ElementPtr should look like
* { "result": [ return_value, result_value ] }
* result value here is optional and depends on the command
*
* This protocol is very likely to change.
*/
void setCommandHandler(isc::data::ConstElementPtr(*command_handler)(
const std::string& command,
isc::data::ConstElementPtr args));
/**
* Gives access to the configuration values of a different module
* Once this function has been called with the name of the specification
* file or the module you want the configuration of, you can use
* \c getRemoteConfigValue() to get a specific setting.
* Changes are automatically updated, and you can specify handlers
* for those changes. This function will subscribe to the relevant module
* channel.
*
* This method must be called before calling the \c start() method on the
* ModuleCCSession (it also implies the ModuleCCSession must have been
* constructed with start_immediately being false).
*
* \param spec_name This specifies the module to add. It is either a
* filename of the spec file to use or a name of module
* (in case it's a module name, the spec data is
* downloaded from the configuration manager, therefore
* the configuration manager must know it). If
* spec_is_filename is true (the default), then a
* filename is assumed, otherwise a module name.
* \param handler The handler functor called whenever there's a change.
* Called once initially from this function. May be NULL
* if you don't want any handler to be called and you're
* fine with requesting the data through
* getRemoteConfigValue() each time.
*
* The handler should not throw, or it'll fall through and
* the exception will get into strange places, probably
* aborting the application.
* \param spec_is_filename Says if spec_name is filename or module name.
* \return The name of the module specified in the given specification
* file
*/
typedef boost::function<void(const std::string&,
isc::data::ConstElementPtr,
const ConfigData&)> RemoteHandler;
/// \brief Called when a notification comes
///
/// The callback should be exception-free. If it raises an exception,
/// it'll leak through the event loop up and probably terminate the
/// application.
///
/// \param event_name The identification of event type.
/// \param params The parameters of the event. This may be NULL
/// pointer in case no parameters were sent with the event.
typedef boost::function<void (const std::string& event_name,
const data::ConstElementPtr& params)>
NotificationCallback;
/// \brief Multiple notification callbacks for the same notification
typedef std::list<NotificationCallback> NotificationCallbacks;
/// \brief Returns the loggers related to this module
///
/// This function does two things;
/// - it drops the configuration parts for loggers for other modules.
/// - it replaces the '*' in the name of the loggers by the name of
/// this module, but *only* if the expanded name is not configured
/// explicitly.
///
/// Examples: if this is the module b10-resolver,
/// For the config names ['*', 'b10-auth']
/// The '*' is replaced with 'b10-resolver', and this logger is used.
/// 'b10-auth' is ignored (of course, it will not be in the b10-auth
/// module).
///
/// For ['*', 'b10-resolver']
/// The '*' is ignored, and only 'b10-resolver' is used.
///
/// For ['*.reslib', 'b10-resolver']
/// Or ['b10-resolver.reslib', '*']
/// Both are used, where the * will be expanded to b10-resolver
///
/// \note This is a public function at this time, but mostly for
/// the purposes of testing. Once we can directly test what loggers
/// are running, this function may be moved to the unnamed namespace
///
/// \param loggers the original 'loggers' config list
/// \return ListElement containing only loggers relevant for this
/// module, where * is replaced by the root logger name
isc::data::ConstElementPtr
getRelatedLoggers(isc::data::ConstElementPtr loggers);
}; // end of namespace isc::config
}; // end of namespace isc
#endif // CCSESSION_H
#endif // COMMAND_INTERPRETER_H
// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2009,2015 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -28,27 +28,43 @@ using namespace std;
namespace {
/// @brief Shortcut method for creating elements from JSON string
///
/// @param str string to be converted
/// @return Element structure
ElementPtr
el(const std::string& str) {
return (Element::fromJSON(str));
}
// This test verifies that that createAnswer method is able to generate
// various answers.
TEST(CommandInterpreterTest, createAnswer) {
ConstElementPtr answer;
// By default the answer is a successful one.
answer = createAnswer();
EXPECT_EQ("{ \"result\": 0 }", answer->str());
// Let's check if we can generate an error.
answer = createAnswer(1, "error");
EXPECT_EQ("{ \"result\": 1, \"text\": \"error\" }", answer->str());
// This is expected to throw. When status code is non-zero (indicating error),
// textual explanation is mandatory.
EXPECT_THROW(createAnswer(1, ElementPtr()), CtrlChannelError);
EXPECT_THROW(createAnswer(1, Element::create(1)), CtrlChannelError);
// Let's check if answer can be generate with some data in it.
ConstElementPtr arg = el("[ \"just\", \"some\", \"data\" ]");
answer = createAnswer(0, arg);
EXPECT_EQ("{ \"arguments\": [ \"just\", \"some\", \"data\" ], \"result\": 0 }",
answer->str());
}
// This test checks whether parseAnswer is able to handle well and malformed
// answers.
TEST(CommandInterpreterTest, parseAnswer) {
ConstElementPtr answer;
ConstElementPtr arg;
......@@ -79,6 +95,8 @@ TEST(CommandInterpreterTest, parseAnswer) {
EXPECT_EQ("[ \"just\", \"some\", \"data\" ]", arg->str());
}
// This test checks whether createCommand function is able to create commands
// with and without parameters.
TEST(CommandInterpreterTest, createCommand) {
ConstElementPtr command;
ConstElementPtr arg;
......@@ -102,6 +120,8 @@ TEST(CommandInterpreterTest, createCommand) {
command->str());
}
// This test checks whether parseCommand function is able to parse various valid
// and malformed commands.
TEST(CommandInterpreterTest, parseCommand) {
ConstElementPtr arg;
std::string cmd;
......@@ -124,71 +144,12 @@ TEST(CommandInterpreterTest, parseCommand) {
EXPECT_EQ("my_command", cmd);
EXPECT_EQ("1", arg->str());
parseCommand(arg, el("{ \"command\": \"my_command\", \"arguments\": [ \"some\", \"argument\", \"list\" ] }"));
parseCommand(arg, el("{ \"command\": \"my_command\", \"arguments\": "
"[ \"some\", \"argument\", \"list\" ] }"));
EXPECT_EQ("my_command", cmd);
ASSERT_TRUE(arg);
EXPECT_EQ("[ \"some\", \"argument\", \"list\" ]", arg->str());