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>
11#include <agent/ca_controller.h>
12#include <agent/ca_log.h>
13#include <agent/ca_process.h>
15#include <asiolink/io_service.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
28using namespace isc::asiolink;
29using namespace isc::config;
30using namespace isc::data;
31using namespace isc::hooks;
32using namespace isc::process;
33
34namespace isc {
35namespace agent {
36
37CtrlAgentCommandMgr&
39 static CtrlAgentCommandMgr command_mgr;
40 return (command_mgr);
41}
42
43CtrlAgentCommandMgr::CtrlAgentCommandMgr()
45}
46
48CtrlAgentCommandMgr::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
69CtrlAgentCommandMgr::handleCommandInternal(std::string cmd_name,
71 isc::data::ConstElementPtr original_cmd) {
72
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.
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
180CtrlAgentCommandMgr::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
Command Manager for Control Agent.
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.
static CtrlAgentCommandMgr & instance()
Returns sole instance of the Command Manager.
static process::DControllerBasePtr & instance()
Static singleton instance method.
Represents client side connection over the unix domain socket.
Command Manager which can delegate commands to a hook library.
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.
bool delegateCommandToHookLibrary(const std::string &cmd_name, const isc::data::ConstElementPtr &params, const isc::data::ConstElementPtr &original_cmd, isc::data::ElementPtr &answer)
Handles the command within the hooks libraries.
static ElementPtr create(const Position &pos=ZERO_POSITION())
Definition: data.cc:223
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition: data.cc:263
This file contains several functions and constants that are used for handling commands and responses ...
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
boost::shared_ptr< CtrlAgentCfgContext > CtrlAgentCfgContextPtr
Pointer to a configuration context.
Definition: ca_cfg_mgr.h:22
boost::shared_ptr< CtrlAgentProcess > CtrlAgentProcessPtr
Defines a shared pointer to CtrlAgentProcess.
Definition: ca_process.h:147
isc::log::Logger agent_logger("ctrl-agent")
Control Agent logger.
Definition: ca_log.h:18
boost::shared_ptr< CtrlAgentCfgMgr > CtrlAgentCfgMgrPtr
Defines a shared pointer to CtrlAgentCfgMgr.
Definition: ca_cfg_mgr.h:208
boost::shared_ptr< const JSONFeed > ConstJSONFeedPtr
Pointer to the const JSONFeed.
Definition: json_feed.h:27
const char * CONTROL_TEXT
String used for storing textual description ("text")
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
ConstElementPtr createAnswer()
Creates a standard config/command level success answer message (i.e.
const int CONTROL_RESULT_COMMAND_UNSUPPORTED
Status code indicating that the specified command is not supported.
ConstElementPtr parseAnswer(int &rcode, const ConstElementPtr &msg)
constexpr long TIMEOUT_AGENT_FORWARD_COMMAND
Timeout for the Control Agent to forward command to a Kea server, e.g.
Definition: timeouts.h:31
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:23
boost::shared_ptr< Element > ElementPtr
Definition: data.h:22
const int DBGLVL_COMMAND
This debug level is reserved for logging the exchange of messages/commands between processes,...
Definition: log_dbglevels.h:54
Defines the logger used by the top-level component of kea-dhcp-ddns.