ca_command_mgr_unittests.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  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. void bindServerSocket(const std::string& response) {
  153. server_socket_.reset(new test::TestServerUnixSocket(*getIOService(),
  154. unixSocketFilePath(),
  155. TEST_TIMEOUT,
  156. response));
  157. server_socket_->bindServerSocket();
  158. }
  159. /// @brief Creates command with no arguments.
  160. ///
  161. /// @param command_name Command name.
  162. /// @param service Service value to be added to the command. This value is
  163. /// specified as a list of comma separated values, e.g. "dhcp4, dhcp6".
  164. ///
  165. /// @return Pointer to the instance of the created command.
  166. ConstElementPtr createCommand(const std::string& command_name,
  167. const std::string& service) {
  168. ElementPtr command = Element::createMap();
  169. command->set("command", Element::create(command_name));
  170. // Only add the 'service' parameter if non-empty.
  171. if (!service.empty()) {
  172. std::string s = boost::replace_all_copy(service, ",", "\",\"");
  173. s = std::string("[ \"") + s + std::string("\" ]");
  174. command->set("service", Element::fromJSON(s));
  175. }
  176. command->set("arguments", Element::createMap());
  177. return (command);
  178. }
  179. /// @brief Test forwarding the command.
  180. ///
  181. /// @param server_type Server for which the client socket should be
  182. /// configured.
  183. /// @param service Service to be included in the command.
  184. /// @param expected_result0 Expected first result in response from the server.
  185. /// @param expected_result1 Expected second result in response from the server.
  186. /// @param expected_result2 Expected third result in response from the server.
  187. /// server socket after which the IO service should be stopped.
  188. /// @param expected_responses Number of responses after which the test finishes.
  189. /// @param server_response Stub response to be sent by the server.
  190. void testForward(const CtrlAgentCfgContext::ServerType& server_type,
  191. const std::string& service,
  192. const int expected_result0,
  193. const int expected_result1 = -1,
  194. const int expected_result2 = -1,
  195. const size_t expected_responses = 1,
  196. const std::string& server_response = "{ \"result\": 0 }") {
  197. // Configure client side socket.
  198. configureControlSocket(server_type);
  199. // Create server side socket.
  200. bindServerSocket(server_response);
  201. // The client side communication is synchronous. To be able to respond
  202. // to this we need to run the server side socket at the same time.
  203. // Running IO service in a thread guarantees that the server responds
  204. // as soon as it receives the control command.
  205. isc::util::thread::Thread(boost::bind(&CtrlAgentCommandMgrTest::runIO,
  206. getIOService(), server_socket_,
  207. expected_responses));
  208. ConstElementPtr command = createCommand("foo", service);
  209. ConstElementPtr answer = mgr_.handleCommand("foo", ConstElementPtr(),
  210. command);
  211. checkAnswer(answer, expected_result0, expected_result1, expected_result2);
  212. }
  213. /// @brief Runs IO service until number of sent responses is lower than
  214. /// expected.
  215. ///
  216. /// @param server_socket Pointer to the server socket.
  217. /// @param expected_responses Number of expected responses.
  218. static void runIO(IOServicePtr& io_service,
  219. const test::TestServerUnixSocketPtr& server_socket,
  220. const size_t expected_responses) {
  221. while (server_socket->getResponseNum() < expected_responses) {
  222. io_service->run_one();
  223. }
  224. }
  225. CtrlAgentCommandMgrTest* getTestSelf() {
  226. return (this);
  227. }
  228. /// @brief a convenience reference to control agent command manager
  229. CtrlAgentCommandMgr& mgr_;
  230. /// @brief Pointer to the test server unix socket.
  231. test::TestServerUnixSocketPtr server_socket_;
  232. };
  233. /// Just a basic test checking that non-existent command is handled
  234. /// properly.
  235. TEST_F(CtrlAgentCommandMgrTest, bogus) {
  236. ConstElementPtr answer;
  237. EXPECT_NO_THROW(answer = mgr_.handleCommand("fish-and-chips-please",
  238. ConstElementPtr(),
  239. ConstElementPtr()));
  240. checkAnswer(answer, isc::config::CONTROL_RESULT_COMMAND_UNSUPPORTED);
  241. };
  242. /// Just a basic test checking that 'list-commands' is supported.
  243. TEST_F(CtrlAgentCommandMgrTest, listCommands) {
  244. ConstElementPtr answer;
  245. EXPECT_NO_THROW(answer = mgr_.handleCommand("list-commands",
  246. ConstElementPtr(),
  247. ConstElementPtr()));
  248. checkAnswer(answer, isc::config::CONTROL_RESULT_SUCCESS);
  249. };
  250. /// Check that control command is successfully forwarded to the DHCPv4 server.
  251. TEST_F(CtrlAgentCommandMgrTest, forwardToDHCPv4Server) {
  252. testForward(CtrlAgentCfgContext::TYPE_DHCP4, "dhcp4",
  253. isc::config::CONTROL_RESULT_SUCCESS);
  254. }
  255. /// Check that control command is successfully forwarded to the DHCPv6 server.
  256. TEST_F(CtrlAgentCommandMgrTest, forwardToDHCPv6Server) {
  257. testForward(CtrlAgentCfgContext::TYPE_DHCP6, "dhcp6",
  258. isc::config::CONTROL_RESULT_SUCCESS);
  259. }
  260. /// Check that the same command is forwarded to multiple servers.
  261. TEST_F(CtrlAgentCommandMgrTest, forwardToBothDHCPServers) {
  262. configureControlSocket(CtrlAgentCfgContext::TYPE_DHCP6);
  263. testForward(CtrlAgentCfgContext::TYPE_DHCP4, "dhcp4,dhcp6",
  264. isc::config::CONTROL_RESULT_SUCCESS,
  265. isc::config::CONTROL_RESULT_SUCCESS,
  266. -1, 2);
  267. }
  268. /// Check that the command may forwarded to the second server even if
  269. /// forwarding to a first server fails.
  270. TEST_F(CtrlAgentCommandMgrTest, failForwardToServer) {
  271. testForward(CtrlAgentCfgContext::TYPE_DHCP6, "dhcp4,dhcp6",
  272. isc::config::CONTROL_RESULT_ERROR,
  273. isc::config::CONTROL_RESULT_SUCCESS);
  274. }
  275. /// Check that control command is not forwarded if the service is not specified.
  276. TEST_F(CtrlAgentCommandMgrTest, noService) {
  277. testForward(CtrlAgentCfgContext::TYPE_DHCP6, "",
  278. isc::config::CONTROL_RESULT_COMMAND_UNSUPPORTED,
  279. -1, -1, 0);
  280. }
  281. /// Check that error is returned to the client when the server to which the
  282. /// command was forwarded sent an invalid message.
  283. TEST_F(CtrlAgentCommandMgrTest, invalidAnswer) {
  284. testForward(CtrlAgentCfgContext::TYPE_DHCP6, "dhcp6",
  285. isc::config::CONTROL_RESULT_ERROR, -1, -1, 1,
  286. "{ \"result\": 0");
  287. }
  288. /// Check that error is returned to the client if the forwarding socket is
  289. /// not configured for the given service.
  290. TEST_F(CtrlAgentCommandMgrTest, noClientSocket) {
  291. ConstElementPtr command = createCommand("foo", "dhcp4");
  292. ConstElementPtr answer = mgr_.handleCommand("foo", ConstElementPtr(),
  293. command);
  294. checkAnswer(answer, isc::config::CONTROL_RESULT_ERROR);
  295. }
  296. /// Check that error is returned to the client if the remote server to
  297. /// which the control command is to be forwarded is not available.
  298. TEST_F(CtrlAgentCommandMgrTest, noServerSocket) {
  299. configureControlSocket(CtrlAgentCfgContext::TYPE_DHCP6);
  300. ConstElementPtr command = createCommand("foo", "dhcp6");
  301. ConstElementPtr answer = mgr_.handleCommand("foo", ConstElementPtr(),
  302. command);
  303. checkAnswer(answer, isc::config::CONTROL_RESULT_ERROR);
  304. }
  305. // Check that list-commands command is forwarded when the service
  306. // value is specified.
  307. TEST_F(CtrlAgentCommandMgrTest, forwardListCommands) {
  308. // Configure client side socket.
  309. configureControlSocket(CtrlAgentCfgContext::TYPE_DHCP4);
  310. // Create server side socket.
  311. bindServerSocket("{ \"result\" : 3 }");
  312. // The client side communication is synchronous. To be able to respond
  313. // to this we need to run the server side socket at the same time.
  314. // Running IO service in a thread guarantees that the server responds
  315. // as soon as it receives the control command.
  316. isc::util::thread::Thread(boost::bind(&CtrlAgentCommandMgrTest::runIO,
  317. getIOService(), server_socket_, 1));
  318. ConstElementPtr command = createCommand("list-commands", "dhcp4");
  319. ConstElementPtr answer = mgr_.handleCommand("list-commands", ConstElementPtr(),
  320. command);
  321. // Answer of 3 is specific to the stub response we send when the
  322. // command is forwarded. So having this value returned means that
  323. // the command was forwarded as expected.
  324. checkAnswer(answer, 3);
  325. }
  326. }