Kea  1.5.0
ca_command_mgr.cc
Go to the documentation of this file.
1 // Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include <agent/ca_cfg_mgr.h>
10 #include <agent/ca_command_mgr.h>
11 #include <agent/ca_controller.h>
12 #include <agent/ca_log.h>
13 #include <agent/ca_process.h>
14 #include <asiolink/asio_wrapper.h>
15 #include <asiolink/io_service.h>
17 #include <cc/command_interpreter.h>
18 #include <cc/data.h>
19 #include <cc/json_feed.h>
21 #include <config/timeouts.h>
22 #include <boost/pointer_cast.hpp>
23 #include <iterator>
24 #include <sstream>
25 #include <string>
26 #include <vector>
27 
28 using namespace isc::asiolink;
29 using namespace isc::config;
30 using namespace isc::data;
31 using namespace isc::hooks;
32 using namespace isc::process;
33 
34 namespace isc {
35 namespace agent {
36 
37 CtrlAgentCommandMgr&
38 CtrlAgentCommandMgr::instance() {
39  static CtrlAgentCommandMgr command_mgr;
40  return (command_mgr);
41 }
42 
43 CtrlAgentCommandMgr::CtrlAgentCommandMgr()
44  : HookedCommandMgr() {
45 }
46 
48 CtrlAgentCommandMgr::handleCommand(const std::string& cmd_name,
49  const isc::data::ConstElementPtr& params,
50  const isc::data::ConstElementPtr& original_cmd) {
51  ConstElementPtr answer = handleCommandInternal(cmd_name, params, original_cmd);
52 
53  if (answer->getType() == Element::list) {
54  return (answer);
55  }
56 
57  // In general, the handlers should return a list of answers rather than a
58  // single answer, but in some cases we rely on the generic handlers,
59  // e.g. 'list-commands', which may return a single answer not wrapped in
60  // the list. Such answers need to be wrapped in the list here.
61  ElementPtr answer_list = Element::createList();
62  answer_list->add(boost::const_pointer_cast<Element>(answer));
63 
64  return (answer_list);
65 }
66 
67 
69 CtrlAgentCommandMgr::handleCommandInternal(std::string cmd_name,
71  isc::data::ConstElementPtr original_cmd) {
72 
73  ConstElementPtr services = Element::createList();
74 
75  // Retrieve 'service' parameter to determine if we should forward the
76  // command or handle it on our own.
77  if (original_cmd && original_cmd->contains("service")) {
78  services = original_cmd->get("service");
79  // If 'service' value is not a list, this is a fatal error. We don't want
80  // to try processing commands that don't adhere to the required format.
81  if (services->getType() != Element::list) {
82  return (createAnswer(CONTROL_RESULT_ERROR, "service value must be a list"));
83  }
84  }
85 
86  // 'service' parameter hasn't been specified which indicates that the command
87  // is intended to be processed by the CA. The following command will try to
88  // process the command with hooks libraries (if available) or by one of the
89  // CA's native handlers.
90  if (services->empty()) {
91 
92  // It is frequent user error to not include the 'service' parameter in
93  // the commands that should be forwarded to Kea servers. If the command
94  // lacks this parameter the CA will try to process it and often fail
95  // because it is not supported by the CA. In the future we may want to
96  // make this parameter mandatory. For now, we're going to improve the
97  // situation by clearly explaining to the controlling client that the
98  // command is not supported by the CA, but it is possible that he may
99  // achieve what he wants by providing the 'service' parameter.
100 
101  // Our interface is very restrictive so we walk around this by const
102  // casting the returned pointer. It is certainly easier to do than
103  // changing the whole data interface.
104  ElementPtr answer = boost::const_pointer_cast<Element>
105  (HookedCommandMgr::handleCommand(cmd_name, params, original_cmd));
106 
107  try {
108  // Check what error code was returned by the handler.
109  int rcode = 0;
110  ConstElementPtr text = parseAnswer(rcode, answer);
111 
112  // There is a dedicated error code for unsupported command case.
113  if (rcode == CONTROL_RESULT_COMMAND_UNSUPPORTED) {
114 
115  // Append the explanatory text to the text reported by the handler.
116  // Smart, eh?
117  std::ostringstream s;
118  s << text->stringValue();
119  s << " You did not include \"service\" parameter in the command,"
120  " which indicates that Kea Control Agent should process this"
121  " command rather than forward it to one or more DHCP servers. If you"
122  " aimed to send this command to one of the DHCP servers you"
123  " should include the \"service\" parameter in your request, e.g."
124  " \"service\": [ \"dhcp4\" ] to forward the command to the DHCPv4"
125  " server, or \"service\": [ \"dhcp4\", \"dhcp6\" ] to forward it to"
126  " both DHCPv4 and DHCPv6 servers etc.";
127 
128  answer->set(CONTROL_TEXT, Element::create(s.str()));
129  }
130 
131  } catch (...) {
132  // Exceptions are not really possible assuming that the BaseCommandMgr
133  // creates the response correctly.
134  }
135 
136  return (answer);
137  }
138 
139  ElementPtr answer_list = Element::createList();
140 
141  // Before the command is forwarded we check if there are any hooks libraries
142  // which would process the command.
143  if (HookedCommandMgr::delegateCommandToHookLibrary(cmd_name, params, original_cmd,
144  answer_list)) {
145  // The command has been processed by hooks library. Return the result.
146  return (answer_list);
147  }
148 
149  // We don't know whether the hooks libraries modified the value of the
150  // answer list, so let's be safe and re-create the answer_list.
151  answer_list = Element::createList();
152 
153  // For each value within 'service' we have to try forwarding the command.
154  for (unsigned i = 0; i < services->size(); ++i) {
155  if (original_cmd) {
156  ConstElementPtr answer;
157  try {
159  CTRL_AGENT_COMMAND_FORWARD_BEGIN)
160  .arg(cmd_name).arg(services->get(i)->stringValue());
161 
162  answer = forwardCommand(services->get(i)->stringValue(),
163  cmd_name, original_cmd);
164 
165  } catch (const CommandForwardingError& ex) {
167  CTRL_AGENT_COMMAND_FORWARD_FAILED)
168  .arg(cmd_name).arg(ex.what());
169  answer = createAnswer(CONTROL_RESULT_ERROR, ex.what());
170  }
171 
172  answer_list->add(boost::const_pointer_cast<Element>(answer));
173  }
174  }
175 
176  return (answer_list);
177 }
178 
180 CtrlAgentCommandMgr::forwardCommand(const std::string& service,
181  const std::string& cmd_name,
182  const isc::data::ConstElementPtr& command) {
183  // Context will hold the server configuration.
185 
186  // There is a hierarchy of the objects through which we need to pass to get
187  // the configuration context. We may simplify this at some point but since
188  // we're in the singleton we want to make sure that we're using most current
189  // configuration.
190  boost::shared_ptr<CtrlAgentController> controller =
191  boost::dynamic_pointer_cast<CtrlAgentController>(CtrlAgentController::instance());
192  if (controller) {
193  CtrlAgentProcessPtr process = controller->getCtrlAgentProcess();
194  if (process) {
195  CtrlAgentCfgMgrPtr cfgmgr = process->getCtrlAgentCfgMgr();
196  if (cfgmgr) {
197  ctx = cfgmgr->getCtrlAgentCfgContext();
198  }
199  }
200  }
201 
202  // This is highly unlikely but keep the checks just in case someone messes up
203  // in the code.
204  if (!ctx) {
205  isc_throw(CommandForwardingError, "internal server error: unable to retrieve"
206  " Control Agent configuration information");
207  }
208 
209  // Now that we know what service it should be forwarded to, we should
210  // find a matching forwarding socket. If this socket is not configured,
211  // we have to communicate it to the client.
212  ConstElementPtr socket_info = ctx->getControlSocketInfo(service);
213  if (!socket_info) {
214  isc_throw(CommandForwardingError, "forwarding socket is not configured"
215  " for the server type " << service);
216  }
217 
218  // If the configuration does its job properly the socket-name must be
219  // specified and must be a string value.
220  std::string socket_name = socket_info->get("socket-name")->stringValue();
221 
222  // Forward command and receive reply.
223  IOServicePtr io_service(new IOService());;
224  ClientConnection conn(*io_service);
225  boost::system::error_code received_ec;
226  ConstJSONFeedPtr received_feed;
227  conn.start(ClientConnection::SocketPath(socket_name),
228  ClientConnection::ControlCommand(command->toWire()),
229  [&io_service, &received_ec, &received_feed]
230  (const boost::system::error_code& ec, ConstJSONFeedPtr feed) {
231  // Capture error code and parsed data.
232  received_ec = ec;
233  received_feed = feed;
234  // Got the IO service so stop IO service. This causes to
235  // stop IO service when all handlers have been invoked.
236  io_service->stopWork();
238  io_service->run();
239 
240  if (received_ec) {
241  isc_throw(CommandForwardingError, "unable to forward command to the "
242  << service << " service: " << received_ec.message()
243  << ". The server is likely to be offline");
244  }
245 
246  // This shouldn't happen because the fact that there was no time out indicates
247  // that the whole response has been read and it should be stored within the
248  // feed. But, let's check to prevent assertions.
249  if (!received_feed) {
250  isc_throw(CommandForwardingError, "internal server error: empty response"
251  " received from the unix domain socket");
252  }
253 
254  ConstElementPtr answer;
255  try {
256  answer = received_feed->toElement();
257 
258  LOG_INFO(agent_logger, CTRL_AGENT_COMMAND_FORWARDED)
259  .arg(cmd_name).arg(service);
260 
261  } catch (const std::exception& ex) {
262  isc_throw(CommandForwardingError, "internal server error: unable to parse"
263  " server's answer to the forwarded message: " << ex.what());
264  }
265 
266  return (answer);
267 }
268 
269 
270 } // end of namespace isc::agent
271 } // end of namespace isc
isc::config::ClientConnection::Timeout
Encapsulates timeout value.
Definition: client_connection.h:94
isc::config::CONTROL_RESULT_COMMAND_UNSUPPORTED
const int CONTROL_RESULT_COMMAND_UNSUPPORTED
Status code indicating that the specified command is not supported.
Definition: command_interpreter.h:45
isc::agent::CtrlAgentCommandMgr
Command Manager for Control Agent.
Definition: ca_command_mgr.h:38
ca_process.h
ca_log.h
isc::config::createAnswer
ConstElementPtr createAnswer(const int status_code, const std::string &text, const ConstElementPtr &arg)
Definition: command_interpreter.cc:33
isc::agent::agent_logger
isc::log::Logger agent_logger("ctrl-agent")
Control Agent logger.
Definition: ca_log.h:18
isc::config
Definition: command_interpreter.cc:23
unix_domain_socket.h
ca_cfg_mgr.h
isc::config::CONTROL_RESULT_ERROR
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
Definition: command_interpreter.h:42
isc::data
Definition: cfg_to_element.h:25
isc::config::HookedCommandMgr
Command Manager which can delegate commands to a hook library.
Definition: hooked_command_mgr.h:39
isc::agent::CtrlAgentCfgMgrPtr
boost::shared_ptr< CtrlAgentCfgMgr > CtrlAgentCfgMgrPtr
Defines a shared pointer to CtrlAgentCfgMgr.
Definition: ca_cfg_mgr.h:208
io_service.h
isc
Defines the logger used by the top-level component of kea-dhcp-ddns.
Definition: agent_parser.cc:144
isc_throw
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Definition: exceptions/exceptions.h:192
isc::config::ClientConnection::ControlCommand
Encapsulates control command.
Definition: client_connection.h:86
LOG_DEBUG
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
isc::config::CONTROL_TEXT
const char * CONTROL_TEXT
String used for storing textual description ("text")
Definition: command_interpreter.cc:27
command_interpreter.h
isc::log::DBGLVL_COMMAND
const int DBGLVL_COMMAND
This debug level is reserved for logging the exchange of messages/commands between processes,...
Definition: log_dbglevels.h:54
isc::config::TIMEOUT_AGENT_FORWARD_COMMAND
constexpr long TIMEOUT_AGENT_FORWARD_COMMAND
Timeout for the Control Agent to forward command to a Kea server, e.g.
Definition: timeouts.h:31
json_feed.h
isc::agent::CtrlAgentController::instance
static process::DControllerBasePtr & instance()
Static singleton instance method.
Definition: ca_controller.cc:28
client_connection.h
isc::agent::CtrlAgentProcessPtr
boost::shared_ptr< CtrlAgentProcess > CtrlAgentProcessPtr
Defines a shared pointer to CtrlAgentProcess.
Definition: ca_process.h:147
isc::config::ConstJSONFeedPtr
boost::shared_ptr< const JSONFeed > ConstJSONFeedPtr
Pointer to the const JSONFeed.
Definition: json_feed.h:27
isc::agent::CtrlAgentCommandMgr::handleCommand
virtual isc::data::ConstElementPtr handleCommand(const std::string &cmd_name, const isc::data::ConstElementPtr &params, const isc::data::ConstElementPtr &original_cmd)
Handles the command having a given name and arguments.
Definition: ca_command_mgr.cc:48
isc::process
Definition: config_base.cc:16
isc::agent::CtrlAgentCfgContextPtr
boost::shared_ptr< CtrlAgentCfgContext > CtrlAgentCfgContextPtr
Pointer to a configuration context.
Definition: ca_cfg_mgr.h:20
isc::config::parseAnswer
ConstElementPtr parseAnswer(int &rcode, const ConstElementPtr &msg)
Definition: command_interpreter.cc:68
data.h
ca_controller.h
isc::config::ClientConnection::SocketPath
Encapsulates socket path.
Definition: client_connection.h:78
isc::hooks
Definition: callout_handle.cc:21
isc::data::ElementPtr
boost::shared_ptr< Element > ElementPtr
Definition: data.h:20
isc::config::ClientConnection
Represents client side connection over the unix domain socket.
Definition: client_connection.h:70
isc::data::ConstElementPtr
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:23
timeouts.h
asio_wrapper.h
ca_command_mgr.h
LOG_INFO
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20