kea_controller_unittest.cc 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  1. // Copyright (C) 2012-2016 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 <asiolink/io_address.h>
  8. #include <cc/command_interpreter.h>
  9. #include <dhcp/dhcp6.h>
  10. #include <dhcp/duid.h>
  11. #include <dhcp/iface_mgr.h>
  12. #include <dhcp6/ctrl_dhcp6_srv.h>
  13. #include <dhcp6/parser_context.h>
  14. #include <dhcp6/tests/dhcp6_test_utils.h>
  15. #include <dhcpsrv/cfgmgr.h>
  16. #include <dhcpsrv/lease.h>
  17. #include <dhcpsrv/lease_mgr_factory.h>
  18. #include <dhcpsrv/testutils/mysql_schema.h>
  19. #include <log/logger_support.h>
  20. #include <util/stopwatch.h>
  21. #include <boost/scoped_ptr.hpp>
  22. #include <gtest/gtest.h>
  23. #include <fstream>
  24. #include <iostream>
  25. #include <sstream>
  26. #include <arpa/inet.h>
  27. #include <unistd.h>
  28. using namespace std;
  29. using namespace isc;
  30. using namespace isc::asiolink;
  31. using namespace isc::config;
  32. using namespace isc::data;
  33. using namespace isc::dhcp;
  34. using namespace isc::dhcp::test;
  35. using namespace isc::hooks;
  36. namespace {
  37. class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
  38. // "Naked" DHCPv6 server, exposes internal fields
  39. public:
  40. NakedControlledDhcpv6Srv():ControlledDhcpv6Srv(0) { }
  41. using ControlledDhcpv6Srv::signal_handler_;
  42. };
  43. class JSONFileBackendTest : public dhcp::test::BaseServerTest {
  44. public:
  45. JSONFileBackendTest()
  46. : BaseServerTest() {
  47. }
  48. ~JSONFileBackendTest() {
  49. LeaseMgrFactory::destroy();
  50. isc::log::setDefaultLoggingOutput();
  51. static_cast<void>(remove(TEST_FILE));
  52. static_cast<void>(remove(TEST_INCLUDE));
  53. };
  54. void writeFile(const std::string& file_name, const std::string& content) {
  55. static_cast<void>(remove(file_name.c_str()));
  56. ofstream out(file_name.c_str(), ios::trunc);
  57. EXPECT_TRUE(out.is_open());
  58. out << content;
  59. out.close();
  60. }
  61. /// @brief Runs timers for specified time.
  62. ///
  63. /// Internally, this method calls @c IfaceMgr::receive6 to run the
  64. /// callbacks for the installed timers.
  65. ///
  66. /// @param timeout_ms Amount of time after which the method returns.
  67. void runTimersWithTimeout(const long timeout_ms) {
  68. isc::util::Stopwatch stopwatch;
  69. while (stopwatch.getTotalMilliseconds() < timeout_ms) {
  70. // Block for up to one millisecond waiting for the timers'
  71. // callbacks to be executed.
  72. IfaceMgr::instancePtr()->receive6(0, 1000);
  73. }
  74. }
  75. static const char* TEST_FILE;
  76. static const char* TEST_INCLUDE;
  77. };
  78. const char* JSONFileBackendTest::TEST_FILE = "test-config.json";
  79. const char* JSONFileBackendTest::TEST_INCLUDE = "test-include.json";
  80. // This test checks if configuration can be read from a JSON file.
  81. TEST_F(JSONFileBackendTest, jsonFile) {
  82. // Prepare configuration file.
  83. string config = "{ \"Dhcp6\": {"
  84. "\"interfaces-config\": {"
  85. " \"interfaces\": [ \"*\" ]"
  86. "},"
  87. "\"preferred-lifetime\": 3000,"
  88. "\"rebind-timer\": 2000, "
  89. "\"renew-timer\": 1000, "
  90. "\"subnet6\": [ { "
  91. " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
  92. " \"subnet\": \"2001:db8:1::/64\" "
  93. " },"
  94. " {"
  95. " \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
  96. " \"subnet\": \"2001:db8:2::/64\", "
  97. " \"id\": 0"
  98. " },"
  99. " {"
  100. " \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
  101. " \"subnet\": \"2001:db8:3::/64\" "
  102. " } ],"
  103. "\"valid-lifetime\": 4000 }"
  104. "}";
  105. writeFile(TEST_FILE, config);
  106. // Now initialize the server
  107. boost::scoped_ptr<ControlledDhcpv6Srv> srv;
  108. ASSERT_NO_THROW(
  109. srv.reset(new ControlledDhcpv6Srv(0))
  110. );
  111. // And configure it using the config file.
  112. EXPECT_NO_THROW(srv->init(TEST_FILE));
  113. // Now check if the configuration has been applied correctly.
  114. const Subnet6Collection* subnets =
  115. CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
  116. ASSERT_TRUE(subnets);
  117. ASSERT_EQ(3, subnets->size()); // We expect 3 subnets.
  118. // Check subnet 1.
  119. EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText());
  120. EXPECT_EQ(64, subnets->at(0)->get().second);
  121. // Check pools in the first subnet.
  122. const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
  123. ASSERT_EQ(1, pools1.size());
  124. EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
  125. EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
  126. EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
  127. // Check subnet 2.
  128. EXPECT_EQ("2001:db8:2::", subnets->at(1)->get().first.toText());
  129. EXPECT_EQ(64, subnets->at(1)->get().second);
  130. // Check pools in the second subnet.
  131. const PoolCollection& pools2 = subnets->at(1)->getPools(Lease::TYPE_NA);
  132. ASSERT_EQ(1, pools2.size());
  133. EXPECT_EQ("2001:db8:2::", pools2.at(0)->getFirstAddress().toText());
  134. EXPECT_EQ("2001:db8:2::ffff:ffff:ffff", pools2.at(0)->getLastAddress().toText());
  135. EXPECT_EQ(Lease::TYPE_NA, pools2.at(0)->getType());
  136. // And finally check subnet 3.
  137. EXPECT_EQ("2001:db8:3::", subnets->at(2)->get().first.toText());
  138. EXPECT_EQ(64, subnets->at(2)->get().second);
  139. // ... and it's only pool.
  140. const PoolCollection& pools3 = subnets->at(2)->getPools(Lease::TYPE_NA);
  141. EXPECT_EQ("2001:db8:3::", pools3.at(0)->getFirstAddress().toText());
  142. EXPECT_EQ("2001:db8:3::ffff:ffff:ffff", pools3.at(0)->getLastAddress().toText());
  143. EXPECT_EQ(Lease::TYPE_NA, pools3.at(0)->getType());
  144. }
  145. // This test checks if configuration can be read from a JSON file
  146. // using hash (#) line comments
  147. TEST_F(JSONFileBackendTest, hashComments) {
  148. string config_hash_comments = "# This is a comment. It should be \n"
  149. "#ignored. Real config starts in line below\n"
  150. "{ \"Dhcp6\": {"
  151. "\"interfaces-config\": {"
  152. " \"interfaces\": [ \"*\" ]"
  153. "},"
  154. "\"preferred-lifetime\": 3000,"
  155. "\"rebind-timer\": 2000, "
  156. "\"renew-timer\": 1000, \n"
  157. "# comments in the middle should be ignored, too\n"
  158. "\"subnet6\": [ { "
  159. " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
  160. " \"subnet\": \"2001:db8:1::/64\" "
  161. " } ],"
  162. "\"valid-lifetime\": 4000 }"
  163. "}";
  164. writeFile(TEST_FILE, config_hash_comments);
  165. // Now initialize the server
  166. boost::scoped_ptr<ControlledDhcpv6Srv> srv;
  167. ASSERT_NO_THROW(
  168. srv.reset(new ControlledDhcpv6Srv(0))
  169. );
  170. // And configure it using config without
  171. EXPECT_NO_THROW(srv->init(TEST_FILE));
  172. // Now check if the configuration has been applied correctly.
  173. const Subnet6Collection* subnets =
  174. CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
  175. ASSERT_TRUE(subnets);
  176. ASSERT_EQ(1, subnets->size());
  177. // Check subnet 1.
  178. EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText());
  179. EXPECT_EQ(64, subnets->at(0)->get().second);
  180. // Check pools in the first subnet.
  181. const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
  182. ASSERT_EQ(1, pools1.size());
  183. EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
  184. EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
  185. EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
  186. }
  187. // This test checks if configuration can be read from a JSON file
  188. // using C++ line (//) comments.
  189. TEST_F(JSONFileBackendTest, cppLineComments) {
  190. string config_cpp_line_comments = "// This is a comment. It should be \n"
  191. "//ignored. Real config starts in line below\n"
  192. "{ \"Dhcp6\": {"
  193. "\"interfaces-config\": {"
  194. " \"interfaces\": [ \"*\" ]"
  195. "},"
  196. "\"preferred-lifetime\": 3000,"
  197. "\"rebind-timer\": 2000, "
  198. "\"renew-timer\": 1000, \n"
  199. "// comments in the middle should be ignored, too\n"
  200. "\"subnet6\": [ { "
  201. " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
  202. " \"subnet\": \"2001:db8:1::/64\" "
  203. " } ],"
  204. "\"valid-lifetime\": 4000 }"
  205. "}";
  206. writeFile(TEST_FILE, config_cpp_line_comments);
  207. // Now initialize the server
  208. boost::scoped_ptr<ControlledDhcpv6Srv> srv;
  209. ASSERT_NO_THROW(
  210. srv.reset(new ControlledDhcpv6Srv(0))
  211. );
  212. // And configure it using config without
  213. EXPECT_NO_THROW(srv->init(TEST_FILE));
  214. // Now check if the configuration has been applied correctly.
  215. const Subnet6Collection* subnets =
  216. CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
  217. ASSERT_TRUE(subnets);
  218. ASSERT_EQ(1, subnets->size());
  219. // Check subnet 1.
  220. EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText());
  221. EXPECT_EQ(64, subnets->at(0)->get().second);
  222. // Check pools in the first subnet.
  223. const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
  224. ASSERT_EQ(1, pools1.size());
  225. EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
  226. EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
  227. EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
  228. }
  229. // This test checks if configuration can be read from a JSON file
  230. // using C block (/* */) comments
  231. TEST_F(JSONFileBackendTest, cBlockComments) {
  232. string config_c_block_comments = "/* This is a comment. It should be \n"
  233. "ignored. Real config starts in line below*/\n"
  234. "{ \"Dhcp6\": {"
  235. "\"interfaces-config\": {"
  236. " \"interfaces\": [ \"*\" ]"
  237. "},"
  238. "\"preferred-lifetime\": 3000,"
  239. "\"rebind-timer\": 2000, "
  240. "\"renew-timer\": 1000, \n"
  241. "/* comments in the middle should be ignored, too*/\n"
  242. "\"subnet6\": [ { "
  243. " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
  244. " \"subnet\": \"2001:db8:1::/64\" "
  245. " } ],"
  246. "\"valid-lifetime\": 4000 }"
  247. "}";
  248. writeFile(TEST_FILE, config_c_block_comments);
  249. // Now initialize the server
  250. boost::scoped_ptr<ControlledDhcpv6Srv> srv;
  251. ASSERT_NO_THROW(
  252. srv.reset(new ControlledDhcpv6Srv(0))
  253. );
  254. // And configure it using config without
  255. EXPECT_NO_THROW(srv->init(TEST_FILE));
  256. // Now check if the configuration has been applied correctly.
  257. const Subnet6Collection* subnets =
  258. CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
  259. ASSERT_TRUE(subnets);
  260. ASSERT_EQ(1, subnets->size());
  261. // Check subnet 1.
  262. EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText());
  263. EXPECT_EQ(64, subnets->at(0)->get().second);
  264. // Check pools in the first subnet.
  265. const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
  266. ASSERT_EQ(1, pools1.size());
  267. EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
  268. EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
  269. EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
  270. }
  271. // This test checks if configuration can be read from a JSON file
  272. // using an include file.
  273. TEST_F(JSONFileBackendTest, include) {
  274. string config = "{ \"Dhcp6\": {"
  275. "\"interfaces-config\": {"
  276. " \"interfaces\": [ \"*\" ]"
  277. "},"
  278. "\"preferred-lifetime\": 3000,"
  279. "\"rebind-timer\": 2000, "
  280. "\"renew-timer\": 1000, \n"
  281. "<?include \"" + string(TEST_INCLUDE) + "\"?>,"
  282. "\"valid-lifetime\": 4000 }"
  283. "}";
  284. string include = "\n"
  285. "\"subnet6\": [ { "
  286. " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
  287. " \"subnet\": \"2001:db8:1::/64\" "
  288. " } ]\n";
  289. writeFile(TEST_FILE, config);
  290. writeFile(TEST_INCLUDE, include);
  291. // Now initialize the server
  292. boost::scoped_ptr<ControlledDhcpv6Srv> srv;
  293. ASSERT_NO_THROW(
  294. srv.reset(new ControlledDhcpv6Srv(0))
  295. );
  296. // And configure it using config without
  297. EXPECT_NO_THROW(srv->init(TEST_FILE));
  298. // Now check if the configuration has been applied correctly.
  299. const Subnet6Collection* subnets =
  300. CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
  301. ASSERT_TRUE(subnets);
  302. ASSERT_EQ(1, subnets->size());
  303. // Check subnet 1.
  304. EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText());
  305. EXPECT_EQ(64, subnets->at(0)->get().second);
  306. // Check pools in the first subnet.
  307. const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
  308. ASSERT_EQ(1, pools1.size());
  309. EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
  310. EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
  311. EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
  312. }
  313. // This test checks if recursive include of a file is detected
  314. TEST_F(JSONFileBackendTest, recursiveInclude) {
  315. string config_recursive_include = "{ \"Dhcp6\": {"
  316. "\"interfaces-config\": {"
  317. " \"interfaces\": [ <?include \"" + string(TEST_INCLUDE) + "\"?> ]"
  318. "},"
  319. "\"preferred-lifetime\": 3000,"
  320. "\"rebind-timer\": 2000, "
  321. "\"renew-timer\": 1000, \n"
  322. "\"subnet6\": [ { "
  323. " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
  324. " \"subnet\": \"2001:db8:1::/64\" "
  325. " } ],"
  326. "\"valid-lifetime\": 4000 }"
  327. "}";
  328. string include = "\"eth\", <?include \"" + string(TEST_INCLUDE) + "\"?>";
  329. string msg = "configuration error using file '" + string(TEST_FILE) +
  330. "': Too many nested include.";
  331. writeFile(TEST_FILE, config_recursive_include);
  332. writeFile(TEST_INCLUDE, include);
  333. // Now initialize the server
  334. boost::scoped_ptr<ControlledDhcpv6Srv> srv;
  335. ASSERT_NO_THROW(
  336. srv.reset(new ControlledDhcpv6Srv(0))
  337. );
  338. // And configure it using config
  339. try {
  340. srv->init(TEST_FILE);
  341. FAIL() << "Expected Dhcp6ParseError but nothing was raised";
  342. }
  343. catch (const Exception& ex) {
  344. EXPECT_EQ(msg, ex.what());
  345. }
  346. }
  347. // This test checks if configuration can be read from a JSON file.
  348. // This test checks if configuration detects failure when trying:
  349. // - empty file
  350. // - empty filename
  351. // - no Dhcp6 element
  352. // - Config file that contains Dhcp6 but has a content error
  353. TEST_F(JSONFileBackendTest, configBroken) {
  354. // Empty config is not allowed, because Dhcp6 element is missing
  355. string config_empty = "";
  356. // This config does not have mandatory Dhcp6 element
  357. string config_v4 = "{ \"Dhcp4\": { \"interfaces\": [ \"*\" ],"
  358. "\"preferred-lifetime\": 3000,"
  359. "\"rebind-timer\": 2000, "
  360. "\"renew-timer\": 1000, "
  361. "\"subnet4\": [ { "
  362. " \"pool\": [ \"192.0.2.0/24\" ],"
  363. " \"subnet\": \"192.0.2.0/24\" "
  364. " } ]}";
  365. // This has Dhcp6 element, but it's utter nonsense
  366. string config_nonsense = "{ \"Dhcp6\": { \"reviews\": \"are so much fun\" } }";
  367. // Now initialize the server
  368. boost::scoped_ptr<ControlledDhcpv6Srv> srv;
  369. ASSERT_NO_THROW(
  370. srv.reset(new ControlledDhcpv6Srv(0))
  371. );
  372. // Try to configure without filename. Should fail.
  373. EXPECT_THROW(srv->init(""), BadValue);
  374. // Try to configure it using empty file. Should fail.
  375. writeFile(TEST_FILE, config_empty);
  376. EXPECT_THROW(srv->init(TEST_FILE), BadValue);
  377. // Now try to load a config that does not have Dhcp6 component.
  378. writeFile(TEST_FILE, config_v4);
  379. EXPECT_THROW(srv->init(TEST_FILE), BadValue);
  380. // Now try to load a config with Dhcp6 full of nonsense.
  381. writeFile(TEST_FILE, config_nonsense);
  382. EXPECT_THROW(srv->init(TEST_FILE), BadValue);
  383. }
  384. // This test verifies that the DHCP server installs the timers for reclaiming
  385. // and flushing expired leases.
  386. TEST_F(JSONFileBackendTest, timers) {
  387. // This is a basic configuration which enables timers for reclaiming
  388. // expired leases and flushing them after 500 seconds since they expire.
  389. // Both timers run at 1 second intervals.
  390. string config =
  391. "{ \"Dhcp6\": {"
  392. "\"interfaces-config\": {"
  393. " \"interfaces\": [ ]"
  394. "},"
  395. "\"lease-database\": {"
  396. " \"type\": \"memfile\","
  397. " \"persist\": false"
  398. "},"
  399. "\"expired-leases-processing\": {"
  400. " \"reclaim-timer-wait-time\": 1,"
  401. " \"hold-reclaimed-time\": 500,"
  402. " \"flush-reclaimed-timer-wait-time\": 1"
  403. "},"
  404. "\"rebind-timer\": 2000, "
  405. "\"renew-timer\": 1000, "
  406. "\"subnet6\": [ ],"
  407. "\"preferred-lifetime\": 3000, "
  408. "\"valid-lifetime\": 4000 }"
  409. "}";
  410. writeFile(TEST_FILE, config);
  411. // Create an instance of the server and initialize it.
  412. boost::scoped_ptr<ControlledDhcpv6Srv> srv;
  413. ASSERT_NO_THROW(srv.reset(new ControlledDhcpv6Srv(0)));
  414. ASSERT_NO_THROW(srv->init(TEST_FILE));
  415. // Create an expired lease. The lease is expired by 40 seconds ago
  416. // (valid lifetime = 60, cltt = now - 100). The lease will be reclaimed
  417. // but shouldn't be flushed in the database because the reclaimed are
  418. // held in the database 500 seconds after reclamation, according to the
  419. // current configuration.
  420. DuidPtr duid_expired(new DUID(DUID::fromText("00:01:02:03:04:05:06").getDuid()));
  421. Lease6Ptr lease_expired(new Lease6(Lease::TYPE_NA, IOAddress("3000::1"),
  422. duid_expired, 1, 50, 60, 10, 20, SubnetID(1)));
  423. lease_expired->cltt_ = time(NULL) - 100;
  424. // Create expired-reclaimed lease. The lease has expired 1000 - 60 seconds
  425. // ago. It should be removed from the lease database when the "flush" timer
  426. // goes off.
  427. DuidPtr duid_reclaimed(new DUID(DUID::fromText("01:02:03:04:05:06:07").getDuid()));
  428. Lease6Ptr lease_reclaimed(new Lease6(Lease::TYPE_NA, IOAddress("3000::2"),
  429. duid_reclaimed, 1, 50, 60, 10, 20, SubnetID(1)));
  430. lease_reclaimed->cltt_ = time(NULL) - 1000;
  431. lease_reclaimed->state_ = Lease6::STATE_EXPIRED_RECLAIMED;
  432. // Add leases to the database.
  433. LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
  434. ASSERT_NO_THROW(lease_mgr.addLease(lease_expired));
  435. ASSERT_NO_THROW(lease_mgr.addLease(lease_reclaimed));
  436. // Make sure they have been added.
  437. ASSERT_TRUE(lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::1")));
  438. ASSERT_TRUE(lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::2")));
  439. // Poll the timers for a while to make sure that each of them is executed
  440. // at least once.
  441. ASSERT_NO_THROW(runTimersWithTimeout(5000));
  442. // Verify that the leases in the database have been processed as expected.
  443. // First lease should be reclaimed, but not removed.
  444. ASSERT_NO_THROW(
  445. lease_expired = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::1"))
  446. );
  447. ASSERT_TRUE(lease_expired);
  448. EXPECT_TRUE(lease_expired->stateExpiredReclaimed());
  449. // Second lease should have been removed.
  450. ASSERT_NO_THROW(
  451. lease_reclaimed = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::2"))
  452. );
  453. EXPECT_FALSE(lease_reclaimed);
  454. }
  455. // This test verifies that the DUID type can be selected.
  456. TEST_F(JSONFileBackendTest, serverId) {
  457. string config =
  458. "{ \"Dhcp6\": {"
  459. "\"interfaces-config\": {"
  460. " \"interfaces\": [ ]"
  461. "},"
  462. "\"lease-database\": {"
  463. " \"type\": \"memfile\","
  464. " \"persist\": false"
  465. "},"
  466. "\"server-id\": {"
  467. " \"type\": \"EN\","
  468. " \"enterprise-id\": 1234"
  469. "},"
  470. "\"rebind-timer\": 2000, "
  471. "\"renew-timer\": 1000, "
  472. "\"subnet6\": [ ],"
  473. "\"preferred-lifetime\": 3000, "
  474. "\"valid-lifetime\": 4000 }"
  475. "}";
  476. writeFile(TEST_FILE, config);
  477. // Create an instance of the server and initialize it.
  478. boost::scoped_ptr<ControlledDhcpv6Srv> srv;
  479. ASSERT_NO_THROW(srv.reset(new ControlledDhcpv6Srv(0)));
  480. ASSERT_NO_THROW(srv->init(TEST_FILE));
  481. // Check that DUID configuration is affected.
  482. ConstCfgDUIDPtr duid_cfg = CfgMgr::instance().getCurrentCfg()->getCfgDUID();
  483. ASSERT_TRUE(duid_cfg);
  484. EXPECT_EQ(DUID::DUID_EN, duid_cfg->getType());
  485. EXPECT_EQ(1234, duid_cfg->getEnterpriseId());
  486. }
  487. // This test verifies that the server uses default (Memfile) lease database
  488. // backend when no backend is explicitly specified in the configuration.
  489. TEST_F(JSONFileBackendTest, defaultLeaseDbBackend) {
  490. // This is basic server configuration which excludes lease database
  491. // backend specification. The default Memfile backend should be
  492. // initialized in this case.
  493. string config =
  494. "{ \"Dhcp6\": {"
  495. "\"interfaces-config\": {"
  496. " \"interfaces\": [ ]"
  497. "},"
  498. "\"rebind-timer\": 2000, "
  499. "\"renew-timer\": 1000, \n"
  500. "\"subnet6\": [ ],"
  501. "\"preferred-lifetime\": 3000, "
  502. "\"valid-lifetime\": 4000 }"
  503. "}";
  504. writeFile(TEST_FILE, config);
  505. // Create an instance of the server and initialize it.
  506. boost::scoped_ptr<ControlledDhcpv6Srv> srv;
  507. ASSERT_NO_THROW(srv.reset(new ControlledDhcpv6Srv(0)));
  508. ASSERT_NO_THROW(srv->init(TEST_FILE));
  509. // The backend should have been created.
  510. EXPECT_NO_THROW(static_cast<void>(LeaseMgrFactory::instance()));
  511. }
  512. // Starting tests which require MySQL backend availability. Those tests
  513. // will not be executed if Kea has been compiled without the
  514. // --with-dhcp-mysql.
  515. #ifdef HAVE_MYSQL
  516. /// @brief Test fixture class for the tests utilizing MySQL database
  517. /// backend.
  518. class JSONFileBackendMySQLTest : public JSONFileBackendTest {
  519. public:
  520. /// @brief Constructor.
  521. ///
  522. /// Recreates MySQL schema for a test.
  523. JSONFileBackendMySQLTest() : JSONFileBackendTest() {
  524. destroyMySQLSchema();
  525. createMySQLSchema();
  526. }
  527. /// @brief Destructor.
  528. ///
  529. /// Destroys MySQL schema.
  530. virtual ~JSONFileBackendMySQLTest() {
  531. destroyMySQLSchema();
  532. }
  533. /// @brief Creates server configuration with specified backend type.
  534. ///
  535. /// @param backend Backend type or empty string to indicate that the
  536. /// backend configuration should not be placed in the resulting
  537. /// JSON configuration.
  538. ///
  539. /// @return Server configuration.
  540. std::string createConfiguration(const std::string& backend) const;
  541. /// @brief Test reconfiguration with a backend change.
  542. ///
  543. /// If any of the parameters is an empty string it indicates that the
  544. /// created configuration should exclude backend configuration.
  545. ///
  546. /// @param backend_first Type of a backend to be used initially.
  547. /// @param backend_second Type of a backend to be used after
  548. /// reconfiguration.
  549. void testBackendReconfiguration(const std::string& backend_first,
  550. const std::string& backend_second);
  551. };
  552. std::string
  553. JSONFileBackendMySQLTest::createConfiguration(const std::string& backend) const {
  554. // This is basic server configuration which excludes lease database
  555. // backend specification. The default Memfile backend should be
  556. // initialized in this case.
  557. std::ostringstream config;
  558. config <<
  559. "{ \"Dhcp6\": {"
  560. "\"interfaces-config\": {"
  561. " \"interfaces\": [ ]"
  562. "},";
  563. // For non-empty lease backend type we have to add a backend configuration
  564. // section.
  565. if (!backend.empty()) {
  566. config <<
  567. "\"lease-database\": {"
  568. " \"type\": \"" << backend << "\"";
  569. // SQL backends require database credentials.
  570. if (backend != "memfile") {
  571. config <<
  572. ","
  573. " \"name\": \"keatest\","
  574. " \"user\": \"keatest\","
  575. " \"password\": \"keatest\"";
  576. }
  577. config << "},";
  578. }
  579. // Append the rest of the configuration.
  580. config <<
  581. "\"rebind-timer\": 2000, "
  582. "\"renew-timer\": 1000, \n"
  583. "\"subnet6\": [ ],"
  584. "\"preferred-lifetime\": 3000, "
  585. "\"valid-lifetime\": 4000 }"
  586. "}";
  587. return (config.str());
  588. }
  589. void
  590. JSONFileBackendMySQLTest::
  591. testBackendReconfiguration(const std::string& backend_first,
  592. const std::string& backend_second) {
  593. writeFile(TEST_FILE, createConfiguration(backend_first));
  594. // Create an instance of the server and initialize it.
  595. boost::scoped_ptr<NakedControlledDhcpv6Srv> srv;
  596. ASSERT_NO_THROW(srv.reset(new NakedControlledDhcpv6Srv()));
  597. srv->setConfigFile(TEST_FILE);
  598. ASSERT_NO_THROW(srv->init(TEST_FILE));
  599. // The backend should have been created and its type should be
  600. // correct.
  601. ASSERT_NO_THROW(static_cast<void>(LeaseMgrFactory::instance()));
  602. EXPECT_EQ(backend_first.empty() ? "memfile" : backend_first,
  603. LeaseMgrFactory::instance().getType());
  604. // New configuration modifies the lease database backend type.
  605. writeFile(TEST_FILE, createConfiguration(backend_second));
  606. // Explicitly calling signal handler for SIGHUP to trigger server
  607. // reconfiguration.
  608. srv->signal_handler_(SIGHUP);
  609. // The backend should have been created and its type should be
  610. // correct.
  611. ASSERT_NO_THROW(static_cast<void>(LeaseMgrFactory::instance()));
  612. EXPECT_EQ(backend_second.empty() ? "memfile" : backend_second,
  613. LeaseMgrFactory::instance().getType());
  614. }
  615. // This test verifies that backend specification can be added on
  616. // server reconfiguration.
  617. TEST_F(JSONFileBackendMySQLTest, reconfigureBackendUndefinedToMySQL) {
  618. testBackendReconfiguration("", "mysql");
  619. }
  620. // This test verifies that when backend specification is removed the
  621. // default backend is used.
  622. TEST_F(JSONFileBackendMySQLTest, reconfigureBackendMySQLToUndefined) {
  623. testBackendReconfiguration("mysql", "");
  624. }
  625. // This test verifies that backend type can be changed from Memfile
  626. // to MySQL.
  627. TEST_F(JSONFileBackendMySQLTest, reconfigureBackendMemfileToMySQL) {
  628. testBackendReconfiguration("memfile", "mysql");
  629. }
  630. #endif
  631. } // End of anonymous namespace