Commit 19a50ed1 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac5078_rebase'

parents 5e6a6ec7 8a39fd2f
......@@ -1722,6 +1722,7 @@ AC_CONFIG_FILES([Makefile
src/lib/asiodns/Makefile
src/lib/asiodns/tests/Makefile
src/lib/asiolink/Makefile
src/lib/asiolink/testutils/Makefile
src/lib/asiolink/tests/Makefile
src/lib/cc/Makefile
src/lib/cc/tests/Makefile
......
......@@ -10,6 +10,7 @@
#include <agent/simple_parser.h>
#include <cc/simple_parser.h>
#include <cc/command_interpreter.h>
#include <exceptions/exceptions.h>
using namespace isc::dhcp;
using namespace isc::process;
......@@ -34,6 +35,20 @@ CtrlAgentCfgContext::CtrlAgentCfgContext(const CtrlAgentCfgContext& orig)
ctrl_sockets_[TYPE_DHCP6] = orig.ctrl_sockets_[TYPE_DHCP6];
}
CtrlAgentCfgContext::ServerType
CtrlAgentCfgContext::toServerType(const std::string& service) {
if (service == "dhcp4") {
return (CtrlAgentCfgContext::TYPE_DHCP4);
} else if (service == "dhcp6") {
return (CtrlAgentCfgContext::TYPE_DHCP6);
} else if (service == "d2") {
return (CtrlAgentCfgContext::TYPE_D2);
}
isc_throw(isc::BadValue, "invalid service value " << service);
}
CtrlAgentCfgMgr::CtrlAgentCfgMgr()
: DCfgMgrBase(DCfgContextBasePtr(new CtrlAgentCfgContext())) {
......
......@@ -11,6 +11,7 @@
#include <hooks/hooks_config.h>
#include <process/d_cfg_mgr.h>
#include <boost/pointer_cast.hpp>
#include <string>
namespace isc {
namespace agent {
......@@ -42,6 +43,11 @@ public:
/// @brief Used check that specified ServerType is within valid range.
static const uint32_t MAX_TYPE_SUPPORTED = TYPE_D2;
/// @brief Converts service specified as a string to ServerType.
///
/// @param service Service value as a string: 'dhcp4', 'dhcp6', 'd2'.
static ServerType toServerType(const std::string& service);
/// @brief Creates a clone of this context object.
///
/// Note this method does not do deep copy the information about control sockets.
......
......@@ -4,11 +4,26 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <agent/ca_cfg_mgr.h>
#include <agent/ca_command_mgr.h>
#include <agent/ca_controller.h>
#include <agent/ca_log.h>
#include <agent/ca_process.h>
#include <asiolink/asio_wrapper.h>
#include <asiolink/io_service.h>
#include <asiolink/unix_domain_socket.h>
#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 {
namespace agent {
......@@ -25,13 +40,192 @@ CtrlAgentCommandMgr::CtrlAgentCommandMgr()
ConstElementPtr
CtrlAgentCommandMgr::handleCommand(const std::string& cmd_name,
const isc::data::ConstElementPtr& params) {
const isc::data::ConstElementPtr& params,
const isc::data::ConstElementPtr& original_cmd) {
ConstElementPtr answer = handleCommandInternal(cmd_name, params, original_cmd);
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>(HookedCommandMgr::handleCommand(cmd_name, params)));
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"));
}
}
// '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));
}
ElementPtr answer_list = Element::createList();
// 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);
}
}
// 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());
}
answer_list->add(boost::const_pointer_cast<Element>(answer));
}
}
return (answer_list);
}
ConstElementPtr
CtrlAgentCommandMgr::forwardCommand(const std::string& service,
const std::string& cmd_name,
const isc::data::ConstElementPtr& command) {
// Context will hold the server configuration.
CtrlAgentCfgContextPtr ctx;
// There is a hierarchy of the objects through which we need to pass to get
// the configuration context. We may simplify this at some point but since
// we're in the singleton we want to make sure that we're using most current
// configuration.
boost::shared_ptr<CtrlAgentController> controller =
boost::dynamic_pointer_cast<CtrlAgentController>(CtrlAgentController::instance());
if (controller) {
CtrlAgentProcessPtr process = controller->getCtrlAgentProcess();
if (process) {
CtrlAgentCfgMgrPtr cfgmgr = process->getCtrlAgentCfgMgr();
if (cfgmgr) {
ctx = cfgmgr->getCtrlAgentCfgContext();
}
}
}
// This is highly unlikely but keep the checks just in case someone messes up
// in the code.
if (!ctx) {
isc_throw(CommandForwardingError, "internal server error: unable to retrieve"
" Control Agent configuration information");
}
// 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);
} catch (const std::exception& ex) {
// Invalid value in service list. Can't proceed.
isc_throw(CommandForwardingError, ex.what());
}
// Now that we know what service it should be forwarded to, we should
// find a matching forwarding socket. If this socket is not configured,
// we have to communicate it to the client.
ConstElementPtr socket_info = ctx->getControlSocketInfo(server_type);
if (!socket_info) {
isc_throw(CommandForwardingError, "forwarding socket is not configured"
" for the server type " << service);
}
// If the configuration does its job properly the socket-name must be
// specified and must be a string value.
std::string socket_name = socket_info->get("socket-name")->stringValue();
// Forward command and receive reply.
IOService io_service;
UnixDomainSocket unix_socket(io_service);
size_t receive_len;
try {
unix_socket.connect(socket_name);
std::string wire_command = command->toWire();
unix_socket.write(&wire_command[0], wire_command.size());
receive_len = unix_socket.receive(&receive_buf_[0], receive_buf_.size());
} catch (const std::exception& ex) {
isc_throw(CommandForwardingError, "unable to forward command to the "
<< service << " service: " << ex.what() << ". The server "
"is likely to be offline");
}
// This is really not possible right now, but when we migrate to the
// solution using timeouts it is possible that the response is not
// received.
if (receive_len == 0) {
isc_throw(CommandForwardingError, "internal server error: no answer"
" received from the server to the forwarded message");
}
std::string reply(&receive_buf_[0], receive_len);
ConstElementPtr answer;
try {
answer = Element::fromJSON(reply);
LOG_INFO(agent_logger, CTRL_AGENT_COMMAND_FORWARDED)
.arg(cmd_name).arg(service);
} catch (const std::exception& ex) {
isc_throw(CommandForwardingError, "internal server error: unable to parse"
" server's answer to the forwarded message: " << ex.what());
}
return (answer);
}
} // end of namespace isc::agent
} // end of namespace isc
......@@ -8,12 +8,22 @@
#define CTRL_AGENT_COMMAND_MGR_H
#include <config/hooked_command_mgr.h>
#include <exceptions/exceptions.h>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <array>
namespace isc {
namespace agent {
/// @brief Exception thrown when an error occurred during control command
/// forwarding.
class CommandForwardingError : public Exception {
public:
CommandForwardingError(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.
......@@ -37,28 +47,71 @@ public:
/// @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.
///
/// If the received command doesn't include 'service' parameter or this
/// parameter is blank, the command is first handled by the attached hooks
/// libraries, and if still unhandled, the Control Agent itself.
///
/// @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 non-blank 'service' parameter has been specified the hooks
/// are executed. If the hooks 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);
const isc::data::ConstElementPtr& params,
const isc::data::ConstElementPtr& original_cmd);
private:
/// @brief Implements the logic for @ref CtrlAgentCommandMgr::handleCommand.
///
/// All parameters are passed by value because they may be modified within
/// the method.
///
/// @param cmd_name Command name.
/// @param params Command arguments.
/// @param original_cmd Original command being processed.
///
/// @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 a specified server.
///
/// @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.
isc::data::ConstElementPtr
forwardCommand(const std::string& service, const std::string& cmd_name,
const isc::data::ConstElementPtr& command);
/// @brief Private constructor.
///
/// The instance should be created using @ref CtrlAgentCommandMgr::instance,
/// thus the constructor is private.
CtrlAgentCommandMgr();
/// @brief Buffer into which responses to forwarded commands are stored.
std::array<char, 8192> receive_buf_;
};
} // end of namespace isc::agent
......
......@@ -86,5 +86,10 @@ CtrlAgentController::CtrlAgentController()
CtrlAgentController::~CtrlAgentController() {
}
CtrlAgentProcessPtr
CtrlAgentController::getCtrlAgentProcess() {
return (boost::dynamic_pointer_cast<CtrlAgentProcess>(getProcess()));
}
} // namespace isc::agent
} // namespace isc
......@@ -7,6 +7,7 @@
#ifndef CTRL_AGENT_CONTROLLER_H
#define CTRL_AGENT_CONTROLLER_H
#include <agent/ca_process.h>
#include <process/d_controller.h>
namespace isc {
......@@ -32,6 +33,9 @@ public:
/// @brief Destructor
virtual ~CtrlAgentController();
/// @brief Returns pointer to an instance of the underlying process object.
CtrlAgentProcessPtr getCtrlAgentProcess();
/// @brief Defines the application name, this is passed into base class
/// and appears in log statements.
static const char* agent_app_name_;
......
......@@ -6,6 +6,10 @@
$NAMESPACE isc::agent
% CTRL_AGENT_COMMAND_FORWARDED command %1 successfully forwarded to the service %2
This informational message is issued when the CA successfully forwards
the control message to the specified Kea service and receives a response.
% CTRL_AGENT_HTTP_SERVICE_STARTED HTTP service bound to address %1:%2
This informational message indicates that the server has started HTTP service
on the specified address and port. All control commands should be sent to this
......@@ -20,10 +24,24 @@ This is a debug message issued when the Control Agent exits its
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
......
......@@ -5,6 +5,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
#include <asiolink/asio_wrapper.h>
#include <agent/ca_process.h>
#include <agent/ca_controller.h>
#include <agent/ca_response_creator_factory.h>
......@@ -12,10 +13,10 @@
#include <asiolink/io_address.h>
#include <asiolink/io_error.h>
#include <cc/command_interpreter.h>
#include <http/listener.h>
#include <boost/pointer_cast.hpp>
using namespace isc::asiolink;
using namespace isc::data;
using namespace isc::http;
using namespace isc::process;
......@@ -32,7 +33,8 @@ namespace agent {
CtrlAgentProcess::CtrlAgentProcess(const char* name,
const asiolink::IOServicePtr& io_service)
: DProcessBase(name, io_service, DCfgMgrBasePtr(new CtrlAgentCfgMgr())) {
: DProcessBase(name, io_service, DCfgMgrBasePtr(new CtrlAgentCfgMgr())),
http_listeners_() {
}
CtrlAgentProcess::~CtrlAgentProcess() {
......@@ -47,53 +49,20 @@ CtrlAgentProcess::run() {
LOG_INFO(agent_logger, CTRL_AGENT_STARTED).arg(VERSION);
try {
// Register commands.
CtrlAgentControllerPtr controller =
boost::dynamic_pointer_cast<CtrlAgentController>(
CtrlAgentController::instance());
controller->registerCommands();
// Create response creator factory first. It will be used to generate
// response creators. Each response creator will be used to generate
// answer to specific request.
HttpResponseCreatorFactoryPtr rcf(new CtrlAgentResponseCreatorFactory());
DCfgContextBasePtr base_ctx = getCfgMgr()->getContext();
CtrlAgentCfgContextPtr ctx =
boost::dynamic_pointer_cast<CtrlAgentCfgContext>(base_ctx);
if (!ctx) {
isc_throw(Unexpected, "Interal logic error: bad context type");
}
/// @todo: If the parameter is a hostname, we need to resolve it.
IOAddress server_address("::");
try {
server_address = IOAddress(ctx->getHttpHost());
} catch (const IOError& e) {
isc_throw(BadValue, "Failed to convert " << ctx->getHttpHost()
<< " to IP address:" << e.what());
}
uint16_t server_port = ctx->getHttpPort();
// Create http listener. It will open up a TCP socket and be prepared
// to accept incoming connection.
HttpListener http_listener(*getIoService(), server_address,
server_port, rcf, REQUEST_TIMEOUT);
// Instruct the http listener to actually open socket, install callback
// and start listening.
http_listener.start();
// Ok, seems we're good to go.
LOG_INFO(agent_logger, CTRL_AGENT_HTTP_SERVICE_STARTED)
.arg(server_address.toText()).arg(server_port);
// Let's process incoming data or expiring timers in a loop until
// shutdown condition is detected.
while (!shouldShutdown()) {
getIoService()->run_one();
// 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();
}
stopIOService();
} catch (const std::exception& ex) {
......@@ -120,6 +89,15 @@ CtrlAgentProcess::run() {
LOG_DEBUG(agent_logger, isc::log::DBGLVL_START_SHUT, CTRL_AGENT_RUN_EXIT);
}
size_t
CtrlAgentProcess::runIO() {
size_t cnt = getIoService()->get_io_service().poll();
if (!cnt) {
cnt = getIoService()->get_io_service().run_one();
}
return (cnt);
}
isc::data::ConstElementPtr
CtrlAgentProcess::shutdown(isc::data::ConstElementPtr /*args*/) {
setShutdownFlag(true);
......@@ -129,17 +107,113 @@ CtrlAgentProcess::shutdown(isc::data::ConstElementPtr /*args*/) {
isc::data::ConstElementPtr
CtrlAgentProcess::configure(isc::data::ConstElementPtr config_set,
bool check_only) {
// System reconfiguration often poses an interesting issue whereby the
// configuration parsing is successful, but an attempt to use a new
// configuration is not. This will leave us in the inconsistent state
// when the configuration is in fact only partially applied and the
// system's ability to operate is impaired. The use of C++ lambda is
// a way to resolve this problem by injecting the code to the
// simpleParseConfig which performs an attempt to open new instance
// of the listener (if required). The lambda code will throw an
// exception if it fails and cause the simpleParseConfig to rollback
// configuration changes and report an error.
ConstElementPtr answer = getCfgMgr()->simpleParseConfig(config_set,
check_only,
[this]() {
DCfgContextBasePtr base_ctx = getCfgMgr()->getContext();
CtrlAgentCfgContextPtr
ctx = boost::dynamic_pointer_cast<CtrlAgentCfgContext>(base_ctx);
if (!ctx) {
isc_throw(Unexpected, "Interal logic error: bad context type");
}
/// @todo: If the parameter is a hostname, we need to resolve it.
IOAddress server_address("::");
try {
server_address = IOAddress(ctx->getHttpHost());
} catch (const IOError& e) {
isc_throw(BadValue, "Failed to convert " << ctx->getHttpHost()
<< " to IP address:" << e.what());
}
uint16_t server_port = ctx->getHttpPort();
// Only open a new listener if the configuration has changed.
if (http_listeners_.empty() ||
(http_listeners_.back()->getLocalAddress() != server_address) ||
(http_listeners_.back()->getLocalPort() != server_port)) {
// Create response creator factory first. It will be used to
// generate response creators. Each response creator will be
// used to generate answer to specific request.
HttpResponseCreatorFactoryPtr rcf(new CtrlAgentResponseCreatorFactory());
// Create http listener. It will open up a TCP socket and be
// prepared to accept incoming connection.
HttpListenerPtr http_listener(new HttpListener(*getIoService(),
server_address,
server_port, rcf,
REQUEST_TIMEOUT));
// Instruct the http listener to actually open socket, install
// callback and start listening.
http_listener->start();
// The new listener is running so add it to the collection of
// active listeners. The next step will be to remove all other
// active listeners, but we do it inside the main process loop.
http_listeners_.push_back(http_listener);
}
// Ok, seems we're good to go.
LOG_INFO(agent_logger, CTRL_AGENT_HTTP_SERVICE_STARTED)
.arg(server_address.toText()).arg(server_port);