#include <cc/command_interpreter.h>
#include <cc/data.h>
#include <boost/pointer_cast.hpp>
+#include <iterator>
#include <string>
#include <vector>
using namespace isc::asiolink;
using namespace isc::config;
using namespace isc::data;
+using namespace isc::hooks;
using namespace isc::process;
namespace isc {
CtrlAgentCommandMgr::handleCommand(const std::string& cmd_name,
const isc::data::ConstElementPtr& params,
const isc::data::ConstElementPtr& original_cmd) {
- ConstElementPtr answer;
+ ConstElementPtr answer = handleCommandInternal(cmd_name, params, original_cmd);
- try {
- // list-commands is a special case. The Control Agent always supports this
- // command but most of the time users don't want to list commands supported
- // by the CA but by one of the Kea servers. The user would indicate that
- // by specifying 'service' value.
- if (cmd_name == "list-commands") {
- if (original_cmd && original_cmd->contains("service")) {
- ConstElementPtr services = original_cmd->get("service");
- if (services && !services->empty()) {
- // The non-empty control command 'service' parameter exists which
- // means we will forward this command to the Kea server. Let's
- // cheat that Control Agent doesn't support this command to
- // avoid it being handled by CA.
- answer = createAnswer(CONTROL_RESULT_COMMAND_UNSUPPORTED,
- "forwarding list-commands command");
- }
- }
+ if (answer->getType() == Element::list) {
+ return (answer);
+ }
+
+ // In general, the handlers should return a list of answers rather than a
+ // single answer, but in some cases we rely on the generic handlers,
+ // e.g. 'list-commands', which may return a single answer not wrapped in
+ // the list. Such answers need to be wrapped in the list here.
+ ElementPtr answer_list = Element::createList();
+ answer_list->add(boost::const_pointer_cast<Element>(answer));
+
+ return (answer_list);
+}
+
+
+ConstElementPtr
+CtrlAgentCommandMgr::handleCommandInternal(std::string cmd_name,
+ isc::data::ConstElementPtr params,
+ isc::data::ConstElementPtr original_cmd) {
+
+ ConstElementPtr services = Element::createList();
+
+ // Retrieve 'service' parameter to determine if we should forward the
+ // command or handle it on our own.
+ if (original_cmd && original_cmd->contains("service")) {
+ services = original_cmd->get("service");
+ // If 'service' value is not a list, this is a fatal error. We don't want
+ // to try processing commands that don't adhere to the required format.
+ if (services->getType() != Element::list) {
+ return (createAnswer(CONTROL_RESULT_ERROR, "service value must be a list"));
}
- } catch (const std::exception& ex) {
- answer = createAnswer(CONTROL_RESULT_ERROR, "invalid service parameter value: "
- + std::string(ex.what()));
}
- if (!answer) {
- // Try handling this command on our own.
- answer = HookedCommandMgr::handleCommand(cmd_name, params, original_cmd);
+ // 'service' parameter hasn't been specified which indicates that the command
+ // is intended to be processed by the CA. The following command will try to
+ // process the command with hooks libraries (if available) or by one of the
+ // CA's native handlers.
+ if (services->empty()) {
+ return (HookedCommandMgr::handleCommand(cmd_name, params, original_cmd));
}
- int rcode = 0;
- static_cast<void>(parseAnswer(rcode, answer));
+ ElementPtr answer_list = Element::createList();
- // We have tried handling the command on our own but it seems that neither
- // the Control Agent nor a hook library can handle this command. We need
- // to try forwarding the command to one of the Kea servers.
- if (original_cmd && (rcode == CONTROL_RESULT_COMMAND_UNSUPPORTED)) {
- try {
- answer = tryForwardCommand(cmd_name, original_cmd);
+ // Before the command is forwarded it should be processed by the hooks libraries.
+ if (HookedCommandMgr::delegateCommandToHookLibrary(cmd_name, params, original_cmd,
+ answer_list)) {
+ // If the hooks libraries set the 'skip' flag, they indicate that the
+ // commands have been processed. The answer_list should contain the list
+ // of answers with each answer pertaining to one service.
+ if (callout_handle_->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
+ LOG_DEBUG(agent_logger, isc::log::DBGLVL_COMMAND,
+ CTRL_AGENT_COMMAND_PROCESS_SKIP)
+ .arg(cmd_name);
+ return (answer_list);
+ }
+ }
- } catch (const CommandForwardingError& ex) {
- // This is apparently some configuration error or client's error.
- // We have notify the client.
- answer = createAnswer(CONTROL_RESULT_ERROR, ex.what());
+ // We don't know whether the hooks libraries modified the value of the
+ // answer list, so let's be safe and re-create the answer_list.
+ answer_list = Element::createList();
+
+ // For each value within 'service' we have to try forwarding the command.
+ for (unsigned i = 0; i < services->size(); ++i) {
+ if (original_cmd) {
+ ConstElementPtr answer;
+ try {
+ LOG_DEBUG(agent_logger, isc::log::DBGLVL_COMMAND,
+ CTRL_AGENT_COMMAND_FORWARD_BEGIN)
+ .arg(cmd_name).arg(services->get(i)->stringValue());
+
+ answer = forwardCommand(services->get(i)->stringValue(),
+ cmd_name, original_cmd);
+
+ } catch (const CommandForwardingError& ex) {
+ LOG_DEBUG(agent_logger, isc::log::DBGLVL_COMMAND,
+ CTRL_AGENT_COMMAND_FORWARD_FAILED)
+ .arg(cmd_name).arg(ex.what());
+ answer = createAnswer(CONTROL_RESULT_ERROR, ex.what());
+ }
- } catch (const CommandForwardingSkip& ex) {
- // Command is not intended to be forwarded so do nothing.
+ answer_list->add(boost::const_pointer_cast<Element>(answer));
}
}
- // We have a response, so let's wrap it in the list.
- ElementPtr answer_list = Element::createList();
- answer_list->add(boost::const_pointer_cast<Element>(answer));
-
return (answer_list);
}
ConstElementPtr
-CtrlAgentCommandMgr::tryForwardCommand(const std::string& cmd_name,
- const isc::data::ConstElementPtr& command) {
+CtrlAgentCommandMgr::forwardCommand(const std::string& service,
+ const std::string& cmd_name,
+ const isc::data::ConstElementPtr& command) {
// Context will hold the server configuration.
CtrlAgentCfgContextPtr ctx;
" Control Agent configuration information");
}
- // If the service is not specified it means that the Control Agent is the
- // intended receiver of this message. This is not a fatal error, we simply
- // skip forwarding the command and rely on the internal logic of the
- // Control Agent to generate response.
- ConstElementPtr service_elements = command->get("service");
- if (!service_elements) {
- isc_throw(CommandForwardingSkip, "service parameter not specified");
- }
-
- // If the service exists it must be a list, even though we currently allow
- // only one service.
- std::vector<ElementPtr> service_vec;
- try {
- service_vec = service_elements->listValue();
-
- } catch (const std::exception& ex) {
- isc_throw(CommandForwardingError, "service parameter is not a list");
- }
-
- // service list may be empty in which case we treat it as it is not specified.
- if (service_vec.empty()) {
- isc_throw(CommandForwardingSkip, "service parameter is empty");
- }
-
- // Do not allow more than one service value. This will be allowed in the
- // future.
- if (service_vec.size() > 1) {
- isc_throw(CommandForwardingError, "service parameter must contain 0 or 1"
- " service value");
- }
-
// Convert the service to the server type values. Make sure the client
// provided right value.
CtrlAgentCfgContext::ServerType server_type;
try {
- server_type = CtrlAgentCfgContext::toServerType(service_vec.at(0)->stringValue());
+ server_type = CtrlAgentCfgContext::toServerType(service);
} catch (const std::exception& ex) {
// Invalid value in service list. Can't proceed.
ConstElementPtr socket_info = ctx->getControlSocketInfo(server_type);
if (!socket_info) {
isc_throw(CommandForwardingError, "forwarding socket is not configured"
- " for the server type " << service_vec.at(0)->stringValue());
+ " for the server type " << service);
}
// If the configuration does its job properly the socket-name must be
unix_socket.write(&wire_command[0], wire_command.size());
receive_len = unix_socket.receive(&receive_buf_[0], receive_buf_.size());
- } catch (...) {
+ } catch (const std::exception& ex) {
isc_throw(CommandForwardingError, "unable to forward command to the "
- + service_vec.at(0)->stringValue() + " service. The server "
+ << service << " service: " << ex.what() << ". The server "
"is likely to be offline");
}
answer = Element::fromJSON(reply);
LOG_INFO(agent_logger, CTRL_AGENT_COMMAND_FORWARDED)
- .arg(cmd_name)
- .arg(service_vec.at(0)->stringValue());
+ .arg(cmd_name).arg(service);
} catch (const std::exception& ex) {
isc_throw(CommandForwardingError, "internal server error: unable to parse"
isc::Exception(file, line, what) { };
};
-/// @brief Exception thrown when command forwarding has been skipped.
-class CommandForwardingSkip : public Exception {
-public:
- CommandForwardingSkip(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) { };
-};
-
/// @brief Command Manager for Control Agent.
///
/// This is an implementation of the Command Manager within Control Agent.
/// @brief Handles the command having a given name and arguments.
///
/// This method extends the base implementation with the ability to forward
- /// commands to Kea servers if the Control Agent failed to handle it itself.
+ /// commands to Kea servers.
///
- /// @todo Currently this method only wraps an answer within a list Element.
- /// This will be later used to include multiple answers within this list.
- /// For now it is just a single answer from the Control Agent.
+ /// If the received command doesn't include 'service' parameter or this
+ /// parameter is blank, the command is handled by the Control Agent or the
+ /// attached hooks libraries.
+ ///
+ /// If the non-blank 'service' parameter has been specified the callouts
+ /// are executed. If the callouts process the command the result is returned
+ /// to the controlling client. Otherwise, the command is forwarded to each
+ /// Kea server listed in the 'service' parameter.
///
/// @param cmd_name Command name.
/// @param params Command arguments.
/// @param original_cmd Original command being processed.
///
- /// @return Pointer to the const data element representing response
- /// to a command.
+ /// @return Pointer to the const data element representing a list of
+ /// responses to the command. If the command has been handled by the CA,
+ /// this list includes one response.
virtual isc::data::ConstElementPtr
handleCommand(const std::string& cmd_name,
const isc::data::ConstElementPtr& params,
private:
- /// @brief Tries to forward received control command to Kea servers.
+ /// @brief Implements the logic for @ref CtrlAgentCommandMgr::handleCommand.
///
- /// When the Control Agent was unable to process the control command
- /// because it doesn't recognize it, the command should be forwarded to
- /// the specific Kea services listed within a 'service' parameter.
+ /// All parameters are passed by value because they may be modified within
+ /// the method.
///
- /// @todo Currently only one service per control command is supported.
- /// Forwarding to multiple services should be allowed in the future.
+ /// @param cmd_name Command name.
+ /// @param params Command arguments.
+ /// @param original_cmd Original command being processed.
///
- /// This method makes an attempt to forward the control command. If
- /// the 'service' parameter is not specified or it is empty, the
- /// command is not forwarded and the @ref CommandForwardingSkip exception
- /// is thrown. The caller catching this exception should not treat
- /// this situation as an error but this is normal situation when the
- /// message is not intended to be forwarded.
+ /// @return Pointer to the const data element representing a list of responses
+ /// to the command or a single response (not wrapped in a list). The
+ /// @ref CtrlAgentCommandMgr::handleCommand will wrap non-list value returned
+ /// in a single element list.
+ isc::data::ConstElementPtr
+ handleCommandInternal(std::string cmd_name,
+ isc::data::ConstElementPtr params,
+ isc::data::ConstElementPtr original_cmd);
+
+ /// @brief Tries to forward received control command to Kea servers.
///
- /// All other exceptions should be treated as an error.
+ /// When the Control Agent was unable to process the control command
+ /// because it doesn't recognize it, the command should be forwarded to
+ /// the specific Kea services listed within a 'service' parameter. This
+ /// method forwards the command to the specified Kea service.
///
+ /// @param service Contains name of the service where the command should be
+ /// forwarded.
/// @param cmd_name Command name.
/// @param command Pointer to the object representing the forwarded command.
///
/// @return Response to forwarded command.
/// @throw CommandForwardingError when an error occurred during forwarding.
- /// @throw CommandForwardingSkip when 'service' parameter hasn't been
- /// specified which means that the command should not be forwarded.
isc::data::ConstElementPtr
- tryForwardCommand(const std::string& cmd_name,
- const isc::data::ConstElementPtr& command);
+ forwardCommand(const std::string& destination,
+ const std::string& cmd_name,
+ const isc::data::ConstElementPtr& command);
/// @brief Private constructor.
///
class CtrlAgentController : public process::DControllerBase {
public:
- using DControllerBase::getIOService;
-
/// @brief Static singleton instance method.
///
/// This method returns the base class singleton instance member.
event loop.
% CTRL_AGENT_STARTED Kea Control Agent version %1 started
-This informational message indicates that the DHCP-DDNS server has
+This informational message indicates that the Control Agent has
processed all configuration information and is ready to begin processing.
The version is also printed.
+% CTRL_AGENT_COMMAND_FORWARD_BEGIN begin forwarding command %1 to service %2
+This debug message is issued when the Control Agent starts forwarding a
+received command to one of the Kea servers.
+
+% CTRL_AGENT_COMMAND_FORWARD_FAILED failed forwarding command %1: %2
+This debug message is issued when the Control Agent failed forwarding a
+received command to one of the Kea servers. The second argument provides
+the details of the error.
+
+% CTRL_AGENT_COMMAND_PROCESS_SKIP command %1 already processed by hooks libraries, skipping
+This debug message is issued when the Control Agent skips processing
+received command because it has determined that the hooks libraries
+already processed the command.
+
% 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
CtrlAgentController::instance());
controller->registerCommands();
- CtrlAgentCfgContextPtr ctx =
- boost::dynamic_pointer_cast<CtrlAgentCfgContext>(base_ctx);
// Let's process incoming data or expiring timers in a loop until
// shutdown condition is detected.
while (!shouldShutdown()) {
+ // Remove unused listeners within the main loop because new listeners
+ // are created in within a callback method. This avoids removal the
+ // listeners within a callback.
garbageCollectListeners();
runIO();
}
#include <boost/pointer_cast.hpp>
#include <gtest/gtest.h>
#include <cstdlib>
+#include <vector>
using namespace isc::agent;
using namespace isc::asiolink;
/// verification of the response parameters.
///
/// @param answer answer to be verified
- /// @param expected_code code expected to be returned in the answer
- void checkAnswer(ConstElementPtr answer, int expected_code) {
+ /// @param expected_code0 code expected to be returned in first result within
+ /// the answer.
+ /// @param expected_code1 code expected to be returned in second result within
+ /// the answer.
+ /// @param expected_code2 code expected to be returned in third result within
+ /// the answer.
+ void checkAnswer(const ConstElementPtr& answer, const int expected_code0 = 0,
+ const int expected_code1 = -1, const int expected_code2 = -1) {
+ std::vector<int> expected_codes;
+ if (expected_code0 >= 0) {
+ expected_codes.push_back(expected_code0);
+ }
+
+ if (expected_code1 >= 0) {
+ expected_codes.push_back(expected_code1);
+ }
+
+ if (expected_code2 >= 0) {
+ expected_codes.push_back(expected_code2);
+ }
+
int status_code;
// There may be multiple answers returned within a list.
std::vector<ElementPtr> answer_list = answer->listValue();
- // There must be at least one answer.
- ASSERT_GE(answer_list.size(), 1);
- // Check that all answers indicate success.
+
+ ASSERT_EQ(expected_codes.size(), answer_list.size());
+ // Check all answers.
for (auto ans = answer_list.cbegin(); ans != answer_list.cend();
++ans) {
- ASSERT_NO_THROW(isc::config::parseAnswer(status_code, *ans));
- EXPECT_EQ(expected_code, status_code);
+ ConstElementPtr text;
+ ASSERT_NO_THROW(text = isc::config::parseAnswer(status_code, *ans));
+ EXPECT_EQ(expected_codes[std::distance(answer_list.cbegin(), ans)],
+ status_code)
+ << "answer contains text: " << text->stringValue();
}
}
///
/// @param response Stub response to be sent from the server socket to the
/// client.
- void bindServerSocket(const std::string& response) {
+ /// @param stop_after_count Number of received messages received over the
+ /// server socket after which the IO service should be stopped.
+ void bindServerSocket(const std::string& response,
+ const unsigned int stop_after_count = 1) {
server_socket_.reset(new test::TestServerUnixSocket(*getIOService(),
unixSocketFilePath(),
TEST_TIMEOUT,
response));
- server_socket_->bindServerSocket();
+ server_socket_->bindServerSocket(stop_after_count);
}
/// @brief Creates command with no arguments.
///
/// @param command_name Command name.
- /// @param service Service value to be added to the command. If this value
- /// holds an empty string, the service parameter is not added.
+ /// @param service Service value to be added to the command. This value is
+ /// specified as a list of comma separated values, e.g. "dhcp4, dhcp6".
///
/// @return Pointer to the instance of the created command.
ConstElementPtr createCommand(const std::string& command_name,
const std::string& service) {
ElementPtr command = Element::createMap();
-
command->set("command", Element::create(command_name));
// Only add the 'service' parameter if non-empty.
if (!service.empty()) {
- ElementPtr services = Element::createList();
- services->add(Element::create(service));
- command->set("service", services);
+ std::string s = boost::replace_all_copy(service, ",", "\",\"");
+ s = std::string("[ \"") + s + std::string("\" ]");
+ command->set("service", Element::fromJSON(s));
}
command->set("arguments", Element::createMap());
/// @param server_type Server for which the client socket should be
/// configured.
/// @param service Service to be included in the command.
- /// @param expected_result Expected result in response from the server.
+ /// @param expected_result0 Expected first result in response from the server.
+ /// @param expected_result1 Expected second result in response from the server.
+ /// @param expected_result2 Expected third result in response from the server.
+ /// @param stop_after_count Number of received messages received over the
+ /// server socket after which the IO service should be stopped.
/// @param server_response Stub response to be sent by the server.
void testForward(const CtrlAgentCfgContext::ServerType& server_type,
const std::string& service,
- const int expected_result,
+ const int expected_result0,
+ const int expected_result1 = -1,
+ const int expected_result2 = -1,
+ const unsigned stop_after_count = 1,
const std::string& server_response = "{ \"result\": 0 }") {
// Configure client side socket.
configureControlSocket(server_type);
// Create server side socket.
- bindServerSocket(server_response);
+ bindServerSocket(server_response, stop_after_count);
// The client side communication is synchronous. To be able to respond
// to this we need to run the server side socket at the same time.
ConstElementPtr answer = mgr_.handleCommand("foo", ConstElementPtr(),
command);
- checkAnswer(answer, expected_result);
+ checkAnswer(answer, expected_result0, expected_result1, expected_result2);
}
/// @brief a convenience reference to control agent command manager
isc::config::CONTROL_RESULT_SUCCESS);
}
+/// Check that the same command is forwarded to multiple servers.
+TEST_F(CtrlAgentCommandMgrTest, forwardToBothDHCPServers) {
+ configureControlSocket(CtrlAgentCfgContext::TYPE_DHCP6);
+
+ testForward(CtrlAgentCfgContext::TYPE_DHCP4, "dhcp4,dhcp6",
+ isc::config::CONTROL_RESULT_SUCCESS,
+ isc::config::CONTROL_RESULT_SUCCESS,
+ -1, 2);
+}
+
+/// Check that the command may forwarded to the second server even if
+/// forwarding to a first server fails.
+TEST_F(CtrlAgentCommandMgrTest, failForwardToServer) {
+ testForward(CtrlAgentCfgContext::TYPE_DHCP6, "dhcp4,dhcp6",
+ isc::config::CONTROL_RESULT_ERROR,
+ isc::config::CONTROL_RESULT_SUCCESS);
+}
+
/// Check that control command is not forwarded if the service is not specified.
TEST_F(CtrlAgentCommandMgrTest, noService) {
testForward(CtrlAgentCfgContext::TYPE_DHCP6, "",
/// command was forwarded sent an invalid message.
TEST_F(CtrlAgentCommandMgrTest, invalidAnswer) {
testForward(CtrlAgentCfgContext::TYPE_DHCP6, "dhcp6",
- isc::config::CONTROL_RESULT_ERROR,
+ isc::config::CONTROL_RESULT_ERROR, -1, -1, 1,
"{ \"result\": 0");
}
server_acceptor_(io_service_.get_io_service()),
server_socket_(io_service_.get_io_service()),
test_timer_(io_service_),
- custom_response_(custom_response) {
+ custom_response_(custom_response),
+ stop_after_count_(1),
+ read_count_(0) {
test_timer_.setup(boost::bind(&TestServerUnixSocket::timeoutHandler, this),
test_timeout, IntervalTimer::ONE_SHOT);
}
void
-TestServerUnixSocket::bindServerSocket() {
+TestServerUnixSocket::bindServerSocket(const unsigned int stop_after_count) {
server_acceptor_.open();
server_acceptor_.bind(server_endpoint_);
server_acceptor_.listen();
- server_acceptor_.async_accept(server_socket_,
- boost::bind(&TestServerUnixSocket::
- acceptHandler, this, _1));
+ accept();
+
+ stop_after_count_ = stop_after_count;
}
void
readHandler, this, _1, _2));
}
+void
+TestServerUnixSocket::accept() {
+ server_acceptor_.async_accept(server_socket_,
+ boost::bind(&TestServerUnixSocket::
+ acceptHandler, this, _1));
+}
+
+
void
TestServerUnixSocket::readHandler(const boost::system::error_code&,
size_t bytes_transferred) {
if (!custom_response_.empty()) {
boost::asio::write(server_socket_, boost::asio::buffer(custom_response_.c_str(),
custom_response_.size()));
+
} else {
std::string received(&raw_buf_[0], bytes_transferred);
std::string response("received " + received);
boost::asio::write(server_socket_, boost::asio::buffer(response.c_str(),
response.size()));
}
- io_service_.stop();
+
+ // Close the connection as we might be expecting another connection over the
+ // same socket.
+ server_socket_.close();
+
+ // Stop IO service if we have reached the maximum number of read messages.
+ if (++read_count_ >= stop_after_count_) {
+ io_service_.stop();
+
+ } else {
+ // Previous connection is done, so let's accept another connection.
+ accept();
+ }
}
void
TestServerUnixSocket(IOService& io_service,
const std::string& socket_file_path,
const long test_timeout,
- const std::string& custom_response = "");
+ const std::string& custom_respons_ = "");
/// @brief Creates and binds server socket.
- void bindServerSocket();
+ ///
+ /// @param stop_after_count Number of received messages after which the
+ /// IO service should be stopped.
+ void bindServerSocket(const unsigned int stop_after_count = 1);
/// @brief Server acceptor handler.
///
private:
+ /// @brief Asynchronously accept new connections.
+ void accept();
+
/// @brief IO service used by the tests.
IOService& io_service_;
/// @brief Holds custom response to be sent to the client.
std::string custom_response_;
+
+ /// @brief Number of messages received after which IO service gets stopped.
+ unsigned int stop_after_count_;
+
+ /// @brief Number of messages received so far.
+ unsigned int read_count_;
};
/// @brief Pointer to the @ref TestServerUnixSocket.
///
/// @param data Pointer to data to be written.
/// @param length Number of bytes to be written.
- /// @param [out] ec Error code returned as a result of an attempt to
- /// write to socket.
///
/// @return Number of bytes written.
/// @throw UnixDomainSocketError if error occurs.
/// @param [out] data Pointer to a location into which the read data should
/// be stored.
/// @param length Length of the buffer.
- /// @param [out] ec Error code returned as a result of an attempt to
- /// read from socket.
///
/// @return Number of bytes read.
/// @throw UnixDomainSocketError if error occurs.
: BaseCommandMgr(), callout_handle_(HooksManager::createCalloutHandle()) {
}
-ConstElementPtr
-HookedCommandMgr::handleCommand(const std::string& cmd_name,
- const ConstElementPtr& params,
- const ConstElementPtr& original_cmd) {
- if (!callout_handle_) {
- isc_throw(Unexpected, "callout handle not configured for the Command "
- "Manager: this is a programming error");
- }
-
- std::string final_cmd_name = cmd_name;
- ConstElementPtr final_params = boost::const_pointer_cast<Element>(params);
- ConstElementPtr final_cmd = original_cmd;
+bool
+HookedCommandMgr::delegateCommandToHookLibrary(std::string& cmd_name,
+ ConstElementPtr& params,
+ ConstElementPtr& original_cmd,
+ ElementPtr& answer) {
ConstElementPtr hook_response;
if (HooksManager::calloutsPresent(Hooks.hook_index_control_command_receive_)) {
// The callouts should set the response.
callout_handle_->getArgument("response", hook_response);
- // If the hook return 'skip' status, simply return the response.
- if (callout_handle_->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
- LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_HOOK_RECEIVE_SKIP)
- .arg(cmd_name);
-
- return (hook_response);
-
- }
// The hook library can modify the command or arguments. Thus, we
// retrieve the command returned by the callouts and use it as input
// to the local command handler.
ConstElementPtr hook_command;
callout_handle_->getArgument("command", hook_command);
- final_cmd_name = parseCommand(final_params, hook_command);
- final_cmd = hook_command;
+ cmd_name = parseCommand(params, hook_command);
+ original_cmd = hook_command;
+
+ answer = boost::const_pointer_cast<Element>(hook_response);
+
+ return (true);
+ }
+
+ return (false);
+}
+
+ConstElementPtr
+HookedCommandMgr::handleCommand(const std::string& cmd_name,
+ const ConstElementPtr& params,
+ const ConstElementPtr& original_cmd) {
+ if (!callout_handle_) {
+ isc_throw(Unexpected, "callout handle not configured for the Command "
+ "Manager: this is a programming error");
+ }
+
+ std::string mutable_cmd_name = cmd_name;
+ ConstElementPtr mutable_params = params;
+ ConstElementPtr mutable_cmd = original_cmd;
+
+ ElementPtr hook_response;
+ if (delegateCommandToHookLibrary(mutable_cmd_name, mutable_params,
+ mutable_cmd, hook_response)) {
+ if (callout_handle_->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_HOOK_RECEIVE_SKIP)
+ .arg(cmd_name);
+
+ return (hook_response);
+ }
}
// If we're here it means that the callouts weren't called or the 'skip'
// status wasn't returned. The latter is the case when the 'list-commands'
// is being processed. Anyhow, we need to handle the command using local
// Command Mananger.
- ConstElementPtr response = BaseCommandMgr::handleCommand(final_cmd_name,
- final_params,
- final_cmd);
+ ConstElementPtr response = BaseCommandMgr::handleCommand(mutable_cmd_name,
+ mutable_params,
+ mutable_cmd);
// For the 'list-commands' case we will have to combine commands supported
// by the hook libraries with the commands that this Command Manager supports.
- if ((final_cmd_name == "list-commands") && hook_response && response) {
+ if ((mutable_cmd_name == "list-commands") && hook_response && response) {
response = combineCommandsLists(hook_response, response);
}
return (callout_handle_);
}
- /// @brief Handles the command having a given name and arguments.
+ /// @brief Handles the command within the hooks libraries.
///
- /// This method checks if the hook library is installed which implements
+ /// This method checks if the hooks libraries are installed which implement
/// callouts for the 'control_command_receive' hook point, and calls them
- /// if they exist. If the hook library supports the given command it creates
- /// a response and returns it in the 'response' argument of the
- /// @ref isc::data::ConstElementPtr type. If the callout also sets the 'skip'
- /// status, the response created by the callout is returned. Otherwise, the
- /// @ref BaseCommandMgr::handleCommand is called.
+ /// if they exist. If the hooks library supports the given command it creates
+ /// a response and returns it in the @c answer argument.
+ ///
+ /// Values of all arguments can be modified by the hook library.
+ ///
+ /// @param [out] cmd_name Command name.
+ /// @param [out] params Command arguments.
+ /// @param [out] original_cmd Original command received.
+ /// @param [out] answer Command processing result returned by the hook.
+ ///
+ /// @return Boolean value indicating if any callouts have been executed.
+ bool
+ delegateCommandToHookLibrary(std::string& cmd_name,
+ isc::data::ConstElementPtr& params,
+ isc::data::ConstElementPtr& original_cmd,
+ isc::data::ElementPtr& answer);
+
+ /// @brief Handles the command having a given name and arguments.
+ ///
+ /// This method calls @ref HookedCommandMgr::delegateCommandToHookLibrary to
+ /// try to process the command with the hook libraries, if they are installed.
+ /// If the returned @c skip value indicates that the callout set the 'skip' flag
+ /// the command is assumed to have been processed and the response is returned.
+ /// If the 'skip' flag is not set, the @ref BaseCommandMgr::handleCommand is
+ /// called.
///
/// @param cmd_name Command name.
/// @param params Command arguments.