command_unittest.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. // Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <config.h>
  15. #include "datasrc_util.h"
  16. #include <auth/auth_srv.h>
  17. #include <auth/auth_config.h>
  18. #include <auth/command.h>
  19. #include <auth/datasrc_config.h>
  20. #include <dns/name.h>
  21. #include <dns/rrclass.h>
  22. #include <dns/rrtype.h>
  23. #include <dns/rrttl.h>
  24. #include <cc/data.h>
  25. #include <config/ccsession.h>
  26. #include <datasrc/memory_datasrc.h>
  27. #include <asiolink/asiolink.h>
  28. #include <util/unittests/mock_socketsession.h>
  29. #include <testutils/mockups.h>
  30. #include <cassert>
  31. #include <cstdlib>
  32. #include <string>
  33. #include <stdexcept>
  34. #include <boost/bind.hpp>
  35. #include <gtest/gtest.h>
  36. #include <sys/types.h>
  37. #include <unistd.h>
  38. using namespace std;
  39. using namespace isc::dns;
  40. using namespace isc::data;
  41. using namespace isc::datasrc;
  42. using namespace isc::config;
  43. using namespace isc::util::unittests;
  44. using namespace isc::testutils;
  45. using namespace isc::auth::unittest;
  46. namespace {
  47. class AuthCommandTest : public ::testing::Test {
  48. protected:
  49. AuthCommandTest() :
  50. server_(xfrout_, ddns_forwarder_),
  51. rcode_(-1),
  52. expect_rcode_(0),
  53. itimer_(server_.getIOService())
  54. {}
  55. void checkAnswer(const int expected_code, const char* name = "") {
  56. SCOPED_TRACE(name);
  57. parseAnswer(rcode_, result_);
  58. EXPECT_EQ(expected_code, rcode_) << result_->str();
  59. }
  60. MockXfroutClient xfrout_;
  61. MockSocketSessionForwarder ddns_forwarder_;
  62. AuthSrv server_;
  63. ConstElementPtr result_;
  64. // The shutdown command parameter
  65. ConstElementPtr param_;
  66. int rcode_, expect_rcode_;
  67. isc::asiolink::IntervalTimer itimer_;
  68. public:
  69. void stopServer(); // need to be public for boost::bind
  70. void dontStopServer(); // need to be public for boost::bind
  71. };
  72. TEST_F(AuthCommandTest, unknownCommand) {
  73. result_ = execAuthServerCommand(server_, "no_such_command",
  74. ConstElementPtr());
  75. parseAnswer(rcode_, result_);
  76. EXPECT_EQ(1, rcode_);
  77. }
  78. TEST_F(AuthCommandTest, DISABLED_unexpectedException) {
  79. // execAuthServerCommand() won't catch standard exceptions.
  80. // Skip this test for now: ModuleCCSession doesn't seem to validate
  81. // commands.
  82. EXPECT_THROW(execAuthServerCommand(server_, "_throw_exception",
  83. ConstElementPtr()),
  84. runtime_error);
  85. }
  86. void
  87. AuthCommandTest::stopServer() {
  88. result_ = execAuthServerCommand(server_, "shutdown", param_);
  89. parseAnswer(rcode_, result_);
  90. assert(rcode_ == 0); // make sure the test stops when something is wrong
  91. }
  92. TEST_F(AuthCommandTest, shutdown) {
  93. // Param defaults to empty/null pointer on creation
  94. itimer_.setup(boost::bind(&AuthCommandTest::stopServer, this), 1);
  95. server_.getIOService().run();
  96. EXPECT_EQ(0, rcode_);
  97. }
  98. TEST_F(AuthCommandTest, shutdownCorrectPID) {
  99. // Put the pid parameter there
  100. const pid_t pid(getpid());
  101. ElementPtr param(new isc::data::MapElement());
  102. param->set("pid", ConstElementPtr(new isc::data::IntElement(pid)));
  103. param_ = param;
  104. // With the correct PID, it should act exactly the same as in case
  105. // of no parameter
  106. itimer_.setup(boost::bind(&AuthCommandTest::stopServer, this), 1);
  107. server_.getIOService().run();
  108. EXPECT_EQ(0, rcode_);
  109. }
  110. // This is like stopServer, but the server should not stop after the
  111. // command, it should be running
  112. void
  113. AuthCommandTest::dontStopServer() {
  114. result_ = execAuthServerCommand(server_, "shutdown", param_);
  115. parseAnswer(rcode_, result_);
  116. EXPECT_EQ(expect_rcode_, rcode_);
  117. rcode_ = -1;
  118. // We run the stopServer now, to really stop the server.
  119. // If it had stopped already, it won't be run and the rcode -1 will
  120. // be left here.
  121. param_ = ConstElementPtr();
  122. itimer_.cancel();
  123. itimer_.setup(boost::bind(&AuthCommandTest::stopServer, this), 1);
  124. }
  125. // If we provide something not an int, the PID is not really specified, so
  126. // act as if nothing came.
  127. TEST_F(AuthCommandTest, shutdownNotInt) {
  128. // Put the pid parameter there
  129. ElementPtr param(new isc::data::MapElement());
  130. param->set("pid", ConstElementPtr(new isc::data::StringElement("pid")));
  131. param_ = param;
  132. expect_rcode_ = 1;
  133. // It should reject to stop if the PID is not an int.
  134. itimer_.setup(boost::bind(&AuthCommandTest::dontStopServer, this), 1);
  135. server_.getIOService().run();
  136. EXPECT_EQ(0, rcode_);
  137. }
  138. TEST_F(AuthCommandTest, shutdownIncorrectPID) {
  139. // The PID = 0 should be taken by init, so we are not init and the
  140. // PID should be different
  141. param_ = Element::fromJSON("{\"pid\": 0}");
  142. itimer_.setup(boost::bind(&AuthCommandTest::dontStopServer, this), 1);
  143. server_.getIOService().run();
  144. EXPECT_EQ(0, rcode_);
  145. }
  146. // A helper function commonly used for the "loadzone" command tests.
  147. // It configures the server with a memory data source containing two
  148. // zones, and checks the zones are correctly loaded.
  149. void
  150. zoneChecks(AuthSrv& server) {
  151. isc::util::thread::Mutex::Locker locker(server.getClientListMutex());
  152. EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
  153. find(Name("ns.test1.example")).finder_->
  154. find(Name("ns.test1.example"), RRType::A())->code);
  155. EXPECT_EQ(ZoneFinder::NXRRSET, server.getClientList(RRClass::IN())->
  156. find(Name("ns.test1.example")).finder_->
  157. find(Name("ns.test1.example"), RRType::AAAA())->code);
  158. EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
  159. find(Name("ns.test2.example")).finder_->
  160. find(Name("ns.test2.example"), RRType::A())->code);
  161. EXPECT_EQ(ZoneFinder::NXRRSET, server.getClientList(RRClass::IN())->
  162. find(Name("ns.test2.example")).finder_->
  163. find(Name("ns.test2.example"), RRType::AAAA())->code);
  164. }
  165. void
  166. configureZones(AuthSrv& server) {
  167. ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR "/test1.zone.in "
  168. TEST_DATA_BUILDDIR "/test1.zone.copied"));
  169. ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR "/test2.zone.in "
  170. TEST_DATA_BUILDDIR "/test2.zone.copied"));
  171. const ConstElementPtr config(Element::fromJSON("{"
  172. "\"IN\": [{"
  173. " \"type\": \"MasterFiles\","
  174. " \"params\": {"
  175. " \"test1.example\": \"" +
  176. string(TEST_DATA_BUILDDIR "/test1.zone.copied") + "\","
  177. " \"test2.example\": \"" +
  178. string(TEST_DATA_BUILDDIR "/test2.zone.copied") + "\""
  179. " },"
  180. " \"cache-enable\": true"
  181. "}]}"));
  182. configureDataSource(server, config);
  183. zoneChecks(server);
  184. }
  185. void
  186. newZoneChecks(AuthSrv& server) {
  187. isc::util::thread::Mutex::Locker locker(server.getClientListMutex());
  188. EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
  189. find(Name("ns.test1.example")).finder_->
  190. find(Name("ns.test1.example"), RRType::A())->code);
  191. // now test1.example should have ns/AAAA
  192. EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
  193. find(Name("ns.test1.example")).finder_->
  194. find(Name("ns.test1.example"), RRType::AAAA())->code);
  195. // test2.example shouldn't change
  196. EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
  197. find(Name("ns.test2.example")).finder_->
  198. find(Name("ns.test2.example"), RRType::A())->code);
  199. EXPECT_EQ(ZoneFinder::NXRRSET, server.getClientList(RRClass::IN())->
  200. find(Name("ns.test2.example")).finder_->
  201. find(Name("ns.test2.example"), RRType::AAAA())->code);
  202. }
  203. TEST_F(AuthCommandTest, loadZone) {
  204. configureZones(server_);
  205. ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
  206. "/test1-new.zone.in "
  207. TEST_DATA_BUILDDIR "/test1.zone.copied"));
  208. ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
  209. "/test2-new.zone.in "
  210. TEST_DATA_BUILDDIR "/test2.zone.copied"));
  211. result_ = execAuthServerCommand(server_, "loadzone",
  212. Element::fromJSON(
  213. "{\"origin\": \"test1.example\"}"));
  214. checkAnswer(0);
  215. newZoneChecks(server_);
  216. }
  217. TEST_F(AuthCommandTest,
  218. #ifdef USE_STATIC_LINK
  219. DISABLED_loadZoneSQLite3
  220. #else
  221. loadZoneSQLite3
  222. #endif
  223. )
  224. {
  225. // Prepare the database first
  226. const string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
  227. const string bad_db = TEST_DATA_BUILDDIR "/does-not-exist.sqlite3";
  228. stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
  229. createSQLite3DB(RRClass::IN(), Name("example.org"), test_db.c_str(), ss);
  230. // This describes the data source in the configuration
  231. const ConstElementPtr config(Element::fromJSON("{"
  232. "\"IN\": [{"
  233. " \"type\": \"sqlite3\","
  234. " \"params\": {\"database_file\": \"" + test_db + "\"},"
  235. " \"cache-enable\": true,"
  236. " \"cache-zones\": [\"example.org\"]"
  237. "}]}"));
  238. configureDataSource(server_, config);
  239. {
  240. isc::util::thread::Mutex::Locker locker(server_.getClientListMutex());
  241. // Check that the A record at www.example.org does not exist
  242. EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getClientList(RRClass::IN())->
  243. find(Name("example.org")).finder_->
  244. find(Name("www.example.org"), RRType::A())->code);
  245. // Add the record to the underlying sqlite database, by loading
  246. // it as a separate datasource, and updating it
  247. ConstElementPtr sql_cfg = Element::fromJSON("{ \"type\": \"sqlite3\","
  248. "\"database_file\": \""
  249. + test_db + "\"}");
  250. DataSourceClientContainer sql_ds("sqlite3", sql_cfg);
  251. ZoneUpdaterPtr sql_updater =
  252. sql_ds.getInstance().getUpdater(Name("example.org"), false);
  253. RRsetPtr rrset(new RRset(Name("www.example.org."), RRClass::IN(),
  254. RRType::A(), RRTTL(60)));
  255. rrset->addRdata(rdata::createRdata(rrset->getType(),
  256. rrset->getClass(),
  257. "192.0.2.1"));
  258. sql_updater->addRRset(*rrset);
  259. sql_updater->commit();
  260. EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getClientList(RRClass::IN())->
  261. find(Name("example.org")).finder_->
  262. find(Name("www.example.org"), RRType::A())->code);
  263. }
  264. // Now send the command to reload it
  265. result_ = execAuthServerCommand(server_, "loadzone",
  266. Element::fromJSON(
  267. "{\"origin\": \"example.org\"}"));
  268. checkAnswer(0, "Successful load");
  269. {
  270. isc::util::thread::Mutex::Locker locker(server_.getClientListMutex());
  271. // And now it should be present too.
  272. EXPECT_EQ(ZoneFinder::SUCCESS, server_.getClientList(RRClass::IN())->
  273. find(Name("example.org")).finder_->
  274. find(Name("www.example.org"), RRType::A())->code);
  275. }
  276. // Some error cases. First, the zone has no configuration. (note .com here)
  277. result_ = execAuthServerCommand(server_, "loadzone",
  278. Element::fromJSON("{\"origin\": \"example.com\"}"));
  279. checkAnswer(1, "example.com");
  280. {
  281. isc::util::thread::Mutex::Locker locker(server_.getClientListMutex());
  282. // The previous zone is not hurt in any way
  283. EXPECT_EQ(ZoneFinder::SUCCESS, server_.getClientList(RRClass::IN())->
  284. find(Name("example.org")).finder_->
  285. find(Name("example.org"), RRType::SOA())->code);
  286. }
  287. const ConstElementPtr config2(Element::fromJSON("{"
  288. "\"IN\": [{"
  289. " \"type\": \"sqlite3\","
  290. " \"params\": {\"database_file\": \"" + bad_db + "\"},"
  291. " \"cache-enable\": true,"
  292. " \"cache-zones\": [\"example.com\"]"
  293. "}]}"));
  294. EXPECT_THROW(configureDataSource(server_, config2),
  295. ConfigurableClientList::ConfigurationError);
  296. result_ = execAuthServerCommand(server_, "loadzone",
  297. Element::fromJSON("{\"origin\": \"example.com\"}"));
  298. checkAnswer(1, "Unreadable");
  299. isc::util::thread::Mutex::Locker locker(server_.getClientListMutex());
  300. // The previous zone is not hurt in any way
  301. EXPECT_EQ(ZoneFinder::SUCCESS, server_.getClientList(RRClass::IN())->
  302. find(Name("example.org")).finder_->
  303. find(Name("example.org"), RRType::SOA())->code);
  304. }
  305. TEST_F(AuthCommandTest, loadBrokenZone) {
  306. configureZones(server_);
  307. ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
  308. "/test1-broken.zone.in "
  309. TEST_DATA_BUILDDIR "/test1.zone.copied"));
  310. result_ = execAuthServerCommand(server_, "loadzone",
  311. Element::fromJSON(
  312. "{\"origin\": \"test1.example\"}"));
  313. checkAnswer(1);
  314. zoneChecks(server_); // zone shouldn't be replaced
  315. }
  316. TEST_F(AuthCommandTest, loadUnreadableZone) {
  317. configureZones(server_);
  318. // install the zone file as unreadable
  319. ASSERT_EQ(0, system(INSTALL_PROG " -c -m 000 " TEST_DATA_DIR
  320. "/test1.zone.in "
  321. TEST_DATA_BUILDDIR "/test1.zone.copied"));
  322. result_ = execAuthServerCommand(server_, "loadzone",
  323. Element::fromJSON(
  324. "{\"origin\": \"test1.example\"}"));
  325. checkAnswer(1);
  326. zoneChecks(server_); // zone shouldn't be replaced
  327. }
  328. TEST_F(AuthCommandTest, loadZoneWithoutDataSrc) {
  329. // try to execute load command without configuring the zone beforehand.
  330. // it should fail.
  331. result_ = execAuthServerCommand(server_, "loadzone",
  332. Element::fromJSON(
  333. "{\"origin\": \"test1.example\"}"));
  334. checkAnswer(1);
  335. }
  336. TEST_F(AuthCommandTest, loadZoneInvalidParams) {
  337. configureZones(server_);
  338. // null arg
  339. result_ = execAuthServerCommand(server_, "loadzone", ElementPtr());
  340. checkAnswer(1, "Null arg");
  341. // zone class is bogus
  342. result_ = execAuthServerCommand(server_, "loadzone",
  343. Element::fromJSON(
  344. "{\"origin\": \"test1.example\","
  345. " \"class\": \"no_such_class\"}"));
  346. checkAnswer(1, "No such class");
  347. result_ = execAuthServerCommand(server_, "loadzone",
  348. Element::fromJSON(
  349. "{\"origin\": \"test1.example\","
  350. " \"class\": 1}"));
  351. checkAnswer(1, "Integral class");
  352. // origin is missing
  353. result_ = execAuthServerCommand(server_, "loadzone",
  354. Element::fromJSON("{}"));
  355. checkAnswer(1, "Missing origin");
  356. // zone doesn't exist in the data source
  357. result_ = execAuthServerCommand(server_, "loadzone",
  358. Element::fromJSON("{\"origin\": \"xx\"}"));
  359. checkAnswer(1, "No such zone");
  360. // origin is bogus
  361. result_ = execAuthServerCommand(server_, "loadzone",
  362. Element::fromJSON(
  363. "{\"origin\": \"...\"}"));
  364. checkAnswer(1, "Wrong name");
  365. result_ = execAuthServerCommand(server_, "loadzone",
  366. Element::fromJSON("{\"origin\": 10}"));
  367. checkAnswer(1, "Integral name");
  368. }
  369. TEST_F(AuthCommandTest, getStats) {
  370. result_ = execAuthServerCommand(server_, "getstats", ConstElementPtr());
  371. parseAnswer(rcode_, result_);
  372. // Just check the command execution succeeded. Detailed tests specific to
  373. // statistics are done in its own tests.
  374. EXPECT_EQ(0, rcode_);
  375. }
  376. }