ca_command_mgr.cc 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. // Copyright (C) 2017 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. #include <agent/ca_cfg_mgr.h>
  7. #include <agent/ca_command_mgr.h>
  8. #include <agent/ca_controller.h>
  9. #include <agent/ca_log.h>
  10. #include <agent/ca_process.h>
  11. #include <asiolink/asio_wrapper.h>
  12. #include <asiolink/io_service.h>
  13. #include <asiolink/unix_domain_socket.h>
  14. #include <cc/command_interpreter.h>
  15. #include <cc/data.h>
  16. #include <cc/json_feed.h>
  17. #include <config/client_connection.h>
  18. #include <boost/pointer_cast.hpp>
  19. #include <iterator>
  20. #include <string>
  21. #include <vector>
  22. using namespace isc::asiolink;
  23. using namespace isc::config;
  24. using namespace isc::data;
  25. using namespace isc::hooks;
  26. using namespace isc::process;
  27. namespace {
  28. /// @brief Client side connection timeout.
  29. /// @todo Make it configurable.
  30. const long CONNECTION_TIMEOUT = 5000;
  31. }
  32. namespace isc {
  33. namespace agent {
  34. CtrlAgentCommandMgr&
  35. CtrlAgentCommandMgr::instance() {
  36. static CtrlAgentCommandMgr command_mgr;
  37. return (command_mgr);
  38. }
  39. CtrlAgentCommandMgr::CtrlAgentCommandMgr()
  40. : HookedCommandMgr() {
  41. }
  42. ConstElementPtr
  43. CtrlAgentCommandMgr::handleCommand(const std::string& cmd_name,
  44. const isc::data::ConstElementPtr& params,
  45. const isc::data::ConstElementPtr& original_cmd) {
  46. ConstElementPtr answer = handleCommandInternal(cmd_name, params, original_cmd);
  47. if (answer->getType() == Element::list) {
  48. return (answer);
  49. }
  50. // In general, the handlers should return a list of answers rather than a
  51. // single answer, but in some cases we rely on the generic handlers,
  52. // e.g. 'list-commands', which may return a single answer not wrapped in
  53. // the list. Such answers need to be wrapped in the list here.
  54. ElementPtr answer_list = Element::createList();
  55. answer_list->add(boost::const_pointer_cast<Element>(answer));
  56. return (answer_list);
  57. }
  58. ConstElementPtr
  59. CtrlAgentCommandMgr::handleCommandInternal(std::string cmd_name,
  60. isc::data::ConstElementPtr params,
  61. isc::data::ConstElementPtr original_cmd) {
  62. ConstElementPtr services = Element::createList();
  63. // Retrieve 'service' parameter to determine if we should forward the
  64. // command or handle it on our own.
  65. if (original_cmd && original_cmd->contains("service")) {
  66. services = original_cmd->get("service");
  67. // If 'service' value is not a list, this is a fatal error. We don't want
  68. // to try processing commands that don't adhere to the required format.
  69. if (services->getType() != Element::list) {
  70. return (createAnswer(CONTROL_RESULT_ERROR, "service value must be a list"));
  71. }
  72. }
  73. // 'service' parameter hasn't been specified which indicates that the command
  74. // is intended to be processed by the CA. The following command will try to
  75. // process the command with hooks libraries (if available) or by one of the
  76. // CA's native handlers.
  77. if (services->empty()) {
  78. return (HookedCommandMgr::handleCommand(cmd_name, params, original_cmd));
  79. }
  80. ElementPtr answer_list = Element::createList();
  81. // Before the command is forwarded we check if there are any hooks libraries
  82. // which would process the command.
  83. if (HookedCommandMgr::delegateCommandToHookLibrary(cmd_name, params, original_cmd,
  84. answer_list)) {
  85. // The command has been processed by hooks library. Return the result.
  86. return (answer_list);
  87. }
  88. // We don't know whether the hooks libraries modified the value of the
  89. // answer list, so let's be safe and re-create the answer_list.
  90. answer_list = Element::createList();
  91. // For each value within 'service' we have to try forwarding the command.
  92. for (unsigned i = 0; i < services->size(); ++i) {
  93. if (original_cmd) {
  94. ConstElementPtr answer;
  95. try {
  96. LOG_DEBUG(agent_logger, isc::log::DBGLVL_COMMAND,
  97. CTRL_AGENT_COMMAND_FORWARD_BEGIN)
  98. .arg(cmd_name).arg(services->get(i)->stringValue());
  99. answer = forwardCommand(services->get(i)->stringValue(),
  100. cmd_name, original_cmd);
  101. } catch (const CommandForwardingError& ex) {
  102. LOG_DEBUG(agent_logger, isc::log::DBGLVL_COMMAND,
  103. CTRL_AGENT_COMMAND_FORWARD_FAILED)
  104. .arg(cmd_name).arg(ex.what());
  105. answer = createAnswer(CONTROL_RESULT_ERROR, ex.what());
  106. }
  107. answer_list->add(boost::const_pointer_cast<Element>(answer));
  108. }
  109. }
  110. return (answer_list);
  111. }
  112. ConstElementPtr
  113. CtrlAgentCommandMgr::forwardCommand(const std::string& service,
  114. const std::string& cmd_name,
  115. const isc::data::ConstElementPtr& command) {
  116. // Context will hold the server configuration.
  117. CtrlAgentCfgContextPtr ctx;
  118. // There is a hierarchy of the objects through which we need to pass to get
  119. // the configuration context. We may simplify this at some point but since
  120. // we're in the singleton we want to make sure that we're using most current
  121. // configuration.
  122. boost::shared_ptr<CtrlAgentController> controller =
  123. boost::dynamic_pointer_cast<CtrlAgentController>(CtrlAgentController::instance());
  124. if (controller) {
  125. CtrlAgentProcessPtr process = controller->getCtrlAgentProcess();
  126. if (process) {
  127. CtrlAgentCfgMgrPtr cfgmgr = process->getCtrlAgentCfgMgr();
  128. if (cfgmgr) {
  129. ctx = cfgmgr->getCtrlAgentCfgContext();
  130. }
  131. }
  132. }
  133. // This is highly unlikely but keep the checks just in case someone messes up
  134. // in the code.
  135. if (!ctx) {
  136. isc_throw(CommandForwardingError, "internal server error: unable to retrieve"
  137. " Control Agent configuration information");
  138. }
  139. // Now that we know what service it should be forwarded to, we should
  140. // find a matching forwarding socket. If this socket is not configured,
  141. // we have to communicate it to the client.
  142. ConstElementPtr socket_info = ctx->getControlSocketInfo(service);
  143. if (!socket_info) {
  144. isc_throw(CommandForwardingError, "forwarding socket is not configured"
  145. " for the server type " << service);
  146. }
  147. // If the configuration does its job properly the socket-name must be
  148. // specified and must be a string value.
  149. std::string socket_name = socket_info->get("socket-name")->stringValue();
  150. // Forward command and receive reply.
  151. IOServicePtr io_service(new IOService());;
  152. ClientConnection conn(*io_service);
  153. boost::system::error_code received_ec;
  154. ConstJSONFeedPtr received_feed;
  155. conn.start(ClientConnection::SocketPath(socket_name),
  156. ClientConnection::ControlCommand(command->toWire()),
  157. [&io_service, &received_ec, &received_feed]
  158. (const boost::system::error_code& ec, ConstJSONFeedPtr feed) {
  159. // Capture error code and parsed data.
  160. received_ec = ec;
  161. received_feed = feed;
  162. // Got the IO service so stop IO service. This causes to
  163. // stop IO service when all handlers have been invoked.
  164. io_service->stopWork();
  165. }, ClientConnection::Timeout(CONNECTION_TIMEOUT));
  166. io_service->run();
  167. if (received_ec) {
  168. isc_throw(CommandForwardingError, "unable to forward command to the "
  169. << service << " service: " << received_ec.message()
  170. << ". The server is likely to be offline");
  171. }
  172. // This shouldn't happen because the fact that there was no time out indicates
  173. // that the whole response has been read and it should be stored within the
  174. // feed. But, let's check to prevent assertions.
  175. if (!received_feed) {
  176. isc_throw(CommandForwardingError, "internal server error: empty response"
  177. " received from the unix domain socket");
  178. }
  179. ConstElementPtr answer;
  180. try {
  181. answer = received_feed->toElement();
  182. LOG_INFO(agent_logger, CTRL_AGENT_COMMAND_FORWARDED)
  183. .arg(cmd_name).arg(service);
  184. } catch (const std::exception& ex) {
  185. isc_throw(CommandForwardingError, "internal server error: unable to parse"
  186. " server's answer to the forwarded message: " << ex.what());
  187. }
  188. return (answer);
  189. }
  190. } // end of namespace isc::agent
  191. } // end of namespace isc