#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>
#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
#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;
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
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
// 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
/// (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,
/// @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
/// 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
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;
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;
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;
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());
}
-void doRelatedLoggersTest(const char* input, const char* expected) {
- ConstElementPtr all_conf = isc::data::Element::fromJSON(input);
- ConstElementPtr expected_conf = isc::data::Element::fromJSON(expected);
- EXPECT_EQ(*expected_conf, *isc::config::getRelatedLoggers(all_conf));
-}
-
-TEST(LogConfigTest, relatedLoggersTest) {
- // make sure logger configs for 'other' programs are ignored,
- // and that * is substituted correctly
- // We'll use a root logger name of "kea-test".
- isc::log::setRootLoggerName("kea-test");
-
- doRelatedLoggersTest("[{ \"name\": \"other_module\" }]",
- "[]");
- doRelatedLoggersTest("[{ \"name\": \"other_module.somelib\" }]",
- "[]");
- doRelatedLoggersTest("[{ \"name\": \"test_other\" }]",
- "[]");
- doRelatedLoggersTest("[{ \"name\": \"test_other.somelib\" }]",
- "[]");
- doRelatedLoggersTest("[ { \"name\": \"other_module\" },"
- " { \"name\": \"test\" }]",
- "[ { \"name\": \"kea-test\" } ]");
- doRelatedLoggersTest("[ { \"name\": \"test\" }]",
- "[ { \"name\": \"kea-test\" } ]");
- doRelatedLoggersTest("[ { \"name\": \"test.somelib\" }]",
- "[ { \"name\": \"kea-test.somelib\" } ]");
- doRelatedLoggersTest("[ { \"name\": \"other_module.somelib\" },"
- " { \"name\": \"test.somelib\" }]",
- "[ { \"name\": \"kea-test.somelib\" } ]");
- doRelatedLoggersTest("[ { \"name\": \"other_module.somelib\" },"
- " { \"name\": \"test\" },"
- " { \"name\": \"test.somelib\" }]",
- "[ { \"name\": \"kea-test\" },"
- " { \"name\": \"kea-test.somelib\" } ]");
- doRelatedLoggersTest("[ { \"name\": \"*\" }]",
- "[ { \"name\": \"kea-test\" } ]");
- doRelatedLoggersTest("[ { \"name\": \"*.somelib\" }]",
- "[ { \"name\": \"kea-test.somelib\" } ]");
- doRelatedLoggersTest("[ { \"name\": \"*\", \"severity\": \"DEBUG\" },"
- " { \"name\": \"test\", \"severity\": \"WARN\"}]",
- "[ { \"name\": \"kea-test\", \"severity\": \"WARN\"} ]");
- doRelatedLoggersTest("[ { \"name\": \"*\", \"severity\": \"DEBUG\" },"
- " { \"name\": \"some_module\", \"severity\": \"WARN\"}]",
- "[ { \"name\": \"kea-test\", \"severity\": \"DEBUG\"} ]");
- doRelatedLoggersTest("[ { \"name\": \"kea-test\" }]",
- "[]");
- // make sure 'bad' things like '*foo.x' or '*lib' are ignored
- // (cfgmgr should have already caught it in the logconfig plugin
- // check, and is responsible for reporting the error)
- doRelatedLoggersTest("[ { \"name\": \"*foo\" }]",
- "[ ]");
- doRelatedLoggersTest("[ { \"name\": \"*foo.bar\" }]",
- "[ ]");
- doRelatedLoggersTest("[ { \"name\": \"*foo\" },"
- " { \"name\": \"*foo.lib\" },"
- " { \"name\": \"test\" } ]",
- "[ { \"name\": \"kea-test\" } ]");
-}
-
}