kea_controller_unittest.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. // Copyright (C) 2014-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/dhcp4.h>
  10. #include <dhcp/hwaddr.h>
  11. #include <dhcp/iface_mgr.h>
  12. #include <dhcp4/ctrl_dhcp4_srv.h>
  13. #include <dhcp4/tests/dhcp4_test_utils.h>
  14. #include <dhcpsrv/cfgmgr.h>
  15. #include <dhcpsrv/lease.h>
  16. #include <dhcpsrv/lease_mgr_factory.h>
  17. #include <dhcpsrv/testutils/mysql_schema.h>
  18. #include <log/logger_support.h>
  19. #include <util/stopwatch.h>
  20. #include <boost/scoped_ptr.hpp>
  21. #include <gtest/gtest.h>
  22. #include <fstream>
  23. #include <iostream>
  24. #include <signal.h>
  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 NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
  38. // "Naked" DHCPv4 server, exposes internal fields
  39. public:
  40. NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(0) { }
  41. using ControlledDhcpv4Srv::signal_handler_;
  42. };
  43. /// @brief test class for Kea configuration backend
  44. ///
  45. /// This class is used for testing Kea configuration backend.
  46. /// It is very simple and currently focuses on reading
  47. /// config file from disk. It is expected to be expanded in the
  48. /// near future.
  49. class JSONFileBackendTest : public isc::dhcp::test::BaseServerTest {
  50. public:
  51. JSONFileBackendTest() {
  52. }
  53. ~JSONFileBackendTest() {
  54. LeaseMgrFactory::destroy();
  55. isc::log::setDefaultLoggingOutput();
  56. static_cast<void>(remove(TEST_FILE));
  57. };
  58. /// @brief writes specified content to a well known file
  59. ///
  60. /// Writes specified content to TEST_FILE. Tests will
  61. /// attempt to read that file.
  62. ///
  63. /// @param content content to be written to file
  64. void writeFile(const std::string& content) {
  65. static_cast<void>(remove(TEST_FILE));
  66. ofstream out(TEST_FILE, ios::trunc);
  67. EXPECT_TRUE(out.is_open());
  68. out << content;
  69. out.close();
  70. }
  71. /// @brief Runs timers for specified time.
  72. ///
  73. /// Internally, this method calls @c IfaceMgr::receive4 to run the
  74. /// callbacks for the installed timers.
  75. ///
  76. /// @param timeout_ms Amount of time after which the method returns.
  77. void runTimersWithTimeout(const long timeout_ms) {
  78. isc::util::Stopwatch stopwatch;
  79. while (stopwatch.getTotalMilliseconds() < timeout_ms) {
  80. // Block for up to one millisecond waiting for the timers'
  81. // callbacks to be executed.
  82. IfaceMgr::instancePtr()->receive4(0, 1000);
  83. }
  84. }
  85. /// Name of a config file used during tests
  86. static const char* TEST_FILE;
  87. };
  88. const char* JSONFileBackendTest::TEST_FILE = "test-config.json";
  89. // This test checks if configuration can be read from a JSON file.
  90. TEST_F(JSONFileBackendTest, jsonFile) {
  91. // Prepare configuration file.
  92. string config = "{ \"Dhcp4\": {"
  93. "\"interfaces-config\": {"
  94. " \"interfaces\": [ \"*\" ]"
  95. "},"
  96. "\"rebind-timer\": 2000, "
  97. "\"renew-timer\": 1000, "
  98. "\"subnet4\": [ { "
  99. " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
  100. " \"subnet\": \"192.0.2.0/24\" "
  101. " },"
  102. " {"
  103. " \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
  104. " \"subnet\": \"192.0.3.0/24\", "
  105. " \"id\": 0 "
  106. " },"
  107. " {"
  108. " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
  109. " \"subnet\": \"192.0.4.0/24\" "
  110. " } ],"
  111. "\"valid-lifetime\": 4000 }"
  112. "}";
  113. writeFile(config);
  114. // Now initialize the server
  115. boost::scoped_ptr<ControlledDhcpv4Srv> srv;
  116. ASSERT_NO_THROW(
  117. srv.reset(new ControlledDhcpv4Srv(0))
  118. );
  119. // And configure it using the config file.
  120. EXPECT_NO_THROW(srv->init(TEST_FILE));
  121. // Now check if the configuration has been applied correctly.
  122. const Subnet4Collection* subnets =
  123. CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
  124. ASSERT_TRUE(subnets);
  125. ASSERT_EQ(3, subnets->size()); // We expect 3 subnets.
  126. // Check subnet 1.
  127. EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText());
  128. EXPECT_EQ(24, subnets->at(0)->get().second);
  129. // Check pools in the first subnet.
  130. const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_V4);
  131. ASSERT_EQ(1, pools1.size());
  132. EXPECT_EQ("192.0.2.1", pools1.at(0)->getFirstAddress().toText());
  133. EXPECT_EQ("192.0.2.100", pools1.at(0)->getLastAddress().toText());
  134. EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
  135. // Check subnet 2.
  136. EXPECT_EQ("192.0.3.0", subnets->at(1)->get().first.toText());
  137. EXPECT_EQ(24, subnets->at(1)->get().second);
  138. // Check pools in the second subnet.
  139. const PoolCollection& pools2 = subnets->at(1)->getPools(Lease::TYPE_V4);
  140. ASSERT_EQ(1, pools2.size());
  141. EXPECT_EQ("192.0.3.101", pools2.at(0)->getFirstAddress().toText());
  142. EXPECT_EQ("192.0.3.150", pools2.at(0)->getLastAddress().toText());
  143. EXPECT_EQ(Lease::TYPE_V4, pools2.at(0)->getType());
  144. // And finally check subnet 3.
  145. EXPECT_EQ("192.0.4.0", subnets->at(2)->get().first.toText());
  146. EXPECT_EQ(24, subnets->at(2)->get().second);
  147. // ... and it's only pool.
  148. const PoolCollection& pools3 = subnets->at(2)->getPools(Lease::TYPE_V4);
  149. EXPECT_EQ("192.0.4.101", pools3.at(0)->getFirstAddress().toText());
  150. EXPECT_EQ("192.0.4.150", pools3.at(0)->getLastAddress().toText());
  151. EXPECT_EQ(Lease::TYPE_V4, pools3.at(0)->getType());
  152. }
  153. // This test checks if configuration can be read from a JSON file.
  154. TEST_F(JSONFileBackendTest, comments) {
  155. string config_hash_comments = "# This is a comment. It should be \n"
  156. "#ignored. Real config starts in line below\n"
  157. "{ \"Dhcp4\": {"
  158. "\"interfaces-config\": {"
  159. " \"interfaces\": [ \"*\" ]"
  160. "},"
  161. "\"rebind-timer\": 2000, "
  162. "\"renew-timer\": 1000, \n"
  163. "# comments in the middle should be ignored, too\n"
  164. "\"subnet4\": [ { "
  165. " \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
  166. " \"subnet\": \"192.0.2.0/22\" "
  167. " } ],"
  168. "\"valid-lifetime\": 4000 }"
  169. "}";
  170. /// @todo: Implement C++-style (// ...) comments
  171. /// @todo: Implement C-style (/* ... */) comments
  172. writeFile(config_hash_comments);
  173. // Now initialize the server
  174. boost::scoped_ptr<ControlledDhcpv4Srv> srv;
  175. ASSERT_NO_THROW(
  176. srv.reset(new ControlledDhcpv4Srv(0))
  177. );
  178. // And configure it using config with comments.
  179. EXPECT_NO_THROW(srv->init(TEST_FILE));
  180. // Now check if the configuration has been applied correctly.
  181. const Subnet4Collection* subnets =
  182. CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
  183. ASSERT_TRUE(subnets);
  184. ASSERT_EQ(1, subnets->size());
  185. // Check subnet 1.
  186. EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText());
  187. EXPECT_EQ(22, subnets->at(0)->get().second);
  188. // Check pools in the first subnet.
  189. const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_V4);
  190. ASSERT_EQ(1, pools1.size());
  191. EXPECT_EQ("192.0.2.0", pools1.at(0)->getFirstAddress().toText());
  192. EXPECT_EQ("192.0.2.255", pools1.at(0)->getLastAddress().toText());
  193. EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
  194. }
  195. // This test checks if configuration detects failure when trying:
  196. // - empty file
  197. // - empty filename
  198. // - no Dhcp4 element
  199. // - Config file that contains Dhcp4 but has a content error
  200. TEST_F(JSONFileBackendTest, configBroken) {
  201. // Empty config is not allowed, because Dhcp4 element is missing
  202. string config_empty = "";
  203. // This config does not have mandatory Dhcp4 element
  204. string config_v4 = "{ \"Dhcp6\": { \"interfaces\": [ \"*\" ],"
  205. "\"preferred-lifetime\": 3000,"
  206. "\"rebind-timer\": 2000, "
  207. "\"renew-timer\": 1000, "
  208. "\"subnet4\": [ { "
  209. " \"pool\": [ \"2001:db8::/80\" ],"
  210. " \"subnet\": \"2001:db8::/64\" "
  211. " } ]}";
  212. // This has Dhcp4 element, but it's utter nonsense
  213. string config_nonsense = "{ \"Dhcp4\": { \"reviews\": \"are so much fun\" } }";
  214. // Now initialize the server
  215. boost::scoped_ptr<ControlledDhcpv4Srv> srv;
  216. ASSERT_NO_THROW(
  217. srv.reset(new ControlledDhcpv4Srv(0))
  218. );
  219. // Try to configure without filename. Should fail.
  220. EXPECT_THROW(srv->init(""), BadValue);
  221. // Try to configure it using empty file. Should fail.
  222. writeFile(config_empty);
  223. EXPECT_THROW(srv->init(TEST_FILE), BadValue);
  224. // Now try to load a config that does not have Dhcp4 component.
  225. writeFile(config_v4);
  226. EXPECT_THROW(srv->init(TEST_FILE), BadValue);
  227. // Now try to load a config with Dhcp4 full of nonsense.
  228. writeFile(config_nonsense);
  229. EXPECT_THROW(srv->init(TEST_FILE), BadValue);
  230. }
  231. /// This unit-test reads all files enumerated in configs-test.txt file, loads
  232. /// each of them and verify that they can be loaded.
  233. ///
  234. /// @todo: Unfortunately, we have this test disabled, because all loaded
  235. /// configs use memfile, which attempts to create lease file in
  236. /// /usr/local/var/kea/kea-leases4.csv. We have couple options here:
  237. /// a) disable persistence in example configs - a very bad thing to do
  238. /// as users will forget to reenable it and then will be surprised when their
  239. /// leases disappear
  240. /// b) change configs to store lease file in /tmp. It's almost as bad as the
  241. /// previous one. Users will then be displeased when all their leases are
  242. /// wiped. (most systems wipe /tmp during boot)
  243. /// c) read each config and rewrite it on the fly, so persistence is disabled.
  244. /// This is probably the way to go, but this is a work for a dedicated ticket.
  245. ///
  246. /// Hence I'm leaving the test in, but it is disabled.
  247. TEST_F(JSONFileBackendTest, DISABLED_loadAllConfigs) {
  248. // Create server first
  249. boost::scoped_ptr<ControlledDhcpv4Srv> srv;
  250. ASSERT_NO_THROW(
  251. srv.reset(new ControlledDhcpv4Srv(0))
  252. );
  253. const char* configs_list = "configs-list.txt";
  254. fstream configs(configs_list, ios::in);
  255. ASSERT_TRUE(configs.is_open());
  256. std::string config_name;
  257. while (std::getline(configs, config_name)) {
  258. // Ignore empty and commented lines
  259. if (config_name.empty() || config_name[0] == '#') {
  260. continue;
  261. }
  262. // Unit-tests usually do not print out anything, but in this case I
  263. // think printing out tests configs is warranted.
  264. std::cout << "Loading config file " << config_name << std::endl;
  265. try {
  266. srv->init(config_name);
  267. } catch (const std::exception& ex) {
  268. ADD_FAILURE() << "Exception thrown" << ex.what() << endl;
  269. }
  270. }
  271. }
  272. // This test verifies that the DHCP server installs the timers for reclaiming
  273. // and flushing expired leases.
  274. TEST_F(JSONFileBackendTest, timers) {
  275. // This is a basic configuration which enables timers for reclaiming
  276. // expired leases and flushing them after 500 seconds since they expire.
  277. // Both timers run at 1 second intervals.
  278. string config =
  279. "{ \"Dhcp4\": {"
  280. "\"interfaces-config\": {"
  281. " \"interfaces\": [ ]"
  282. "},"
  283. "\"lease-database\": {"
  284. " \"type\": \"memfile\","
  285. " \"persist\": false"
  286. "},"
  287. "\"expired-leases-processing\": {"
  288. " \"reclaim-timer-wait-time\": 1,"
  289. " \"hold-reclaimed-time\": 500,"
  290. " \"flush-reclaimed-timer-wait-time\": 1"
  291. "},"
  292. "\"rebind-timer\": 2000, "
  293. "\"renew-timer\": 1000, \n"
  294. "\"subnet4\": [ ],"
  295. "\"valid-lifetime\": 4000 }"
  296. "}";
  297. writeFile(config);
  298. // Create an instance of the server and intialize it.
  299. boost::scoped_ptr<ControlledDhcpv4Srv> srv;
  300. ASSERT_NO_THROW(srv.reset(new ControlledDhcpv4Srv(0)));
  301. ASSERT_NO_THROW(srv->init(TEST_FILE));
  302. // Create an expired lease. The lease is expired by 40 seconds ago
  303. // (valid lifetime = 60, cltt = now - 100). The lease will be reclaimed
  304. // but shouldn't be flushed in the database because the reclaimed are
  305. // held in the database 500 seconds after reclamation, according to the
  306. // current configuration.
  307. HWAddrPtr hwaddr_expired(new HWAddr(HWAddr::fromText("00:01:02:03:04:05")));
  308. Lease4Ptr lease_expired(new Lease4(IOAddress("10.0.0.1"), hwaddr_expired,
  309. ClientIdPtr(), 60, 10, 20,
  310. time(NULL) - 100, SubnetID(1)));
  311. // Create expired-reclaimed lease. The lease has expired 1000 - 60 seconds
  312. // ago. It should be removed from the lease database when the "flush" timer
  313. // goes off.
  314. HWAddrPtr hwaddr_reclaimed(new HWAddr(HWAddr::fromText("01:02:03:04:05:06")));
  315. Lease4Ptr lease_reclaimed(new Lease4(IOAddress("10.0.0.2"), hwaddr_reclaimed,
  316. ClientIdPtr(), 60, 10, 20,
  317. time(NULL) - 1000, SubnetID(1)));
  318. lease_reclaimed->state_ = Lease4::STATE_EXPIRED_RECLAIMED;
  319. // Add leases to the database.
  320. LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
  321. ASSERT_NO_THROW(lease_mgr.addLease(lease_expired));
  322. ASSERT_NO_THROW(lease_mgr.addLease(lease_reclaimed));
  323. // Make sure they have been added.
  324. ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.1")));
  325. ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.2")));
  326. // Poll the timers for a while to make sure that each of them is executed
  327. // at least once.
  328. ASSERT_NO_THROW(runTimersWithTimeout(5000));
  329. // Verify that the leases in the database have been processed as expected.
  330. // First lease should be reclaimed, but not removed.
  331. ASSERT_NO_THROW(lease_expired = lease_mgr.getLease4(IOAddress("10.0.0.1")));
  332. ASSERT_TRUE(lease_expired);
  333. EXPECT_TRUE(lease_expired->stateExpiredReclaimed());
  334. // Second lease should have been removed.
  335. ASSERT_NO_THROW(
  336. lease_reclaimed = lease_mgr.getLease4(IOAddress("10.0.0.2"));
  337. );
  338. EXPECT_FALSE(lease_reclaimed);
  339. }
  340. // This test verifies that the server uses default (Memfile) lease database
  341. // backend when no backend is explicitly specified in the configuration.
  342. TEST_F(JSONFileBackendTest, defaultLeaseDbBackend) {
  343. // This is basic server configuration which excludes lease database
  344. // backend specification. The default Memfile backend should be
  345. // initialized in this case.
  346. string config =
  347. "{ \"Dhcp4\": {"
  348. "\"interfaces-config\": {"
  349. " \"interfaces\": [ ]"
  350. "},"
  351. "\"rebind-timer\": 2000, "
  352. "\"renew-timer\": 1000, \n"
  353. "\"subnet4\": [ ],"
  354. "\"valid-lifetime\": 4000 }"
  355. "}";
  356. writeFile(config);
  357. // Create an instance of the server and intialize it.
  358. boost::scoped_ptr<ControlledDhcpv4Srv> srv;
  359. ASSERT_NO_THROW(srv.reset(new ControlledDhcpv4Srv(0)));
  360. ASSERT_NO_THROW(srv->init(TEST_FILE));
  361. // The backend should have been created.
  362. EXPECT_NO_THROW(static_cast<void>(LeaseMgrFactory::instance()));
  363. }
  364. // Starting tests which require MySQL backend availability. Those tests
  365. // will not be executed if Kea has been compiled without the
  366. // --with-dhcp-mysql.
  367. #ifdef HAVE_MYSQL
  368. /// @brief Test fixture class for the tests utilizing MySQL database
  369. /// backend.
  370. class JSONFileBackendMySQLTest : public JSONFileBackendTest {
  371. public:
  372. /// @brief Constructor.
  373. ///
  374. /// Recreates MySQL schema for a test.
  375. JSONFileBackendMySQLTest() : JSONFileBackendTest() {
  376. destroyMySQLSchema();
  377. createMySQLSchema();
  378. }
  379. /// @brief Destructor.
  380. ///
  381. /// Destroys MySQL schema.
  382. virtual ~JSONFileBackendMySQLTest() {
  383. destroyMySQLSchema();
  384. }
  385. /// @brief Creates server configuration with specified backend type.
  386. ///
  387. /// @param backend Backend type or empty string to indicate that the
  388. /// backend configuration should not be placed in the resulting
  389. /// JSON configuration.
  390. ///
  391. /// @return Server configuration.
  392. std::string createConfiguration(const std::string& backend) const;
  393. /// @brief Test reconfiguration with a backend change.
  394. ///
  395. /// If any of the parameters is an empty string it indicates that the
  396. /// created configuration should exclude backend configuration.
  397. ///
  398. /// @param backend_first Type of a backend to be used initially.
  399. /// @param backend_second Type of a backend to be used after
  400. /// reconfiguration.
  401. void testBackendReconfiguration(const std::string& backend_first,
  402. const std::string& backend_second);
  403. };
  404. std::string
  405. JSONFileBackendMySQLTest::createConfiguration(const std::string& backend) const {
  406. // This is basic server configuration which excludes lease database
  407. // backend specification. The default Memfile backend should be
  408. // initialized in this case.
  409. std::ostringstream config;
  410. config <<
  411. "{ \"Dhcp4\": {"
  412. "\"interfaces-config\": {"
  413. " \"interfaces\": [ ]"
  414. "},";
  415. // For non-empty lease backend type we have to add a backend configuration
  416. // section.
  417. if (!backend.empty()) {
  418. config <<
  419. "\"lease-database\": {"
  420. " \"type\": \"" << backend << "\"";
  421. // SQL backends require database credentials.
  422. if (backend != "memfile") {
  423. config <<
  424. ","
  425. " \"name\": \"keatest\","
  426. " \"user\": \"keatest\","
  427. " \"password\": \"keatest\"";
  428. }
  429. config << "},";
  430. }
  431. // Append the rest of the configuration.
  432. config <<
  433. "\"rebind-timer\": 2000, "
  434. "\"renew-timer\": 1000, \n"
  435. "\"subnet4\": [ ],"
  436. "\"valid-lifetime\": 4000 }"
  437. "}";
  438. return (config.str());
  439. }
  440. void
  441. JSONFileBackendMySQLTest::
  442. testBackendReconfiguration(const std::string& backend_first,
  443. const std::string& backend_second) {
  444. writeFile(createConfiguration(backend_first));
  445. // Create an instance of the server and intialize it.
  446. boost::scoped_ptr<NakedControlledDhcpv4Srv> srv;
  447. ASSERT_NO_THROW(srv.reset(new NakedControlledDhcpv4Srv()));
  448. srv->setConfigFile(TEST_FILE);
  449. ASSERT_NO_THROW(srv->init(TEST_FILE));
  450. // The backend should have been created and its type should be
  451. // correct.
  452. ASSERT_NO_THROW(static_cast<void>(LeaseMgrFactory::instance()));
  453. EXPECT_EQ(backend_first.empty() ? "memfile" : backend_first,
  454. LeaseMgrFactory::instance().getType());
  455. // New configuration modifies the lease database backend type.
  456. writeFile(createConfiguration(backend_second));
  457. // Explicitly calling signal handler for SIGHUP to trigger server
  458. // reconfiguration.
  459. srv->signal_handler_(SIGHUP);
  460. // The backend should have been created and its type should be
  461. // correct.
  462. ASSERT_NO_THROW(static_cast<void>(LeaseMgrFactory::instance()));
  463. EXPECT_EQ(backend_second.empty() ? "memfile" : backend_second,
  464. LeaseMgrFactory::instance().getType());
  465. }
  466. // This test verifies that backend specification can be added on
  467. // server reconfiguration.
  468. TEST_F(JSONFileBackendMySQLTest, reconfigureBackendUndefinedToMySQL) {
  469. testBackendReconfiguration("", "mysql");
  470. }
  471. // This test verifies that when backend specification is removed the
  472. // default backend is used.
  473. TEST_F(JSONFileBackendMySQLTest, reconfigureBackendMySQLToUndefined) {
  474. testBackendReconfiguration("mysql", "");
  475. }
  476. // This test verifies that backend type can be changed from Memfile
  477. // to MySQL.
  478. TEST_F(JSONFileBackendMySQLTest, reconfigureBackendMemfileToMySQL) {
  479. testBackendReconfiguration("memfile", "mysql");
  480. }
  481. #endif
  482. } // End of anonymous namespace