ca_command_mgr_unittests.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  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 <config.h>
  7. #include <agent/ca_cfg_mgr.h>
  8. #include <agent/ca_command_mgr.h>
  9. #include <agent/ca_controller.h>
  10. #include <agent/ca_process.h>
  11. #include <asiolink/asio_wrapper.h>
  12. #include <asiolink/interval_timer.h>
  13. #include <asiolink/io_service.h>
  14. #include <asiolink/testutils/test_server_unix_socket.h>
  15. #include <cc/command_interpreter.h>
  16. #include <cc/data.h>
  17. #include <process/testutils/d_test_stubs.h>
  18. #include <util/threads/thread.h>
  19. #include <boost/bind.hpp>
  20. #include <boost/pointer_cast.hpp>
  21. #include <gtest/gtest.h>
  22. #include <cstdlib>
  23. #include <vector>
  24. using namespace isc::agent;
  25. using namespace isc::asiolink;
  26. using namespace isc::data;
  27. using namespace isc::process;
  28. namespace {
  29. /// @brief Test unix socket file name.
  30. const std::string TEST_SOCKET = "test-socket";
  31. /// @brief Test timeout in ms.
  32. const long TEST_TIMEOUT = 10000;
  33. /// @brief Test fixture class for @ref CtrlAgentCommandMgr.
  34. ///
  35. /// @todo Add tests for various commands, including the cases when the
  36. /// commands are forwarded to other servers via unix sockets.
  37. /// Meanwhile, this is just a placeholder for the tests.
  38. class CtrlAgentCommandMgrTest : public DControllerTest {
  39. public:
  40. /// @brief Constructor.
  41. ///
  42. /// Deregisters all commands except 'list-commands'.
  43. CtrlAgentCommandMgrTest()
  44. : DControllerTest(CtrlAgentController::instance),
  45. mgr_(CtrlAgentCommandMgr::instance()) {
  46. mgr_.deregisterAll();
  47. removeUnixSocketFile();
  48. initProcess();
  49. }
  50. /// @brief Destructor.
  51. ///
  52. /// Deregisters all commands except 'list-commands'.
  53. virtual ~CtrlAgentCommandMgrTest() {
  54. mgr_.deregisterAll();
  55. removeUnixSocketFile();
  56. }
  57. /// @brief Verifies received answer
  58. ///
  59. /// @todo Add better checks for failure cases and for
  60. /// verification of the response parameters.
  61. ///
  62. /// @param answer answer to be verified
  63. /// @param expected_code0 code expected to be returned in first result within
  64. /// the answer.
  65. /// @param expected_code1 code expected to be returned in second result within
  66. /// the answer.
  67. /// @param expected_code2 code expected to be returned in third result within
  68. /// the answer.
  69. void checkAnswer(const ConstElementPtr& answer, const int expected_code0 = 0,
  70. const int expected_code1 = -1, const int expected_code2 = -1) {
  71. std::vector<int> expected_codes;
  72. if (expected_code0 >= 0) {
  73. expected_codes.push_back(expected_code0);
  74. }
  75. if (expected_code1 >= 0) {
  76. expected_codes.push_back(expected_code1);
  77. }
  78. if (expected_code2 >= 0) {
  79. expected_codes.push_back(expected_code2);
  80. }
  81. int status_code;
  82. // There may be multiple answers returned within a list.
  83. std::vector<ElementPtr> answer_list = answer->listValue();
  84. ASSERT_EQ(expected_codes.size(), answer_list.size());
  85. // Check all answers.
  86. for (auto ans = answer_list.cbegin(); ans != answer_list.cend();
  87. ++ans) {
  88. ConstElementPtr text;
  89. ASSERT_NO_THROW(text = isc::config::parseAnswer(status_code, *ans));
  90. EXPECT_EQ(expected_codes[std::distance(answer_list.cbegin(), ans)],
  91. status_code)
  92. << "answer contains text: " << text->stringValue();
  93. }
  94. }
  95. /// @brief Returns socket file path.
  96. ///
  97. /// If the KEA_SOCKET_TEST_DIR environment variable is specified, the
  98. /// socket file is created in the location pointed to by this variable.
  99. /// Otherwise, it is created in the build directory.
  100. static std::string unixSocketFilePath() {
  101. std::ostringstream s;
  102. const char* env = getenv("KEA_SOCKET_TEST_DIR");
  103. if (env) {
  104. s << std::string(env);
  105. } else {
  106. s << TEST_DATA_BUILDDIR;
  107. }
  108. s << "/" << TEST_SOCKET;
  109. return (s.str());
  110. }
  111. /// @brief Removes unix socket descriptor.
  112. void removeUnixSocketFile() {
  113. static_cast<void>(remove(unixSocketFilePath().c_str()));
  114. }
  115. /// @brief Returns pointer to CtrlAgentProcess instance.
  116. CtrlAgentProcessPtr getCtrlAgentProcess() {
  117. return (boost::dynamic_pointer_cast<CtrlAgentProcess>(getProcess()));
  118. }
  119. /// @brief Returns pointer to CtrlAgentCfgMgr instance for a process.
  120. CtrlAgentCfgMgrPtr getCtrlAgentCfgMgr() {
  121. CtrlAgentCfgMgrPtr p;
  122. if (getCtrlAgentProcess()) {
  123. p = getCtrlAgentProcess()->getCtrlAgentCfgMgr();
  124. }
  125. return (p);
  126. }
  127. /// @brief Returns a pointer to the configuration context.
  128. CtrlAgentCfgContextPtr getCtrlAgentCfgContext() {
  129. CtrlAgentCfgContextPtr p;
  130. if (getCtrlAgentCfgMgr()) {
  131. p = getCtrlAgentCfgMgr()->getCtrlAgentCfgContext();
  132. }
  133. return (p);
  134. }
  135. /// @brief Adds configuration of the control socket.
  136. ///
  137. /// @param server_type Server type for which socket configuration is to
  138. /// be added.
  139. void
  140. configureControlSocket(const CtrlAgentCfgContext::ServerType& server_type) {
  141. CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
  142. ASSERT_TRUE(ctx);
  143. ElementPtr control_socket = Element::createMap();
  144. control_socket->set("socket-name",
  145. Element::create(unixSocketFilePath()));
  146. ctx->setControlSocketInfo(control_socket, server_type);
  147. }
  148. /// @brief Create and bind server side socket.
  149. ///
  150. /// @param response Stub response to be sent from the server socket to the
  151. /// client.
  152. /// @param use_thread Indicates if the IO service will be ran in thread.
  153. void bindServerSocket(const std::string& response,
  154. const bool use_thread = false) {
  155. server_socket_.reset(new test::TestServerUnixSocket(*getIOService(),
  156. unixSocketFilePath(),
  157. response));
  158. server_socket_->startTimer(TEST_TIMEOUT);
  159. server_socket_->bindServerSocket(use_thread);
  160. }
  161. /// @brief Creates command with no arguments.
  162. ///
  163. /// @param command_name Command name.
  164. /// @param service Service value to be added to the command. This value is
  165. /// specified as a list of comma separated values, e.g. "dhcp4, dhcp6".
  166. ///
  167. /// @return Pointer to the instance of the created command.
  168. ConstElementPtr createCommand(const std::string& command_name,
  169. const std::string& service) {
  170. ElementPtr command = Element::createMap();
  171. command->set("command", Element::create(command_name));
  172. // Only add the 'service' parameter if non-empty.
  173. if (!service.empty()) {
  174. std::string s = boost::replace_all_copy(service, ",", "\",\"");
  175. s = std::string("[ \"") + s + std::string("\" ]");
  176. command->set("service", Element::fromJSON(s));
  177. }
  178. command->set("arguments", Element::createMap());
  179. return (command);
  180. }
  181. /// @brief Test forwarding the command.
  182. ///
  183. /// @param server_type Server for which the client socket should be
  184. /// configured.
  185. /// @param service Service to be included in the command.
  186. /// @param expected_result0 Expected first result in response from the server.
  187. /// @param expected_result1 Expected second result in response from the server.
  188. /// @param expected_result2 Expected third result in response from the server.
  189. /// server socket after which the IO service should be stopped.
  190. /// @param expected_responses Number of responses after which the test finishes.
  191. /// @param server_response Stub response to be sent by the server.
  192. void testForward(const CtrlAgentCfgContext::ServerType& server_type,
  193. const std::string& service,
  194. const int expected_result0,
  195. const int expected_result1 = -1,
  196. const int expected_result2 = -1,
  197. const size_t expected_responses = 1,
  198. const std::string& server_response = "{ \"result\": 0 }") {
  199. // Configure client side socket.
  200. configureControlSocket(server_type);
  201. // Create server side socket.
  202. bindServerSocket(server_response, true);
  203. // The client side communication is synchronous. To be able to respond
  204. // to this we need to run the server side socket at the same time as the
  205. // client. Running IO service in a thread guarantees that the server
  206. //responds as soon as it receives the control command.
  207. isc::util::thread::Thread th(boost::bind(&IOService::run,
  208. getIOService().get()));
  209. // Wait for the IO service in thread to actually run.
  210. server_socket_->waitForRunning();
  211. ConstElementPtr command = createCommand("foo", service);
  212. ConstElementPtr answer = mgr_.handleCommand("foo", ConstElementPtr(),
  213. command);
  214. // Cancel all asynchronous operations and let the handlers to be invoked
  215. // with operation_aborted error code.
  216. server_socket_->stopServer();
  217. getIOService()->stopWork();
  218. // Wait for the thread to finish.
  219. th.wait();
  220. EXPECT_EQ(expected_responses, server_socket_->getResponseNum());
  221. checkAnswer(answer, expected_result0, expected_result1, expected_result2);
  222. }
  223. /// @brief a convenience reference to control agent command manager
  224. CtrlAgentCommandMgr& mgr_;
  225. /// @brief Pointer to the test server unix socket.
  226. test::TestServerUnixSocketPtr server_socket_;
  227. };
  228. /// Just a basic test checking that non-existent command is handled
  229. /// properly.
  230. TEST_F(CtrlAgentCommandMgrTest, bogus) {
  231. ConstElementPtr answer;
  232. EXPECT_NO_THROW(answer = mgr_.handleCommand("fish-and-chips-please",
  233. ConstElementPtr(),
  234. ConstElementPtr()));
  235. checkAnswer(answer, isc::config::CONTROL_RESULT_COMMAND_UNSUPPORTED);
  236. };
  237. /// Just a basic test checking that 'list-commands' is supported.
  238. TEST_F(CtrlAgentCommandMgrTest, listCommands) {
  239. ConstElementPtr answer;
  240. EXPECT_NO_THROW(answer = mgr_.handleCommand("list-commands",
  241. ConstElementPtr(),
  242. ConstElementPtr()));
  243. checkAnswer(answer, isc::config::CONTROL_RESULT_SUCCESS);
  244. };
  245. /// Check that control command is successfully forwarded to the DHCPv4 server.
  246. TEST_F(CtrlAgentCommandMgrTest, forwardToDHCPv4Server) {
  247. testForward(CtrlAgentCfgContext::TYPE_DHCP4, "dhcp4",
  248. isc::config::CONTROL_RESULT_SUCCESS);
  249. }
  250. /// Check that control command is successfully forwarded to the DHCPv6 server.
  251. TEST_F(CtrlAgentCommandMgrTest, forwardToDHCPv6Server) {
  252. testForward(CtrlAgentCfgContext::TYPE_DHCP6, "dhcp6",
  253. isc::config::CONTROL_RESULT_SUCCESS);
  254. }
  255. /// Check that the same command is forwarded to multiple servers.
  256. TEST_F(CtrlAgentCommandMgrTest, forwardToBothDHCPServers) {
  257. configureControlSocket(CtrlAgentCfgContext::TYPE_DHCP6);
  258. testForward(CtrlAgentCfgContext::TYPE_DHCP4, "dhcp4,dhcp6",
  259. isc::config::CONTROL_RESULT_SUCCESS,
  260. isc::config::CONTROL_RESULT_SUCCESS,
  261. -1, 2);
  262. }
  263. /// Check that the command may forwarded to the second server even if
  264. /// forwarding to a first server fails.
  265. TEST_F(CtrlAgentCommandMgrTest, failForwardToServer) {
  266. testForward(CtrlAgentCfgContext::TYPE_DHCP6, "dhcp4,dhcp6",
  267. isc::config::CONTROL_RESULT_ERROR,
  268. isc::config::CONTROL_RESULT_SUCCESS);
  269. }
  270. /// Check that control command is not forwarded if the service is not specified.
  271. TEST_F(CtrlAgentCommandMgrTest, noService) {
  272. testForward(CtrlAgentCfgContext::TYPE_DHCP6, "",
  273. isc::config::CONTROL_RESULT_COMMAND_UNSUPPORTED,
  274. -1, -1, 0);
  275. }
  276. /// Check that error is returned to the client when the server to which the
  277. /// command was forwarded sent an invalid message.
  278. TEST_F(CtrlAgentCommandMgrTest, invalidAnswer) {
  279. testForward(CtrlAgentCfgContext::TYPE_DHCP6, "dhcp6",
  280. isc::config::CONTROL_RESULT_ERROR, -1, -1, 1,
  281. "{ \"result\": }");
  282. }
  283. /// Check that connection is dropped if it takes too long. The test checks
  284. /// client's behavior when partial JSON is returned. Client will be waiting
  285. /// for the '}' and will timeout because it is never received.
  286. /// @todo Currently this test is disabled because we don't have configurable
  287. /// timeout value. It is hardcoded to 5 sec, which is too long for the
  288. /// unit test to run.
  289. TEST_F(CtrlAgentCommandMgrTest, DISABLED_connectionTimeout) {
  290. testForward(CtrlAgentCfgContext::TYPE_DHCP6, "dhcp6",
  291. isc::config::CONTROL_RESULT_ERROR, -1, -1, 1,
  292. "{ \"result\": 0");
  293. }
  294. /// Check that error is returned to the client if the forwarding socket is
  295. /// not configured for the given service.
  296. TEST_F(CtrlAgentCommandMgrTest, noClientSocket) {
  297. ConstElementPtr command = createCommand("foo", "dhcp4");
  298. ConstElementPtr answer = mgr_.handleCommand("foo", ConstElementPtr(),
  299. command);
  300. checkAnswer(answer, isc::config::CONTROL_RESULT_ERROR);
  301. }
  302. /// Check that error is returned to the client if the remote server to
  303. /// which the control command is to be forwarded is not available.
  304. TEST_F(CtrlAgentCommandMgrTest, noServerSocket) {
  305. configureControlSocket(CtrlAgentCfgContext::TYPE_DHCP6);
  306. ConstElementPtr command = createCommand("foo", "dhcp6");
  307. ConstElementPtr answer = mgr_.handleCommand("foo", ConstElementPtr(),
  308. command);
  309. checkAnswer(answer, isc::config::CONTROL_RESULT_ERROR);
  310. }
  311. // Check that list-commands command is forwarded when the service
  312. // value is specified.
  313. TEST_F(CtrlAgentCommandMgrTest, forwardListCommands) {
  314. // Configure client side socket.
  315. configureControlSocket(CtrlAgentCfgContext::TYPE_DHCP4);
  316. // Create server side socket.
  317. bindServerSocket("{ \"result\" : 3 }", true);
  318. // The client side communication is synchronous. To be able to respond
  319. // to this we need to run the server side socket at the same time.
  320. // Running IO service in a thread guarantees that the server responds
  321. // as soon as it receives the control command.
  322. isc::util::thread::Thread th(boost::bind(&IOService::run, getIOService().get()));
  323. // Wait for the IO service in thread to actually run.
  324. server_socket_->waitForRunning();
  325. ConstElementPtr command = createCommand("list-commands", "dhcp4");
  326. ConstElementPtr answer = mgr_.handleCommand("list-commands", ConstElementPtr(),
  327. command);
  328. // Cancel all asynchronous operations and let the handlers to be invoked
  329. // with operation_aborted error code.
  330. server_socket_->stopServer();
  331. getIOService()->stopWork();
  332. // Wait for the thread to finish.
  333. th.wait();
  334. // Answer of 3 is specific to the stub response we send when the
  335. // command is forwarded. So having this value returned means that
  336. // the command was forwarded as expected.
  337. checkAnswer(answer, 3);
  338. }
  339. }