BIND 10 trac826, updated. 6f61d1e1efebe490973228a8c9da69634cc52632 libsession
BIND 10 source code commits
bind10-changes at lists.isc.org
Thu Jun 28 13:29:02 UTC 2012
The branch, trac826 has been updated
via 6f61d1e1efebe490973228a8c9da69634cc52632 (commit)
from 3aa50448b839c1a7ae8b5f39b66d6a61507579c9 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 6f61d1e1efebe490973228a8c9da69634cc52632
Author: Francis Dupont <fdupont at isc.org>
Date: Thu Jun 28 15:28:51 2012 +0200
libsession
-----------------------------------------------------------------------
Summary of changes:
WIN32-NOTES | 2 +-
src/lib/config/.gitignore | 2 +
src/lib/config/Makefile.am | 5 +
src/lib/config/ccsession.cc | 118 +++++++++
src/lib/config/ccsession.h | 180 +++++++++++++-
src/lib/config/config_data.h | 2 +-
src/lib/config/config_log.h | 11 +-
src/lib/config/config_messages.mes | 22 +-
src/lib/config/module_spec.cc | 22 +-
src/lib/config/module_spec.h | 12 +-
src/lib/config/tests/.gitignore | 2 +
src/lib/config/tests/Makefile.am | 3 +
src/lib/config/tests/ccsession_unittests.cc | 346 ++++++++++++++++++++++++++-
src/lib/config/tests/fake_session.cc | 17 +-
src/lib/config/tests/fake_session.h | 11 +-
src/lib/config/tests/testdata/.gitignore | 1 +
src/lib/config/tests/testdata/Makefile.am | 1 +
src/lib/config/tests/testdata/spec32.spec | 21 ++
src/lib/config/tests/testdata/spec39.spec | 21 ++
19 files changed, 748 insertions(+), 51 deletions(-)
create mode 100644 src/lib/config/.gitignore
create mode 100644 src/lib/config/tests/.gitignore
create mode 100644 src/lib/config/tests/testdata/.gitignore
create mode 100644 src/lib/config/tests/testdata/spec39.spec
-----------------------------------------------------------------------
diff --git a/WIN32-NOTES b/WIN32-NOTES
index 3403efe..dc2db24 100644
--- a/WIN32-NOTES
+++ b/WIN32-NOTES
@@ -29,7 +29,7 @@ What is needed:
- google test (aka gtest, got the 1.[56].0 sources but some recent
tests require >= 1.6.0)
- - boost >= 1.35 (got the 1.44/1.46 setup from Boostpro, installed
+ - boost >= 1.35 (got the 1.44/1.47 setup from Boostpro, installed
Multithread and Multithread Debug)
- setproctitle python module (in theory)
diff --git a/src/lib/config/.gitignore b/src/lib/config/.gitignore
new file mode 100644
index 0000000..c7ec9d3
--- /dev/null
+++ b/src/lib/config/.gitignore
@@ -0,0 +1,2 @@
+/config_messages.cc
+/config_messages.h
diff --git a/src/lib/config/Makefile.am b/src/lib/config/Makefile.am
index 500ff12..518d497 100644
--- a/src/lib/config/Makefile.am
+++ b/src/lib/config/Makefile.am
@@ -17,6 +17,11 @@ libcfgclient_la_SOURCES += module_spec.h module_spec.cc
libcfgclient_la_SOURCES += ccsession.cc ccsession.h
libcfgclient_la_SOURCES += config_log.h config_log.cc
+libcfgclient_la_LIBADD = $(top_builddir)/src/lib/cc/libcc.la
+libcfgclient_la_LIBADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+
+libcfgclient_la_LDFLAGS = -no-undefined -version-info 1:0:1
+
nodist_libcfgclient_la_SOURCES = config_messages.h config_messages.cc
# The message file should be in the distribution.
diff --git a/src/lib/config/ccsession.cc b/src/lib/config/ccsession.cc
index 0a6b637..6787609 100644
--- a/src/lib/config/ccsession.cc
+++ b/src/lib/config/ccsession.cc
@@ -493,6 +493,18 @@ ModuleCCSession::ModuleCCSession(
}
+ModuleCCSession::~ModuleCCSession() {
+ try {
+ sendStopping();
+ } catch (const std::exception& exc) {
+ LOG_ERROR(config_logger,
+ CONFIG_CCSESSION_STOPPING).arg(exc.what());
+ } catch (...) {
+ LOG_ERROR(config_logger,
+ CONFIG_CCSESSION_STOPPING_UNKNOWN);
+ }
+};
+
void
ModuleCCSession::start() {
if (started_) {
@@ -593,6 +605,11 @@ ModuleCCSession::checkCommand() {
ConstElementPtr cmd, routing, data;
if (session_.group_recvmsg(routing, data, true)) {
+ // In case the message is wanted asynchronously, it gets used.
+ if (checkAsyncRecv(routing, data)) {
+ return (0);
+ }
+
/* ignore result messages (in case we're out of sync, to prevent
* pingpongs */
if (data->getType() != Element::map || data->contains("result")) {
@@ -745,5 +762,106 @@ ModuleCCSession::updateRemoteConfig(const std::string& module_name,
}
}
+void
+ModuleCCSession::sendStopping() {
+ // Inform the configuration manager that this module is stopping
+ ConstElementPtr cmd(createCommand("stopping",
+ Element::fromJSON(
+ "{\"module_name\": \"" +
+ module_name_ + "\"}")));
+ // It's just an FYI, configmanager is not expected to respond.
+ session_.group_sendmsg(cmd, "ConfigManager");
+}
+
+class ModuleCCSession::AsyncRecvRequest {
+public: // Everything is public here, as the definition is hidden anyway
+ AsyncRecvRequest(const AsyncRecvCallback& cb, const string& rcp, int sq,
+ bool reply) :
+ callback(cb),
+ recipient(rcp),
+ seq(sq),
+ is_reply(reply)
+ {}
+ const AsyncRecvCallback callback;
+ const string recipient;
+ const int seq;
+ const bool is_reply;
+};
+
+ModuleCCSession::AsyncRecvRequestID
+ModuleCCSession::groupRecvMsgAsync(const AsyncRecvCallback& callback,
+ bool is_reply, int seq,
+ const string& recipient) {
+ // This just stores the request, the handling is done in checkCommand()
+
+ // push_back would be simpler, but it does not return the iterator we need
+ return (async_recv_requests_.insert(async_recv_requests_.end(),
+ AsyncRecvRequest(callback, recipient,
+ seq, is_reply)));
+}
+
+bool
+ModuleCCSession::checkAsyncRecv(const ConstElementPtr& envelope,
+ const ConstElementPtr& msg)
+{
+ for (AsyncRecvRequestID request(async_recv_requests_.begin());
+ request != async_recv_requests_.end(); ++request) {
+ // Just go through all the requests and look for a matching one
+ if (requestMatch(*request, envelope)) {
+ // We want the request to be still alive at the time we
+ // call the callback. But we need to remove it on an exception
+ // too, so we use the class. If just C++ had the finally keyword.
+ class RequestDeleter {
+ public:
+ RequestDeleter(AsyncRecvRequests& requests,
+ AsyncRecvRequestID& request) :
+ requests_(requests),
+ request_(request)
+ { }
+ ~RequestDeleter() {
+ requests_.erase(request_);
+ }
+ private:
+ AsyncRecvRequests& requests_;
+ AsyncRecvRequestID& request_;
+ };
+ RequestDeleter deleter(async_recv_requests_, request);
+ // Call the callback
+ request->callback(envelope, msg, request);
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+ModuleCCSession::requestMatch(const AsyncRecvRequest& request,
+ const ConstElementPtr& envelope) const
+{
+ if (request.is_reply != envelope->contains("reply")) {
+ // Wrong type of message
+ return (false);
+ }
+ if (request.is_reply &&
+ (request.seq == -1 ||
+ request.seq == envelope->get("reply")->intValue())) {
+ // This is the correct reply
+ return (true);
+ }
+ if (!request.is_reply &&
+ (request.recipient.empty() ||
+ request.recipient == envelope->get("group")->stringValue())) {
+ // This is the correct command
+ return (true);
+ }
+ // If nothing from the above, we don't want it
+ return (false);
+}
+
+void
+ModuleCCSession::cancelAsyncRecv(const AsyncRecvRequestID& id) {
+ async_recv_requests_.erase(id);
+}
+
}
}
diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h
index dc3fc35..e96a33d 100644
--- a/src/lib/config/ccsession.h
+++ b/src/lib/config/ccsession.h
@@ -15,13 +15,16 @@
#ifndef __CCSESSION_H
#define __CCSESSION_H 1
-#include <string>
-
#include <config/config_data.h>
#include <config/module_spec.h>
+
#include <cc/session.h>
#include <cc/data.h>
+#include <string>
+#include <list>
+#include <boost/function.hpp>
+
namespace isc {
namespace config {
@@ -192,6 +195,14 @@ public:
bool handle_logging = true
);
+ ///
+ /// Destructor
+ ///
+ /// The destructor automatically calls sendStopping(), which sends
+ /// a message to the ConfigManager that this module is stopping
+ ///
+ virtual ~ModuleCCSession();
+
/// Start receiving new commands and configuration changes asynchronously.
///
/// This method must be called only once, and only when the ModuleCCSession
@@ -315,15 +326,175 @@ public:
isc::data::ConstElementPtr getRemoteConfigValue(
const std::string& module_name,
const std::string& identifier) const;
-
+
+ /**
+ * Send a message to the underlying CC session.
+ * This has the same interface as isc::cc::Session::group_sendmsg()
+ *
+ * \param msg see isc::cc::Session::group_sendmsg()
+ * \param group see isc::cc::Session::group_sendmsg()
+ * \param instance see isc::cc::Session::group_sendmsg()
+ * \param to see isc::cc::Session::group_sendmsg()
+ * \return see isc::cc::Session::group_sendmsg()
+ */
+ int groupSendMsg(isc::data::ConstElementPtr msg,
+ std::string group,
+ std::string instance = "*",
+ std::string to = "*") {
+ return (session_.group_sendmsg(msg, group, instance, to));
+ };
+
+ /**
+ * Receive a message from the underlying CC session.
+ * This has the same interface as isc::cc::Session::group_recvmsg()
+ *
+ * \param envelope see isc::cc::Session::group_recvmsg()
+ * \param msg see isc::cc::Session::group_recvmsg()
+ * \param nonblock see isc::cc::Session::group_recvmsg()
+ * \param seq see isc::cc::Session::group_recvmsg()
+ * \return see isc::cc::Session::group_recvmsg()
+ */
+ bool groupRecvMsg(isc::data::ConstElementPtr& envelope,
+ isc::data::ConstElementPtr& msg,
+ bool nonblock = true,
+ int seq = -1) {
+ return (session_.group_recvmsg(envelope, msg, nonblock, seq));
+ };
+
+ /// \brief Forward declaration of internal data structure.
+ ///
+ /// This holds information about one asynchronous request to receive
+ /// a message. It is declared as public to allow declaring other derived
+ /// types, but without showing the internal representation.
+ class AsyncRecvRequest;
+
+ /// \brief List of all requests for asynchronous reads.
+ typedef std::list<AsyncRecvRequest> AsyncRecvRequests;
+
+ /// \brief Identifier of single request for asynchronous read.
+ typedef AsyncRecvRequests::iterator AsyncRecvRequestID;
+
+ /// \brief Callback which is called when an asynchronous receive finishes.
+ ///
+ /// This is the callback used by groupRecvMsgAsync() function. It is called
+ /// when a matching message arrives. It receives following parameters when
+ /// called:
+ /// - The envelope of the message
+ /// - The message itself
+ /// - The ID of the request, as returned by corresponding groupRecvMsgAsync
+ /// call.
+ ///
+ /// It is possible to throw exceptions from the callback, but they will not
+ /// be caught and they will get propagated out through the checkCommand()
+ /// call. This, if not handled on higher level, will likely terminate the
+ /// application. However, the ModuleCCSession internals will be in
+ /// well-defined state after the call (both the callback and the message
+ /// will be removed from the queues as already called).
+ typedef boost::function3<void, const isc::data::ConstElementPtr&,
+ const isc::data::ConstElementPtr&,
+ const AsyncRecvRequestID&>
+ AsyncRecvCallback;
+
+ /// \brief Receive a message from the CC session asynchronously.
+ ///
+ /// This registers a callback which is called when a matching message
+ /// is received. This message returns immediately.
+ ///
+ /// Once a matching message arrives, the callback is called with the
+ /// envelope of the message, the message itself and the result of this
+ /// function call (which might be useful for identifying which of many
+ /// events the recipient is waiting for this is). This makes the callback
+ /// used and is not called again even if a message that would match
+ /// arrives later (this is a single-shot callback).
+ ///
+ /// The callback is never called from within this function. Even if there
+ /// are queued messages, the callback would be called once checkCommand()
+ /// is invoked (possibly from start() or the constructor).
+ ///
+ /// The matching is as follows. If is_reply is true, only replies are
+ /// considered. In that case, if seq is -1, any reply is accepted. If
+ /// it is something else than -1, only the reply with matching seq is
+ /// taken. This may be used to receive replies to commands
+ /// asynchronously.
+ ///
+ /// In case the is_reply is false, the function looks for command messages.
+ /// The seq parameter is ignored, but the recipient one is considered. If
+ /// it is an empty string, any command is taken. If it is non-empty, only
+ /// commands addressed to the recipient channel (eg. group - instance is
+ /// ignored for now) are taken. This can be used to receive foreign commands
+ /// or notifications. In such case, it might be desirable to call the
+ /// groupRecvMsgAsync again from within the callback, to receive any future
+ /// commands or events of the same type.
+ ///
+ /// The interaction with other receiving functions is slightly complicated.
+ /// The groupRecvMsg call takes precedence. If the message matches its
+ /// parameters, it steals the message and no callback matching it as well
+ /// is called. Then, all the queued asynchronous receives are considered,
+ /// with the oldest active ones taking precedence (they work as FIFO).
+ /// If none of them matches, generic command and config handling takes
+ /// place. If it is not handled by that, the message is dropped. However,
+ /// it is better if there's just one place that wants to receive each given
+ /// message.
+ ///
+ /// \exception std::bad_alloc if there isn't enough memory to store the
+ /// callback.
+ /// \param callback is the function to be called when a matching message
+ /// arrives.
+ /// \param is_reply specifies if the desired message should be a reply or
+ /// a command.
+ /// \param seq specifies the reply sequence number in case a reply is
+ /// desired. The default -1 means any reply is OK.
+ /// \param recipient is the CC channel to which the command should be
+ /// addressed to match (in case is_reply is false). Empty means any
+ /// command is good one.
+ /// \return An identifier of the request. This will be passed to the
+ /// callback or can be used to cancel the request by cancelAsyncRecv.
+ /// \todo Decide what to do with instance and what was it meant for anyway.
+ AsyncRecvRequestID groupRecvMsgAsync(const AsyncRecvCallback& callback,
+ bool is_reply, int seq = -1,
+ const std::string& recipient =
+ std::string());
+
+ /// \brief Removes yet unused request for asynchronous receive.
+ ///
+ /// This function cancels a request previously queued by
+ /// groupRecvMsgAsync(). You may use it only before the callback was
+ /// already triggered. If you call it with an ID of callback that
+ /// already happened or was already canceled, the behaviour is undefined
+ /// (but something like a crash is very likely, as the function removes
+ /// an item from a list and this would be removing it from a list that
+ /// does not contain the item).
+ ///
+ /// It is important to cancel requests that are no longer going to happen
+ /// for some reason, as the request would occupy memory forever.
+ ///
+ /// \param id The id of request as returned by groupRecvMsgAsync.
+ void cancelAsyncRecv(const AsyncRecvRequestID& id);
+
private:
ModuleSpec readModuleSpecification(const std::string& filename);
void startCheck();
+ void sendStopping();
+ /// \brief Check if the message is wanted by asynchronous read
+ ///
+ /// It checks if any of the previously queued requests match
+ /// the message. If so, the callback is dispatched and removed.
+ ///
+ /// \param envelope The envelope of the message.
+ /// \param msg The actual message data.
+ /// \return True if the message was used for a callback, false
+ /// otherwise.
+ bool checkAsyncRecv(const data::ConstElementPtr& envelope,
+ const data::ConstElementPtr& msg);
+ /// \brief Checks if a message with this envelope matches the request
+ bool requestMatch(const AsyncRecvRequest& request,
+ const data::ConstElementPtr& envelope) const;
bool started_;
std::string module_name_;
isc::cc::AbstractSession& session_;
ModuleSpec module_specification_;
+ AsyncRecvRequests async_recv_requests_;
isc::data::ConstElementPtr handleConfigUpdate(
isc::data::ConstElementPtr new_config);
@@ -352,9 +523,6 @@ private:
isc::data::ConstElementPtr new_config);
ModuleSpec fetchRemoteSpec(const std::string& module, bool is_filename);
-
- // silence MSVC warning C4512: assignment operator could not be generated
- ModuleCCSession& operator=(ModuleCCSession const&);
};
/// \brief Default handler for logging config updates
diff --git a/src/lib/config/config_data.h b/src/lib/config/config_data.h
index 197d319..3fdbc25 100644
--- a/src/lib/config/config_data.h
+++ b/src/lib/config/config_data.h
@@ -32,7 +32,7 @@ public:
DataNotFoundError(const char* file, size_t line, const std::string& what) :
isc::Exception(file, line, what) {}
};
-
+
class ConfigData {
public:
/// Constructs a ConfigData option with no specification and an
diff --git a/src/lib/config/config_log.h b/src/lib/config/config_log.h
index 74e6a84..21709fd 100644
--- a/src/lib/config/config_log.h
+++ b/src/lib/config/config_log.h
@@ -30,15 +30,10 @@ namespace config {
/// Define the logger used to log messages. We could define it in multiple
/// modules, but defining in a single module and linking to it saves time and
/// space.
-extern isc::log::Logger config_logger; // isc::config::config_logger is the CONFIG logger
+extern isc::log::Logger config_logger;
-/// \brief Debug Levels
-///
-/// Debug levels used in the configuration library
-enum {
- DBG_CONFIG_PROCESS = 40 // Enumerate configuration elements as they
- // ... are processed.
-};
+// Enumerate configuration elements as they are processed.
+const int DBG_CONFIG_PROCESS = DBGLVL_TRACE_BASIC;
} // namespace config
} // namespace isc
diff --git a/src/lib/config/config_messages.mes b/src/lib/config/config_messages.mes
index c439edd..552256c 100644
--- a/src/lib/config/config_messages.mes
+++ b/src/lib/config/config_messages.mes
@@ -30,6 +30,18 @@ but will not send back an answer.
The most likely cause of this error is a programming error. Please raise
a bug report.
+% CONFIG_CCSESSION_STOPPING error sending stopping message: %1
+There was a problem when sending a message signaling that the module using
+this CCSession is stopping. This message is sent so that the rest of the
+system is aware that the module is no longer running. Apart from logging
+this message, the error itself is ignored, and the ModuleCCSession is
+still stopped. The specific exception message is printed.
+
+% CONFIG_CCSESSION_STOPPING_UNKNOWN unknown error sending stopping message
+Similar to CONFIG_CCSESSION_STOPPING, but in this case the exception that
+is seen is not a standard exception, and further information is unknown.
+This is a bug.
+
% CONFIG_GET_FAIL error getting configuration from cfgmgr: %1
The configuration manager returned an error when this module requested
the configuration. The full error message answer from the configuration
@@ -37,6 +49,11 @@ manager is appended to the log error. The most likely cause is that
the module is of a different (command specification) version than the
running configuration manager.
+% CONFIG_JSON_PARSE JSON parse error in %1: %2
+There was an error parsing the JSON file. The given file does not appear
+to be in valid JSON format. Please verify that the filename is correct
+and that the contents are valid JSON.
+
% CONFIG_LOG_EXPLICIT will use logging configuration for explicitly-named logger %1
This is a debug message. When processing the "loggers" part of the
configuration file, the configuration library found an entry for the named
@@ -62,11 +79,6 @@ wildcard entry (one containing the "*" character) that matches a logger
specification in the program. The logging configuration for the program
will be updated with the information.
-% CONFIG_JSON_PARSE JSON parse error in %1: %2
-There was an error parsing the JSON file. The given file does not appear
-to be in valid JSON format. Please verify that the filename is correct
-and that the contents are valid JSON.
-
% CONFIG_MOD_SPEC_FORMAT module specification error in %1: %2
The given file does not appear to be a valid specification file: details
are included in the message. Please verify that the filename is correct
diff --git a/src/lib/config/module_spec.cc b/src/lib/config/module_spec.cc
index 4f4ce14..e979b05 100644
--- a/src/lib/config/module_spec.cc
+++ b/src/lib/config/module_spec.cc
@@ -50,13 +50,14 @@ check_leaf_item(ConstElementPtr spec, const std::string& name,
if (spec->get(name)->getType() == type) {
return;
} else {
- throw ModuleSpecError(name + " not of type " + Element::typeToName(type));
+ isc_throw(ModuleSpecError,
+ name + " not of type " + Element::typeToName(type));
}
} else if (mandatory) {
// todo: want parent item name, and perhaps some info about location
// in list? or just catch and throw new...
// or make this part non-throwing and check return value...
- throw ModuleSpecError(name + " missing in " + spec->str());
+ isc_throw(ModuleSpecError, name + " missing in " + spec->str());
}
}
@@ -90,7 +91,7 @@ check_config_item(ConstElementPtr spec) {
void
check_config_item_list(ConstElementPtr spec) {
if (spec->getType() != Element::list) {
- throw ModuleSpecError("config_data is not a list of elements");
+ isc_throw(ModuleSpecError, "config_data is not a list of elements");
}
BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
check_config_item(item);
@@ -132,7 +133,7 @@ void check_statistics_item_list(ConstElementPtr spec);
void
check_statistics_item_list(ConstElementPtr spec) {
if (spec->getType() != Element::list) {
- throw ModuleSpecError("statistics is not a list of elements");
+ isc_throw(ModuleSpecError, "statistics is not a list of elements");
}
BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
check_config_item(item);
@@ -145,7 +146,7 @@ check_statistics_item_list(ConstElementPtr spec) {
&& item->contains("item_default")) {
if(!check_format(item->get("item_default"),
item->get("item_format"))) {
- throw ModuleSpecError(
+ isc_throw(ModuleSpecError,
"item_default not valid type of item_format");
}
}
@@ -162,7 +163,7 @@ check_command(ConstElementPtr spec) {
void
check_command_list(ConstElementPtr spec) {
if (spec->getType() != Element::list) {
- throw ModuleSpecError("commands is not a list of elements");
+ isc_throw(ModuleSpecError, "commands is not a list of elements");
}
BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
check_command(item);
@@ -193,7 +194,7 @@ check_module_specification(ConstElementPtr def) {
try {
check_data_specification(def);
} catch (const TypeError& te) {
- throw ModuleSpecError(te.what());
+ isc_throw(ModuleSpecError, te.what());
}
}
}
@@ -324,14 +325,14 @@ moduleSpecFromFile(const std::string& file_name, const bool check)
if (!file) {
std::stringstream errs;
errs << "Error opening " << file_name << ": " << strerror(errno);
- throw ModuleSpecError(errs.str());
+ isc_throw(ModuleSpecError, errs.str());
}
ConstElementPtr module_spec_element = Element::fromJSON(file, file_name);
if (module_spec_element->contains("module_spec")) {
return (ModuleSpec(module_spec_element->get("module_spec"), check));
} else {
- throw ModuleSpecError("No module_spec in specification");
+ isc_throw(ModuleSpecError, "No module_spec in specification");
}
}
@@ -343,7 +344,7 @@ moduleSpecFromFile(std::ifstream& in, const bool check)
if (module_spec_element->contains("module_spec")) {
return (ModuleSpec(module_spec_element->get("module_spec"), check));
} else {
- throw ModuleSpecError("No module_spec in specification");
+ isc_throw(ModuleSpecError, "No module_spec in specification");
}
}
@@ -476,7 +477,6 @@ ModuleSpec::validateSpecList(ConstElementPtr spec, ConstElementPtr data,
const bool full, ElementPtr errors) const
{
bool validated = true;
- std::string cur_item_name;
BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
if (!validateSpec(cur_spec_el, data, full, errors)) {
validated = false;
diff --git a/src/lib/config/module_spec.h b/src/lib/config/module_spec.h
index 6d02f45..5ad9c5e 100644
--- a/src/lib/config/module_spec.h
+++ b/src/lib/config/module_spec.h
@@ -31,15 +31,11 @@ namespace isc { namespace config {
/// A standard ModuleSpec exception that is thrown when a
/// specification is not in the correct form.
///
- /// TODO: use jinmei's exception class as a base and not c_str in
- /// what() there
- class ModuleSpecError : public std::exception {
+ class ModuleSpecError : public isc::Exception {
public:
- ModuleSpecError(std::string m = "Module specification is invalid") : msg(m) {}
- ~ModuleSpecError() throw() {}
- const char* what() const throw() { return (msg.c_str()); }
- private:
- std::string msg;
+ ModuleSpecError(const char* file, size_t line,
+ const char* what = "Module specification is invalid") :
+ isc::Exception(file, line, what) {}
};
///
diff --git a/src/lib/config/tests/.gitignore b/src/lib/config/tests/.gitignore
new file mode 100644
index 0000000..abdfa8a
--- /dev/null
+++ b/src/lib/config/tests/.gitignore
@@ -0,0 +1,2 @@
+/data_def_unittests_config.h
+/run_unittests
diff --git a/src/lib/config/tests/Makefile.am b/src/lib/config/tests/Makefile.am
index 2f1fc6f..ac1a547 100644
--- a/src/lib/config/tests/Makefile.am
+++ b/src/lib/config/tests/Makefile.am
@@ -14,6 +14,9 @@ CLEANFILES = *.gcno *.gcda
noinst_LTLIBRARIES = libfake_session.la
libfake_session_la_SOURCES = fake_session.h fake_session.cc
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
TESTS =
if HAVE_GTEST
TESTS += run_unittests
diff --git a/src/lib/config/tests/ccsession_unittests.cc b/src/lib/config/tests/ccsession_unittests.cc
index 793fa30..3fca741 100644
--- a/src/lib/config/tests/ccsession_unittests.cc
+++ b/src/lib/config/tests/ccsession_unittests.cc
@@ -26,10 +26,14 @@
#include <log/logger_name.h>
+#include <boost/scoped_ptr.hpp>
+#include <boost/bind.hpp>
+
using namespace isc::data;
using namespace isc::config;
using namespace isc::cc;
using namespace std;
+using namespace boost;
namespace {
std::string
@@ -190,6 +194,67 @@ TEST_F(CCSessionTest, session2) {
EXPECT_EQ(0, session.getMsgQueue()->size());
}
+TEST_F(CCSessionTest, session_close) {
+ // Test whether ModuleCCSession automatically sends a 'stopping'
+ // message when it is destroyed
+ ConstElementPtr msg;
+ std::string group, to;
+
+ EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
+
+ boost::scoped_ptr<ModuleCCSession> mccs(new ModuleCCSession(
+ ccspecfile("spec2.spec"),
+ session, NULL, NULL,
+ true, false));
+ EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
+ // The initial message is irrelevant for this test
+ // (see session2 test), drop it
+ session.getFirstMessage(group, to);
+ // Queue should now be empty
+ ASSERT_EQ(0, session.getMsgQueue()->size());
+ // Invoke the destructor
+ mccs.reset();
+ // Destructor should have caused a new message
+ ASSERT_EQ(1, session.getMsgQueue()->size());
+ msg = session.getFirstMessage(group, to);
+ EXPECT_EQ("{ \"command\": [ \"stopping\", "
+ "{ \"module_name\": \"Spec2\" } ] }", msg->str());
+ EXPECT_EQ("ConfigManager", group);
+ EXPECT_EQ("*", to);
+ EXPECT_EQ(0, session.getMsgQueue()->size());
+}
+
+TEST_F(CCSessionTest, session_close_exception) {
+ // Test whether an exception encountered during the destructor is
+ // handled correctly
+ ConstElementPtr msg;
+ std::string group, to;
+
+ EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
+
+ boost::scoped_ptr<ModuleCCSession> mccs(new ModuleCCSession(
+ ccspecfile("spec2.spec"),
+ session, NULL, NULL,
+ true, false));
+ EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
+ // The initial message is irrelevant for this test
+ // (see session2 test), drop it
+ session.getFirstMessage(group, to);
+ // Queue should now be empty
+ ASSERT_EQ(0, session.getMsgQueue()->size());
+
+ // Set fake session to throw an exception
+ session.setThrowOnSend(true);
+
+ // Invoke the destructor
+ mccs.reset();
+ // Destructor should not have caused a new message (since fakesession
+ // should have thrown an exception)
+ ASSERT_EQ(0, session.getMsgQueue()->size());
+ //EXPECT_EQ(0, session.getMsgQueue()->size());
+}
+
+
ConstElementPtr my_config_handler(ConstElementPtr new_config) {
if (new_config && new_config->contains("item1") &&
new_config->get("item1")->intValue() == 5) {
@@ -434,10 +499,10 @@ TEST_F(CCSessionTest, remoteConfig) {
const size_t qsize(session.getMsgQueue()->size());
EXPECT_TRUE(session.getMsgQueue()->get(qsize - 2)->equals(*el(
"[ \"ConfigManager\", \"*\", { \"command\": ["
- "\"get_module_spec\", { \"module_name\": \"Spec2\" } ] } ]")));
+ "\"get_module_spec\", { \"module_name\": \"Spec2\" } ] }, -1 ]")));
EXPECT_TRUE(session.getMsgQueue()->get(qsize - 1)->equals(*el(
"[ \"ConfigManager\", \"*\", { \"command\": [ \"get_config\","
- "{ \"module_name\": \"Spec2\" } ] } ]")));
+ "{ \"module_name\": \"Spec2\" } ] }, -1 ]")));
EXPECT_EQ("Spec2", module_name);
// Since we returned an empty local config above, the default value
// for "item1", which is 1, should be used.
@@ -646,13 +711,286 @@ TEST_F(CCSessionTest, doubleStartWithAddRemoteConfig) {
FakeSession::DoubleRead);
}
-namespace {
+/// \brief Test fixture for asynchronous receiving of messages.
+///
+/// This is an extension to the CCSessionTest. It would be possible to add
+/// the functionality to the CCSessionTest, but it is going to be used
+/// only by few tests and is non-trivial, so it is placed to a separate
+/// sub-class.
+class AsyncReceiveCCSessionTest : public CCSessionTest {
+protected:
+ AsyncReceiveCCSessionTest() :
+ mccs_(ccspecfile("spec29.spec"), session, NULL, NULL, false, false),
+ msg_(el("{\"result\": [0]}")),
+ next_flag_(0)
+ {
+ // This is just to make sure the messages get through the fake
+ // session.
+ session.subscribe("test group");
+ session.subscribe("other group");
+ session.subscribe("<ignored>");
+ // Get rid of all unrelated stray messages
+ while (session.getMsgQueue()->size() > 0) {
+ session.getMsgQueue()->remove(0);
+ }
+ }
+ /// \brief Convenience function to queue a request to get a command
+ /// message.
+ ModuleCCSession::AsyncRecvRequestID
+ registerCommand(const string& recipient)
+ {
+ return (mccs_.groupRecvMsgAsync(
+ bind(&AsyncReceiveCCSessionTest::callback, this, next_flag_ ++, _1,
+ _2, _3), false, -1, recipient));
+ }
+ /// \brief Convenience function to queue a request to get a reply
+ /// message.
+ ModuleCCSession::AsyncRecvRequestID
+ registerReply(int seq)
+ {
+ return (mccs_.groupRecvMsgAsync(
+ bind(&AsyncReceiveCCSessionTest::callback, this, next_flag_ ++, _1,
+ _2, _3), true, seq));
+ }
+ /// \brief Check the next called callback was with this flag
+ void called(int flag) {
+ ASSERT_FALSE(called_.empty());
+ EXPECT_EQ(flag, *called_.begin());
+ called_.pop_front();
+ }
+ /// \brief Checks that no more callbacks were called.
+ void nothingCalled() {
+ EXPECT_TRUE(called_.empty());
+ }
+ /// \brief The tested session.
+ ModuleCCSession mccs_;
+ /// \brief The value of message on the last called callback.
+ ConstElementPtr last_msg_;
+ /// \brief A message that can be used
+ ConstElementPtr msg_;
+ // Shared part of the simpleCommand and similar tests.
+ void commandTest(const string& group) {
+ // Push the message inside
+ session.addMessage(msg_, "test group", "<unused>");
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // Register the callback
+ registerCommand(group);
+ // But the callback should not be called yet
+ // (even if the message is there).
+ nothingCalled();
+ // But when we call the checkCommand(), it should be called.
+ mccs_.checkCommand();
+ called(0);
+ EXPECT_EQ(msg_, last_msg_);
+ // But only once
+ nothingCalled();
+ // And the message should be eaten
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ // The callback should have been eaten as well, inserting another
+ // message will not invoke it again
+ session.addMessage(msg_, "test group", "<unused>");
+ mccs_.checkCommand();
+ nothingCalled();
+ }
+ /// \brief Shared part of the simpleResponse and wildcardResponse tests.
+ void responseTest(int seq) {
+ // Push the message inside
+ session.addMessage(msg_, "<ignored>", "<unused>", 1);
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // Register the callback
+ registerReply(seq);
+ // But the callback should not be called yet
+ // (even if the message is there).
+ nothingCalled();
+ // But when we call the checkCommand(), it should be called.
+ mccs_.checkCommand();
+ called(0);
+ EXPECT_EQ(msg_, last_msg_);
+ // But only once
+ nothingCalled();
+ // And the message should be eaten
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ // The callback should have been eaten as well, inserting another
+ // message will not invoke it again
+ session.addMessage(msg_, "test group", "<unused>");
+ mccs_.checkCommand();
+ nothingCalled();
+ }
+ /// \brief Shared part of the noMatch* tests
+ void noMatchTest(int seq, int wanted_seq, bool is_reply) {
+ // Push the message inside
+ session.addMessage(msg_, "other group", "<unused>", seq);
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // Register the callback
+ if (is_reply) {
+ registerReply(wanted_seq);
+ } else {
+ registerCommand("test group");
+ }
+ // But the callback should not be called yet
+ // (even if the message is there).
+ nothingCalled();
+ // And even not now, because it does not match.
+ mccs_.checkCommand();
+ nothingCalled();
+ // And the message should be eaten by the checkCommand
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ }
+private:
+ /// \brief The next flag to be handed out
+ int next_flag_;
+ /// \brief Flags of callbacks already called (as FIFO)
+ list<int> called_;
+ /// \brief This is the callback registered to the tested groupRecvMsgAsync
+ /// function.
+ void callback(int store_flag, const ConstElementPtr&,
+ const ConstElementPtr& msg,
+ const ModuleCCSession::AsyncRecvRequestID&)
+ {
+ called_.push_back(store_flag);
+ last_msg_ = msg;
+ }
+};
+
+// Test we can receive a command, without anything fancy yet
+TEST_F(AsyncReceiveCCSessionTest, simpleCommand) {
+ commandTest("test group");
+}
+
+// Test we can receive a "wildcard" command - without specifying the
+// group to subscribe to. Very similar to simpleCommand test.
+TEST_F(AsyncReceiveCCSessionTest, wildcardCommand) {
+ commandTest("");
+}
+
+// Very similar to simpleCommand, but with a response message
+TEST_F(AsyncReceiveCCSessionTest, simpleResponse) {
+ responseTest(1);
+}
+
+// Matching a response message with wildcard
+TEST_F(AsyncReceiveCCSessionTest, wildcardResponse) {
+ responseTest(-1);
+}
+
+// Check that a wrong command message is not matched
+TEST_F(AsyncReceiveCCSessionTest, noMatchCommand) {
+ noMatchTest(-1, -1, false);
+}
+
+// Check that a wrong response message is not matched
+TEST_F(AsyncReceiveCCSessionTest, noMatchResponse) {
+ noMatchTest(2, 3, true);
+}
+
+// Check that a command will not match on a reply check and vice versa
+TEST_F(AsyncReceiveCCSessionTest, noMatchResponseAgainstCommand) {
+ // Send a command and check it is not matched as a response
+ noMatchTest(-1, -1, true);
+}
+
+TEST_F(AsyncReceiveCCSessionTest, noMatchCommandAgainstResponse) {
+ noMatchTest(2, -1, false);
+}
+
+// We check for command several times before the message actually arrives.
+TEST_F(AsyncReceiveCCSessionTest, delayedCallback) {
+ // First, register the callback
+ registerReply(1);
+ // And see it is not called, because the message is not there yet
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ for (size_t i(0); i < 100; ++ i) {
+ mccs_.checkCommand();
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ nothingCalled();
+ }
+ // Now the message finally arrives
+ session.addMessage(msg_, "<ignored>", "<unused>", 1);
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // And now, the callback is happily triggered.
+ mccs_.checkCommand();
+ called(0);
+ EXPECT_EQ(msg_, last_msg_);
+ // But only once
+ nothingCalled();
+}
+
+// See that if we put multiple messages inside, and request some callbacks,
+// the callbacks are called in the order of messages, not in the order they
+// were registered.
+TEST_F(AsyncReceiveCCSessionTest, outOfOrder) {
+ // First, put some messages there
+ session.addMessage(msg_, "<ignored>", "<unused>", 1);
+ session.addMessage(msg_, "test group", "<unused>");
+ session.addMessage(msg_, "other group", "<unused>");
+ session.addMessage(msg_, "<ignored>", "<unused>", 2);
+ session.addMessage(msg_, "<ignored>", "<unused>", 3);
+ session.addMessage(msg_, "<ignored>", "<unused>", 4);
+ // Now register some callbacks
+ registerReply(13); // Will not be called
+ registerCommand("other group"); // Matches 3rd message
+ registerReply(2); // Matches 4th message
+ registerCommand(""); // Matches the 2nd message
+ registerCommand("test group"); // Will not be called
+ registerReply(-1); // Matches the 1st message
+ registerReply(-1); // Matches the 5th message
+ // Process all messages there
+ while (mccs_.hasQueuedMsgs()) {
+ mccs_.checkCommand();
+ }
+ // These are the numbers of callbacks in the order of messages
+ called(5);
+ called(3);
+ called(1);
+ called(2);
+ called(6);
+ // The last message doesn't trigger anything, so nothing more is called
+ nothingCalled();
+}
+
+// We first add, then remove the callback again and check that nothing is
+// matched.
+TEST_F(AsyncReceiveCCSessionTest, cancel) {
+ // Add the callback
+ ModuleCCSession::AsyncRecvRequestID request(registerReply(1));
+ // Add corresponding message
+ session.addMessage(msg_, "<ignored>", "<unused>", 1);
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // And now, remove the callback again
+ mccs_.cancelAsyncRecv(request);
+ // And see that Nothing Happens(TM)
+ mccs_.checkCommand();
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ nothingCalled();
+}
+
+// We add multiple requests and cancel only one of them to see the rest
+// is unaffected.
+TEST_F(AsyncReceiveCCSessionTest, cancelSome) {
+ // Register few callbacks
+ registerReply(1);
+ ModuleCCSession::AsyncRecvRequestID request(registerCommand(""));
+ registerCommand("test group");
+ // Put some messages there
+ session.addMessage(msg_, "test group", "<unused>");
+ session.addMessage(msg_, "<ignored>", "<unused>", 1);
+ // Cancel the second callback. Therefore the first message will be matched
+ // by the third callback, not by the second.
+ mccs_.cancelAsyncRecv(request);
+ // Now, process the messages
+ mccs_.checkCommand();
+ mccs_.checkCommand();
+ // And see how they matched
+ called(2);
+ called(0);
+ nothingCalled();
+}
+
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));
}
-} // end anonymous namespace
TEST(LogConfigTest, relatedLoggersTest) {
// make sure logger configs for 'other' programs are ignored,
diff --git a/src/lib/config/tests/fake_session.cc b/src/lib/config/tests/fake_session.cc
index e884662..bf92b42 100644
--- a/src/lib/config/tests/fake_session.cc
+++ b/src/lib/config/tests/fake_session.cc
@@ -76,7 +76,8 @@ FakeSession::FakeSession(isc::data::ElementPtr initial_messages,
messages_(initial_messages),
subscriptions_(subscriptions),
msg_queue_(msg_queue),
- started_(false)
+ started_(false),
+ throw_on_send_(false)
{
}
@@ -142,6 +143,9 @@ FakeSession::recvmsg(ConstElementPtr& env, ConstElementPtr& msg, bool nonblock,
ElementPtr new_env = Element::createMap();
new_env->set("group", c_m->get(0));
new_env->set("to", c_m->get(1));
+ if (c_m->get(3)->intValue() != -1) {
+ new_env->set("reply", c_m->get(3));
+ }
env = new_env;
msg = c_m->get(2);
to_remove = c_m;
@@ -185,8 +189,9 @@ int
FakeSession::group_sendmsg(ConstElementPtr msg, std::string group,
std::string to, std::string)
{
- //cout << "[XX] client sends message: " << msg << endl;
- //cout << "[XX] to: " << group << " . " << instance << "." << to << endl;
+ if (throw_on_send_) {
+ isc_throw(Exception, "Throw on send is set in FakeSession");
+ }
addMessage(msg, group, to);
return (1);
}
@@ -209,7 +214,7 @@ FakeSession::reply(ConstElementPtr envelope, ConstElementPtr newmsg) {
bool
FakeSession::hasQueuedMsgs() const {
- return (false);
+ return (msg_queue_ && msg_queue_->size() > 0);
}
ConstElementPtr
@@ -230,12 +235,13 @@ FakeSession::getFirstMessage(std::string& group, std::string& to) const {
void
FakeSession::addMessage(ConstElementPtr msg, const std::string& group,
- const std::string& to)
+ const std::string& to, int seq)
{
ElementPtr m_el = Element::createList();
m_el->add(Element::create(group));
m_el->add(Element::create(to));
m_el->add(msg);
+ m_el->add(Element::create(seq));
if (!msg_queue_) {
msg_queue_ = Element::createList();
}
@@ -265,6 +271,5 @@ FakeSession::haveSubscription(ConstElementPtr group, ConstElementPtr instance)
{
return (haveSubscription(group->stringValue(), instance->stringValue()));
}
-
}
}
diff --git a/src/lib/config/tests/fake_session.h b/src/lib/config/tests/fake_session.h
index 85e47d5..c91b519 100644
--- a/src/lib/config/tests/fake_session.h
+++ b/src/lib/config/tests/fake_session.h
@@ -74,7 +74,7 @@ public:
isc::data::ConstElementPtr getFirstMessage(std::string& group,
std::string& to) const;
void addMessage(isc::data::ConstElementPtr, const std::string& group,
- const std::string& to);
+ const std::string& to, int seq = -1);
bool haveSubscription(const std::string& group,
const std::string& instance);
bool haveSubscription(const isc::data::ConstElementPtr group,
@@ -87,6 +87,14 @@ public:
isc::data::ElementPtr getMessages() { return (messages_); }
isc::data::ElementPtr getMsgQueue() { return (msg_queue_); }
+ /// Throw exception on sendmsg()
+ ///
+ /// When set to true, and sendmsg() is later called, this
+ /// will throw isc::Exception
+ ///
+ /// \param value If true, enable throw. If false, disable it
+ void setThrowOnSend(bool value) { throw_on_send_ = value; }
+
private:
bool recvmsg(isc::data::ConstElementPtr& msg,
bool nonblock = true, int seq = -1);
@@ -98,6 +106,7 @@ private:
isc::data::ElementPtr subscriptions_;
isc::data::ElementPtr msg_queue_;
bool started_;
+ bool throw_on_send_;
};
} // namespace cc
} // namespace isc
diff --git a/src/lib/config/tests/testdata/.gitignore b/src/lib/config/tests/testdata/.gitignore
new file mode 100644
index 0000000..1c67281
--- /dev/null
+++ b/src/lib/config/tests/testdata/.gitignore
@@ -0,0 +1 @@
+/b10-config.db
diff --git a/src/lib/config/tests/testdata/Makefile.am b/src/lib/config/tests/testdata/Makefile.am
index 0d8b92e..1bf9496 100644
--- a/src/lib/config/tests/testdata/Makefile.am
+++ b/src/lib/config/tests/testdata/Makefile.am
@@ -65,3 +65,4 @@ EXTRA_DIST += spec35.spec
EXTRA_DIST += spec36.spec
EXTRA_DIST += spec37.spec
EXTRA_DIST += spec38.spec
+EXTRA_DIST += spec39.spec
diff --git a/src/lib/config/tests/testdata/spec32.spec b/src/lib/config/tests/testdata/spec32.spec
index 68e774e..0d8cf7c 100644
--- a/src/lib/config/tests/testdata/spec32.spec
+++ b/src/lib/config/tests/testdata/spec32.spec
@@ -12,6 +12,27 @@
"item_optional": false,
"item_default": 3
}
+ },
+ { "item_name": "named_set_item2",
+ "item_type": "named_set",
+ "item_optional": true,
+ "item_default": { },
+ "named_set_item_spec": {
+ "item_name": "named_set_element",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "first",
+ "item_type": "integer",
+ "item_optional": true
+ },
+ { "item_name": "second",
+ "item_type": "string",
+ "item_optional": true
+ }
+ ]
+ }
}
]
}
diff --git a/src/lib/config/tests/testdata/spec39.spec b/src/lib/config/tests/testdata/spec39.spec
new file mode 100644
index 0000000..1f72319
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec39.spec
@@ -0,0 +1,21 @@
+{
+ "module_spec": {
+ "module_name": "Spec39",
+ "config_data": [
+ { "item_name": "list",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec": {
+ "item_name": "list_item",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": false
+ }
+ }
+ ],
+ "commands": [],
+ "statistics": []
+ }
+}
+
More information about the bind10-changes
mailing list