ca_controller_unittests.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. // Copyright (C) 2016-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_controller.h>
  8. #include <agent/ca_process.h>
  9. #include <agent/ca_command_mgr.h>
  10. #include <cc/data.h>
  11. #include <cc/command_interpreter.h>
  12. #include <process/testutils/d_test_stubs.h>
  13. #include <boost/pointer_cast.hpp>
  14. #include <sstream>
  15. using namespace std;
  16. using namespace isc::agent;
  17. using namespace isc::data;
  18. using namespace isc::http;
  19. using namespace isc::process;
  20. using namespace boost::posix_time;
  21. namespace {
  22. /// @brief Valid Control Agent Config used in tests.
  23. const char* valid_agent_config =
  24. "{"
  25. " \"http-host\": \"127.0.0.1\","
  26. " \"http-port\": 8081,"
  27. " \"control-sockets\": {"
  28. " \"dhcp4\": {"
  29. " \"socket-type\": \"unix\","
  30. " \"socket-name\": \"/first/dhcp4/socket\""
  31. " },"
  32. " \"dhcp6\": {"
  33. " \"socket-type\": \"unix\","
  34. " \"socket-name\": \"/first/dhcp6/socket\""
  35. " }"
  36. " }"
  37. "}";
  38. /// @brief test fixture class for testing CtrlAgentController class. This
  39. /// class derives from DControllerTest and wraps CtrlAgentController. Much
  40. /// of the underlying functionality is in the DControllerBase class which
  41. /// has extensive set of unit tests that are independent from the Control
  42. /// Agent.
  43. class CtrlAgentControllerTest : public DControllerTest {
  44. public:
  45. /// @brief Constructor.
  46. CtrlAgentControllerTest()
  47. : DControllerTest(CtrlAgentController::instance) {
  48. }
  49. /// @brief Returns pointer to CtrlAgentProcess instance.
  50. CtrlAgentProcessPtr getCtrlAgentProcess() {
  51. return (boost::dynamic_pointer_cast<CtrlAgentProcess>(getProcess()));
  52. }
  53. /// @brief Returns pointer to CtrlAgentCfgMgr instance for a process.
  54. CtrlAgentCfgMgrPtr getCtrlAgentCfgMgr() {
  55. CtrlAgentCfgMgrPtr p;
  56. if (getCtrlAgentProcess()) {
  57. p = getCtrlAgentProcess()->getCtrlAgentCfgMgr();
  58. }
  59. return (p);
  60. }
  61. /// @brief Returns a pointer to the configuration context.
  62. CtrlAgentCfgContextPtr getCtrlAgentCfgContext() {
  63. CtrlAgentCfgContextPtr p;
  64. if (getCtrlAgentCfgMgr()) {
  65. p = getCtrlAgentCfgMgr()->getCtrlAgentCfgContext();
  66. }
  67. return (p);
  68. }
  69. /// @brief Tests that socket info structure contains 'unix' socket-type
  70. /// value and the expected socket-name.
  71. ///
  72. /// @param service Service type.
  73. /// @param exp_socket_name Expected socket name.
  74. void testUnixSocketInfo(const std::string& service,
  75. const std::string& exp_socket_name) {
  76. CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
  77. ASSERT_TRUE(ctx);
  78. ConstElementPtr sock_info = ctx->getControlSocketInfo(service);
  79. ASSERT_TRUE(sock_info);
  80. ASSERT_TRUE(sock_info->contains("socket-type"));
  81. EXPECT_EQ("unix", sock_info->get("socket-type")->stringValue());
  82. ASSERT_TRUE(sock_info->contains("socket-name"));
  83. EXPECT_EQ(exp_socket_name,
  84. sock_info->get("socket-name")->stringValue());
  85. }
  86. /// @brief Compares the status in the given parse result to a given value.
  87. ///
  88. /// @param answer Element set containing an integer response and string
  89. /// comment.
  90. /// @param exp_status is an integer against which to compare the status.
  91. /// @param exp_txt is expected text (not checked if "")
  92. ///
  93. void checkAnswer(isc::data::ConstElementPtr answer,
  94. int exp_status,
  95. string exp_txt = "") {
  96. // Get rid of the outer list.
  97. ASSERT_TRUE(answer);
  98. ASSERT_EQ(Element::list, answer->getType());
  99. ASSERT_LE(1, answer->size());
  100. answer = answer->get(0);
  101. int rcode = 0;
  102. isc::data::ConstElementPtr comment;
  103. comment = isc::config::parseAnswer(rcode, answer);
  104. if (rcode != exp_status) {
  105. ADD_FAILURE() << "Expected status code " << exp_status
  106. << " but received " << rcode << ", comment: "
  107. << (comment ? comment->str() : "(none)");
  108. }
  109. // Ok, parseAnswer interface is weird. If there are no arguments,
  110. // it returns content of text. But if there is an argument,
  111. // it returns the argument and it's not possible to retrieve
  112. // "text" (i.e. comment).
  113. if (comment->getType() != Element::string) {
  114. comment = answer->get("text");
  115. }
  116. if (!exp_txt.empty()) {
  117. EXPECT_EQ(exp_txt, comment->stringValue());
  118. }
  119. }
  120. /// @brief Checks whether specified command is registered
  121. ///
  122. /// @param name name of the command to be checked
  123. /// @param expect_true true - must be registered, false - must not be
  124. void checkCommandRegistered(const std::string& name, bool expect_true = true) {
  125. // First get the list of registered commands
  126. ConstElementPtr lst = Element::fromJSON("{ \"command\": \"list-commands\" }");
  127. ConstElementPtr rsp = CtrlAgentCommandMgr::instance().processCommand(lst);
  128. // The response must be an array with at least one element
  129. ASSERT_TRUE(rsp);
  130. ASSERT_EQ(Element::list, rsp->getType());
  131. ASSERT_LE(1, rsp->size());
  132. ConstElementPtr args = rsp->get(0)->get("arguments");
  133. ASSERT_TRUE(args);
  134. string args_txt = args->str();
  135. if (expect_true) {
  136. EXPECT_TRUE(args_txt.find(name) != string::npos);
  137. } else {
  138. EXPECT_TRUE(args_txt.find(name) == string::npos);
  139. }
  140. }
  141. };
  142. // Basic Controller instantiation testing.
  143. // Verifies that the controller singleton gets created and that the
  144. // basic derivation from the base class is intact.
  145. TEST_F(CtrlAgentControllerTest, basicInstanceTesting) {
  146. // Verify the we can the singleton instance can be fetched and that
  147. // it is the correct type.
  148. DControllerBasePtr& controller = DControllerTest::getController();
  149. ASSERT_TRUE(controller);
  150. ASSERT_NO_THROW(boost::dynamic_pointer_cast<CtrlAgentController>(controller));
  151. // Verify that controller's app name is correct.
  152. EXPECT_TRUE(checkAppName(CtrlAgentController::agent_app_name_));
  153. // Verify that controller's bin name is correct.
  154. EXPECT_TRUE(checkBinName(CtrlAgentController::agent_bin_name_));
  155. // Verify that controller's IOService exists.
  156. EXPECT_TRUE(checkIOService());
  157. // Verify that the Process does NOT exist.
  158. EXPECT_FALSE(checkProcess());
  159. }
  160. // Tests basic command line processing.
  161. // Verifies that:
  162. // 1. Standard command line options are supported.
  163. // 2. Invalid options are detected.
  164. TEST_F(CtrlAgentControllerTest, commandLineArgs) {
  165. char* argv[] = { const_cast<char*>("progName"),
  166. const_cast<char*>("-c"),
  167. const_cast<char*>(DControllerTest::CFG_TEST_FILE),
  168. const_cast<char*>("-d") };
  169. int argc = 4;
  170. // Verify that verbose flag is false initially.
  171. EXPECT_TRUE(checkVerbose(false));
  172. // Verify that standard options can be parsed without error.
  173. EXPECT_NO_THROW(parseArgs(argc, argv));
  174. // Verify that verbose flag is true.
  175. EXPECT_TRUE(checkVerbose(true));
  176. // Verify configuration file name is correct.
  177. EXPECT_TRUE(checkConfigFileName(DControllerTest::CFG_TEST_FILE));
  178. // Verify that an unknown option is detected.
  179. char* argv2[] = { const_cast<char*>("progName"),
  180. const_cast<char*>("-x") };
  181. argc = 2;
  182. EXPECT_THROW(parseArgs(argc, argv2), InvalidUsage);
  183. }
  184. // Tests application process creation and initialization.
  185. // Verifies that the process can be successfully created and initialized.
  186. TEST_F(CtrlAgentControllerTest, initProcessTesting) {
  187. ASSERT_NO_THROW(initProcess());
  188. EXPECT_TRUE(checkProcess());
  189. }
  190. // Tests launch and normal shutdown (stand alone mode).
  191. // This creates an interval timer to generate a normal shutdown and then
  192. // launches with a valid, stand-alone command line and no simulated errors.
  193. TEST_F(CtrlAgentControllerTest, launchNormalShutdown) {
  194. // Write valid_agent_config and then run launch() for 1000 ms.
  195. time_duration elapsed_time;
  196. runWithConfig(valid_agent_config, 1000, elapsed_time);
  197. // Give a generous margin to accommodate slower test environs.
  198. EXPECT_TRUE(elapsed_time.total_milliseconds() >= 800 &&
  199. elapsed_time.total_milliseconds() <= 1300);
  200. }
  201. // Tests that the SIGINT triggers a normal shutdown.
  202. TEST_F(CtrlAgentControllerTest, sigintShutdown) {
  203. // Setup to raise SIGHUP in 1 ms.
  204. TimedSignal sighup(*getIOService(), SIGINT, 1);
  205. // Write valid_agent_config and then run launch() for a maximum
  206. // of 1000 ms.
  207. time_duration elapsed_time;
  208. runWithConfig(valid_agent_config, 1000, elapsed_time);
  209. // Signaled shutdown should make our elapsed time much smaller than
  210. // the maximum run time. Give generous margin to accommodate slow
  211. // test environs.
  212. EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
  213. }
  214. // Tests that the SIGTERM triggers a normal shutdown.
  215. TEST_F(CtrlAgentControllerTest, sigtermShutdown) {
  216. // Setup to raise SIGHUP in 1 ms.
  217. TimedSignal sighup(*getIOService(), SIGTERM, 1);
  218. // Write valid_agent_config and then run launch() for a maximum of 1 s.
  219. time_duration elapsed_time;
  220. runWithConfig(valid_agent_config, 1000, elapsed_time);
  221. // Signaled shutdown should make our elapsed time much smaller than
  222. // the maximum run time. Give generous margin to accommodate slow
  223. // test environs.
  224. EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
  225. }
  226. // Tests that the sockets settings are updated upon successful reconfiguration.
  227. TEST_F(CtrlAgentControllerTest, successfulConfigUpdate) {
  228. // This configuration should be used to override the initial conifguration.
  229. const char* second_config =
  230. "{"
  231. " \"http-host\": \"127.0.0.1\","
  232. " \"http-port\": 8080,"
  233. " \"control-sockets\": {"
  234. " \"dhcp4\": {"
  235. " \"socket-type\": \"unix\","
  236. " \"socket-name\": \"/second/dhcp4/socket\""
  237. " },"
  238. " \"dhcp6\": {"
  239. " \"socket-type\": \"unix\","
  240. " \"socket-name\": \"/second/dhcp6/socket\""
  241. " }"
  242. " }"
  243. "}";
  244. // Schedule reconfiguration.
  245. scheduleTimedWrite(second_config, 100);
  246. // Schedule SIGHUP signal to trigger reconfiguration.
  247. TimedSignal sighup(*getIOService(), SIGHUP, 200);
  248. // Start the server.
  249. time_duration elapsed_time;
  250. runWithConfig(valid_agent_config, 500, elapsed_time);
  251. CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
  252. ASSERT_TRUE(ctx);
  253. // The server should now hold the new listener configuration.
  254. EXPECT_EQ("127.0.0.1", ctx->getHttpHost());
  255. EXPECT_EQ(8080, ctx->getHttpPort());
  256. // The forwarding configuration should have been updated too.
  257. testUnixSocketInfo("dhcp4", "/second/dhcp4/socket");
  258. testUnixSocketInfo("dhcp6", "/second/dhcp6/socket");
  259. CtrlAgentProcessPtr process = getCtrlAgentProcess();
  260. ASSERT_TRUE(process);
  261. // Check that the HTTP listener still exists after reconfiguration.
  262. ConstHttpListenerPtr listener = process->getHttpListener();
  263. ASSERT_TRUE(listener);
  264. EXPECT_TRUE(process->isListening());
  265. // The listener should have been reconfigured to use new address and port.
  266. EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText());
  267. EXPECT_EQ(8080, listener->getLocalPort());
  268. }
  269. // Tests that the server continues to use an old configuration when the listener
  270. // reconfiguration is unsuccessful.
  271. TEST_F(CtrlAgentControllerTest, unsuccessfulConfigUpdate) {
  272. // This is invalid configuration. We're using restricted port number and
  273. // IP address of 1.1.1.1.
  274. const char* second_config =
  275. "{"
  276. " \"http-host\": \"1.1.1.1\","
  277. " \"http-port\": 1,"
  278. " \"control-sockets\": {"
  279. " \"dhcp4\": {"
  280. " \"socket-type\": \"unix\","
  281. " \"socket-name\": \"/second/dhcp4/socket\""
  282. " },"
  283. " \"dhcp6\": {"
  284. " \"socket-type\": \"unix\","
  285. " \"socket-name\": \"/second/dhcp6/socket\""
  286. " }"
  287. " }"
  288. "}";
  289. // Schedule reconfiguration.
  290. scheduleTimedWrite(second_config, 100);
  291. // Schedule SIGHUP signal to trigger reconfiguration.
  292. TimedSignal sighup(*getIOService(), SIGHUP, 200);
  293. // Start the server.
  294. time_duration elapsed_time;
  295. runWithConfig(valid_agent_config, 500, elapsed_time);
  296. CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
  297. ASSERT_TRUE(ctx);
  298. // The reconfiguration should have been unsuccessful, and the server should
  299. // still use the original configuration.
  300. EXPECT_EQ("127.0.0.1", ctx->getHttpHost());
  301. EXPECT_EQ(8081, ctx->getHttpPort());
  302. // Same for forwarding.
  303. testUnixSocketInfo("dhcp4", "/first/dhcp4/socket");
  304. testUnixSocketInfo("dhcp6", "/first/dhcp6/socket");
  305. CtrlAgentProcessPtr process = getCtrlAgentProcess();
  306. ASSERT_TRUE(process);
  307. // We should still be using an original listener.
  308. ConstHttpListenerPtr listener = process->getHttpListener();
  309. ASSERT_TRUE(listener);
  310. EXPECT_TRUE(process->isListening());
  311. EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText());
  312. EXPECT_EQ(8081, listener->getLocalPort());
  313. }
  314. // Tests that it is possible to update the configuration in such a way that the
  315. // listener configuration remains the same. The server should continue using the
  316. // listener instance it has been using prior to the reconfiguration.
  317. TEST_F(CtrlAgentControllerTest, noListenerChange) {
  318. // This configuration should be used to override the initial conifguration.
  319. const char* second_config =
  320. "{"
  321. " \"http-host\": \"127.0.0.1\","
  322. " \"http-port\": 8081,"
  323. " \"control-sockets\": {"
  324. " \"dhcp4\": {"
  325. " \"socket-type\": \"unix\","
  326. " \"socket-name\": \"/second/dhcp4/socket\""
  327. " },"
  328. " \"dhcp6\": {"
  329. " \"socket-type\": \"unix\","
  330. " \"socket-name\": \"/second/dhcp6/socket\""
  331. " }"
  332. " }"
  333. "}";
  334. // Schedule reconfiguration.
  335. scheduleTimedWrite(second_config, 100);
  336. // Schedule SIGHUP signal to trigger reconfiguration.
  337. TimedSignal sighup(*getIOService(), SIGHUP, 200);
  338. // Start the server.
  339. time_duration elapsed_time;
  340. runWithConfig(valid_agent_config, 500, elapsed_time);
  341. CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
  342. ASSERT_TRUE(ctx);
  343. // The server should use a correct listener configuration.
  344. EXPECT_EQ("127.0.0.1", ctx->getHttpHost());
  345. EXPECT_EQ(8081, ctx->getHttpPort());
  346. // The forwarding configuration should have been updated.
  347. testUnixSocketInfo("dhcp4", "/second/dhcp4/socket");
  348. testUnixSocketInfo("dhcp6", "/second/dhcp6/socket");
  349. CtrlAgentProcessPtr process = getCtrlAgentProcess();
  350. ASSERT_TRUE(process);
  351. // The listener should keep listening.
  352. ConstHttpListenerPtr listener = process->getHttpListener();
  353. ASSERT_TRUE(listener);
  354. EXPECT_TRUE(process->isListening());
  355. EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText());
  356. EXPECT_EQ(8081, listener->getLocalPort());
  357. }
  358. // Tests that registerCommands actually registers anything.
  359. TEST_F(CtrlAgentControllerTest, registeredCommands) {
  360. ASSERT_NO_THROW(initProcess());
  361. EXPECT_TRUE(checkProcess());
  362. // The framework available makes it very difficult to test the actual
  363. // code as CtrlAgentController is not initialized the same way it is
  364. // in production code. In particular, the way CtrlAgentController
  365. // is initialized in tests does not call registerCommands().
  366. // This is a crude workaround for this problem. Proper solution shoul
  367. // be developed sooner rather than later.
  368. const DControllerBasePtr& base = getController();
  369. const CtrlAgentControllerPtr& ctrl =
  370. boost::dynamic_pointer_cast<CtrlAgentController>(base);
  371. ASSERT_TRUE(ctrl);
  372. ctrl->registerCommands();
  373. // Check that the following command are really available.
  374. checkCommandRegistered("build-report");
  375. checkCommandRegistered("config-get");
  376. checkCommandRegistered("config-test");
  377. checkCommandRegistered("config-write");
  378. checkCommandRegistered("list-commands");
  379. checkCommandRegistered("shutdown");
  380. checkCommandRegistered("version-get");
  381. ctrl->deregisterCommands();
  382. }
  383. // Tests that config-write really writes a config file that contains
  384. // Control-agent configuration and not some other random nonsense.
  385. TEST_F(CtrlAgentControllerTest, configWrite) {
  386. ASSERT_NO_THROW(initProcess());
  387. EXPECT_TRUE(checkProcess());
  388. // The framework available makes it very difficult to test the actual
  389. // code as CtrlAgentController is not initialized the same way it is
  390. // in production code. In particular, the way CtrlAgentController
  391. // is initialized in tests does not call registerCommands().
  392. // This is a crude workaround for this problem. Proper solution shoul
  393. // be developed sooner rather than later.
  394. const DControllerBasePtr& base = getController();
  395. const CtrlAgentControllerPtr& ctrl
  396. = boost::dynamic_pointer_cast<CtrlAgentController>(base);
  397. ASSERT_TRUE(ctrl);
  398. // Now clean up after ourselves.
  399. ctrl->registerCommands();
  400. // First, build the command:
  401. string file = string(TEST_DATA_BUILDDIR) + string("/config-write.json");
  402. string cmd_txt = "{ \"command\": \"config-write\" }";
  403. ConstElementPtr cmd = Element::fromJSON(cmd_txt);
  404. ConstElementPtr params = Element::fromJSON("{\"filename\": \"" + file + "\" }");
  405. CtrlAgentCommandMgr& mgr_ = CtrlAgentCommandMgr::instance();
  406. // Send the command
  407. ConstElementPtr answer = mgr_.handleCommand("config-write", params, cmd);
  408. // Check that the command was successful
  409. checkAnswer(answer, isc::config::CONTROL_RESULT_SUCCESS);
  410. // Now check that the file is there.
  411. ifstream f(file.c_str());
  412. ASSERT_TRUE(f.good());
  413. // Now that's some rough check that the the config written really contains
  414. // something that looks like Control-agent configuration.
  415. ConstElementPtr from_file = Element::fromJSONFile(file, true);
  416. ASSERT_TRUE(from_file);
  417. ConstElementPtr ca = from_file->get("Control-agent");
  418. ASSERT_TRUE(ca);
  419. EXPECT_TRUE(ca->get("control-sockets"));
  420. EXPECT_TRUE(ca->get("hooks-libraries"));
  421. EXPECT_TRUE(ca->get("http-host"));
  422. EXPECT_TRUE(ca->get("http-port"));
  423. // Remove the file.
  424. ::remove(file.c_str());
  425. // Now clean up after ourselves.
  426. ctrl->deregisterCommands();
  427. }
  428. }