d2_udp_unittest.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. // Copyright (C) 2014, 2015 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. /// @file d2_upd_unittest.cc Unit tests for D2ClientMgr UDP communications.
  15. /// Note these tests are not intended to verify the actual send and receive
  16. /// across UDP sockets. This level of testing is done in libdhcp-ddns.
  17. #include <boost/asio.hpp>
  18. #include <asiolink/io_service.h>
  19. #include <config.h>
  20. #include <dhcp/iface_mgr.h>
  21. #include <dhcpsrv/d2_client_mgr.h>
  22. #include <exceptions/exceptions.h>
  23. #include <boost/function.hpp>
  24. #include <boost/bind.hpp>
  25. #include <gtest/gtest.h>
  26. #include <sys/select.h>
  27. using namespace std;
  28. using namespace isc::dhcp;
  29. using namespace isc;
  30. namespace {
  31. /// @brief Test fixture for excerising D2ClientMgr send management
  32. /// services. It inherents from D2ClientMgr to allow overriding various
  33. /// methods and accessing otherwise restricted member. In particular it
  34. /// overrides the NameChangeSender completion completion callback, allowing
  35. /// the injection of send errors.
  36. class D2ClientMgrTest : public D2ClientMgr, public ::testing::Test {
  37. public:
  38. /// @brief If true simulates a send which completed with a failed status.
  39. bool simulate_send_failure_;
  40. /// @brief If true causes an exception throw in the client error handler.
  41. bool error_handler_throw_;
  42. /// @brief Tracks the number times the completion handler is called.
  43. int callback_count_;
  44. /// @brief Tracks the number of times the client error handler was called.
  45. int error_handler_count_;
  46. /// @brief Constructor
  47. D2ClientMgrTest() : simulate_send_failure_(false),
  48. error_handler_throw_(false),
  49. callback_count_(0), error_handler_count_(0) {
  50. }
  51. /// @brief virtual Destructor
  52. virtual ~D2ClientMgrTest(){
  53. }
  54. /// @brief Updates the D2ClientMgr's configuration to DDNS enabled.
  55. ///
  56. /// @param server_address IP address of kea-dhcp-ddns.
  57. /// @param server_port IP port number of kea-dhcp-ddns.
  58. /// @param protocol NCR protocol to use. (Currently only UDP is
  59. /// supported).
  60. void enableDdns(const std::string& server_address,
  61. const size_t server_port,
  62. const dhcp_ddns::NameChangeProtocol protocol) {
  63. // Update the configuration with one that is enabled.
  64. D2ClientConfigPtr new_cfg;
  65. isc::asiolink::IOAddress server_ip(server_address);
  66. isc::asiolink::IOAddress sender_ip(server_ip.isV4() ?
  67. D2ClientConfig::DFT_V4_SENDER_IP :
  68. D2ClientConfig::DFT_V6_SENDER_IP);
  69. ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
  70. server_ip, server_port,
  71. sender_ip, D2ClientConfig::DFT_SENDER_PORT,
  72. D2ClientConfig::DFT_MAX_QUEUE_SIZE,
  73. protocol, dhcp_ddns::FMT_JSON,
  74. true, true, true, true,
  75. "myhost", ".example.com.")));
  76. ASSERT_NO_THROW(setD2ClientConfig(new_cfg));
  77. ASSERT_TRUE(ddnsEnabled());
  78. }
  79. /// @brief Checks sender's select-fd against an expected state of readiness.
  80. ///
  81. /// Uses select() to determine if the sender's select_fd is marked as
  82. /// ready to read, and compares this against the expected state. The
  83. /// select function is called with a timeout of 0.0 (non blocking).
  84. ///
  85. /// @param expect_ready Expected state of readiness (True if expecting
  86. /// a ready to ready result, false if expecting otherwise).
  87. void selectCheck(bool expect_ready) {
  88. fd_set read_fds;
  89. int maxfd = 0;
  90. FD_ZERO(&read_fds);
  91. // cppcheck-suppress redundantAssignment
  92. int select_fd = -1;
  93. ASSERT_NO_THROW(
  94. // cppcheck-suppress redundantAssignment
  95. select_fd = getSelectFd()
  96. );
  97. FD_SET(select_fd, &read_fds);
  98. maxfd = select_fd;
  99. struct timeval select_timeout;
  100. select_timeout.tv_sec = 0;
  101. select_timeout.tv_usec = 0;
  102. int result = (select(maxfd + 1, &read_fds, NULL, NULL,
  103. &select_timeout));
  104. if (result < 0) {
  105. const char *errstr = strerror(errno);
  106. FAIL() << "select failed :" << errstr;
  107. }
  108. if (expect_ready) {
  109. ASSERT_TRUE(result > 0);
  110. } else {
  111. ASSERT_TRUE(result == 0);
  112. }
  113. }
  114. /// @brief Overrides base class completion callback.
  115. ///
  116. /// This method will be invoked each time a send completes. It allows
  117. /// intervention prior to calling the production implemenation in the
  118. /// base. If simulate_send_failure_ is true, the base call impl will
  119. /// be called with an error status, otherwise it will be called with
  120. /// the result paramater given.
  121. ///
  122. /// @param result Result code of the send operation.
  123. /// @param ncr NameChangeRequest which failed to send.
  124. virtual void operator()(const dhcp_ddns::NameChangeSender::Result result,
  125. dhcp_ddns::NameChangeRequestPtr& ncr) {
  126. ++callback_count_;
  127. if (simulate_send_failure_) {
  128. simulate_send_failure_ = false;
  129. D2ClientMgr::operator()(dhcp_ddns::NameChangeSender::ERROR, ncr);
  130. } else {
  131. D2ClientMgr::operator()(result, ncr);
  132. }
  133. }
  134. /// @brief Serves as the "application level" client error handler.
  135. ///
  136. /// This method is passed into calls to startSender as the client error
  137. /// handler. It should be invoked whenever the completion callback is
  138. /// passed a result other than SUCCESS. If error_handler_throw_
  139. /// is true it will throw an exception.
  140. ///
  141. /// @param result unused - Result code of the send operation.
  142. /// @param ncr unused -NameChangeRequest which failed to send.
  143. void error_handler(const dhcp_ddns::NameChangeSender::Result /*result*/,
  144. dhcp_ddns::NameChangeRequestPtr& /*ncr*/) {
  145. if (error_handler_throw_) {
  146. error_handler_throw_ = false;
  147. isc_throw(isc::InvalidOperation, "Simulated client handler throw");
  148. }
  149. ++error_handler_count_;
  150. }
  151. /// @brief Returns D2ClientErroHandler bound to this::error_handler_.
  152. D2ClientErrorHandler getErrorHandler() {
  153. return (boost::bind(&D2ClientMgrTest::error_handler, this, _1, _2));
  154. }
  155. /// @brief Contructs a NameChangeRequest message from a fixed JSON string.
  156. dhcp_ddns::NameChangeRequestPtr buildTestNcr() {
  157. // Build an NCR from json string.
  158. const char* ncr_str =
  159. "{"
  160. " \"change-type\" : 0 , "
  161. " \"forward-change\" : true , "
  162. " \"reverse-change\" : false , "
  163. " \"fqdn\" : \"myhost.example.com.\" , "
  164. " \"ip-address\" : \"192.168.2.1\" , "
  165. " \"dhcid\" : \"010203040A7F8E3D\" , "
  166. " \"lease-expires-on\" : \"20140121132405\" , "
  167. " \"lease-length\" : 1300 "
  168. "}";
  169. return (dhcp_ddns::NameChangeRequest::fromJSON(ncr_str));
  170. }
  171. /// Expose restricted members.
  172. using D2ClientMgr::getSelectFd;
  173. };
  174. /// @brief Checks that D2ClientMgr disable and enable a UDP sender.
  175. TEST_F(D2ClientMgrTest, udpSenderEnableDisable) {
  176. // Verify DDNS is disabled by default.
  177. ASSERT_FALSE(ddnsEnabled());
  178. // Verify we are not in send mode.
  179. ASSERT_FALSE(amSending());
  180. // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
  181. enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
  182. ASSERT_FALSE(amSending());
  183. ASSERT_NO_THROW(startSender(getErrorHandler()));
  184. ASSERT_TRUE(amSending());
  185. // Verify that we take sender out of send mode.
  186. ASSERT_NO_THROW(stopSender());
  187. ASSERT_FALSE(amSending());
  188. }
  189. /// @brief Checks D2ClientMgr queuing methods with a UDP sender.
  190. TEST_F(D2ClientMgrTest, udpSenderQueing) {
  191. // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
  192. enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
  193. ASSERT_FALSE(amSending());
  194. // Queue should be empty.
  195. EXPECT_EQ(0, getQueueSize());
  196. // Trying to peek past the end of the queue should throw.
  197. EXPECT_THROW(peekAt(1), dhcp_ddns::NcrSenderError);
  198. // Trying to send a NCR when not in send mode should fail.
  199. dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
  200. EXPECT_THROW(sendRequest(ncr), D2ClientError);
  201. // Place sender in send mode.
  202. ASSERT_NO_THROW(startSender(getErrorHandler()));
  203. ASSERT_TRUE(amSending());
  204. // Send should succeed now.
  205. ASSERT_NO_THROW(sendRequest(ncr));
  206. // Queue should have 1 entry.
  207. EXPECT_EQ(1, getQueueSize());
  208. // Attempt to fetch the entry we just queued.
  209. dhcp_ddns::NameChangeRequestPtr ncr2;
  210. ASSERT_NO_THROW(ncr2 = peekAt(0));
  211. // Verify what we queued matches what we fetched.
  212. EXPECT_TRUE(*ncr == *ncr2);
  213. // Clearing the queue while in send mode should fail.
  214. ASSERT_THROW(clearQueue(), dhcp_ddns::NcrSenderError);
  215. // We should still have 1 in the queue.
  216. EXPECT_EQ(1, getQueueSize());
  217. // Get out of send mode.
  218. ASSERT_NO_THROW(stopSender());
  219. ASSERT_FALSE(amSending());
  220. // Clear queue should succeed now.
  221. ASSERT_NO_THROW(clearQueue());
  222. EXPECT_EQ(0, getQueueSize());
  223. }
  224. /// @brief Checks that D2ClientMgr can send with a UDP sender and
  225. /// a private IOService.
  226. TEST_F(D2ClientMgrTest, udpSend) {
  227. // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
  228. enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
  229. // Trying to fetch the select-fd when not sending should fail.
  230. ASSERT_THROW(getSelectFd(), D2ClientError);
  231. // Place sender in send mode.
  232. ASSERT_NO_THROW(startSender(getErrorHandler()));
  233. // select_fd should evaluate to NOT ready to read.
  234. selectCheck(false);
  235. // Build a test request and send it.
  236. dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
  237. ASSERT_NO_THROW(sendRequest(ncr));
  238. // select_fd should evaluate to ready to read.
  239. selectCheck(true);
  240. // Call service handler.
  241. runReadyIO();
  242. // select_fd should evaluate to not ready to read.
  243. selectCheck(false);
  244. }
  245. /// @brief Checks that D2ClientMgr can send with a UDP sender and
  246. /// an external IOService.
  247. TEST_F(D2ClientMgrTest, udpSendExternalIOService) {
  248. // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
  249. enableDdns("127.0.0.1", 53001, dhcp_ddns::NCR_UDP);
  250. // Place sender in send mode using an external IO service.
  251. asiolink::IOService io_service;
  252. ASSERT_NO_THROW(startSender(getErrorHandler(), io_service));
  253. // select_fd should evaluate to NOT ready to read.
  254. selectCheck(false);
  255. // Build a test request and send it.
  256. dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
  257. ASSERT_NO_THROW(sendRequest(ncr));
  258. // select_fd should evaluate to ready to read.
  259. selectCheck(true);
  260. // Call service handler.
  261. runReadyIO();
  262. // select_fd should evaluate to not ready to read.
  263. selectCheck(false);
  264. // Explicitly stop the sender. This ensures the sender's
  265. // ASIO socket is closed prior to the local io_service
  266. // instance goes out of scope.
  267. ASSERT_NO_THROW(stopSender());
  268. }
  269. /// @brief Checks that D2ClientMgr can send with a UDP sender and
  270. /// an external IOService.
  271. TEST_F(D2ClientMgrTest, udpSendExternalIOService6) {
  272. // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
  273. enableDdns("::1", 53001, dhcp_ddns::NCR_UDP);
  274. // Place sender in send mode using an external IO service.
  275. asiolink::IOService io_service;
  276. ASSERT_NO_THROW(startSender(getErrorHandler(), io_service));
  277. // select_fd should evaluate to NOT ready to read.
  278. selectCheck(false);
  279. // Build a test request and send it.
  280. dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
  281. ASSERT_NO_THROW(sendRequest(ncr));
  282. // select_fd should evaluate to ready to read.
  283. selectCheck(true);
  284. // Call service handler.
  285. runReadyIO();
  286. // select_fd should evaluate to not ready to read.
  287. selectCheck(false);
  288. // Explicitly stop the sender. This ensures the sender's
  289. // ASIO socket is closed prior to the local io_service
  290. // instance goes out of scope.
  291. ASSERT_NO_THROW(stopSender());
  292. }
  293. /// @brief Checks that D2ClientMgr invokes the client error handler
  294. /// when send errors occur.
  295. TEST_F(D2ClientMgrTest, udpSendErrorHandler) {
  296. // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
  297. // Place sender in send mode.
  298. enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
  299. ASSERT_NO_THROW(startSender(getErrorHandler()));
  300. // Simulate a failed response in the send call back. This should
  301. // cause the error handler to get invoked.
  302. simulate_send_failure_ = true;
  303. // Verify error count is zero.
  304. ASSERT_EQ(0, error_handler_count_);
  305. // Send a test request.
  306. dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
  307. ASSERT_NO_THROW(sendRequest(ncr));
  308. // Call the ready handler. This should complete the message with an error.
  309. ASSERT_NO_THROW(runReadyIO());
  310. // If we executed error handler properly, the error count should one.
  311. ASSERT_EQ(1, error_handler_count_);
  312. }
  313. /// @brief Checks that client error handler exceptions are handled gracefully.
  314. TEST_F(D2ClientMgrTest, udpSendErrorHandlerThrow) {
  315. // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
  316. // Place sender in send mode.
  317. enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
  318. ASSERT_NO_THROW(startSender(getErrorHandler()));
  319. // Simulate a failed response in the send call back and
  320. // force a throw in the error handler.
  321. simulate_send_failure_ = true;
  322. error_handler_throw_ = true;
  323. // Verify error count is zero.
  324. ASSERT_EQ(0, error_handler_count_);
  325. // Send a test request.
  326. dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
  327. ASSERT_NO_THROW(sendRequest(ncr));
  328. // Call the ready handler. This should complete the message with an error.
  329. // The handler should throw but the exception should not escape.
  330. ASSERT_NO_THROW(runReadyIO());
  331. // If throw flag is false, then we were in the error handler should
  332. // have thrown.
  333. ASSERT_FALSE(error_handler_throw_);
  334. // If error count is still zero, then we did throw.
  335. ASSERT_EQ(0, error_handler_count_);
  336. }
  337. /// @brief Tests that D2ClientMgr registers and unregisters with IfaceMgr.
  338. TEST_F(D2ClientMgrTest, ifaceRegister) {
  339. // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
  340. enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
  341. // Place sender in send mode.
  342. ASSERT_NO_THROW(startSender(getErrorHandler()));
  343. // Queue three messages.
  344. for (unsigned i = 0; i < 3; ++i) {
  345. dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
  346. ASSERT_NO_THROW(sendRequest(ncr));
  347. }
  348. // Make sure queue count is correct.
  349. EXPECT_EQ(3, getQueueSize());
  350. // select_fd should evaluate to ready to read.
  351. selectCheck(true);
  352. // Calling receive should complete the first message and start the second.
  353. IfaceMgr::instance().receive4(0, 0);
  354. // Verify the callback hander was invoked, no errors counted.
  355. EXPECT_EQ(2, getQueueSize());
  356. ASSERT_EQ(1, callback_count_);
  357. ASSERT_EQ(0, error_handler_count_);
  358. // Stop the sender. This should complete the second message but leave
  359. // the third in the queue.
  360. ASSERT_NO_THROW(stopSender());
  361. EXPECT_EQ(1, getQueueSize());
  362. ASSERT_EQ(2, callback_count_);
  363. ASSERT_EQ(0, error_handler_count_);
  364. // Calling receive again should have no affect.
  365. IfaceMgr::instance().receive4(0, 0);
  366. EXPECT_EQ(1, getQueueSize());
  367. ASSERT_EQ(2, callback_count_);
  368. ASSERT_EQ(0, error_handler_count_);
  369. }
  370. /// @brief Checks that D2ClientMgr suspendUpdates works properly.
  371. TEST_F(D2ClientMgrTest, udpSuspendUpdates) {
  372. // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
  373. // Place sender in send mode.
  374. enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
  375. ASSERT_NO_THROW(startSender(getErrorHandler()));
  376. // Send a test request.
  377. for (unsigned i = 0; i < 3; ++i) {
  378. dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
  379. ASSERT_NO_THROW(sendRequest(ncr));
  380. }
  381. ASSERT_EQ(3, getQueueSize());
  382. // Call the ready handler. This should complete the first message
  383. // and initiate sending the second message.
  384. ASSERT_NO_THROW(runReadyIO());
  385. // Queue count should have gone down by 1.
  386. ASSERT_EQ(2, getQueueSize());
  387. // Suspend updates. This should disable updates and stop the sender.
  388. ASSERT_NO_THROW(suspendUpdates());
  389. EXPECT_FALSE(ddnsEnabled());
  390. EXPECT_FALSE(amSending());
  391. // Stopping the sender should have completed the second message's
  392. // in-progess send, so queue size should be 1.
  393. ASSERT_EQ(1, getQueueSize());
  394. }
  395. /// @brief Tests that invokeErrorHandler does not fail if there is no handler.
  396. TEST_F(D2ClientMgrTest, missingErrorHandler) {
  397. // Ensure we aren't in send mode.
  398. ASSERT_FALSE(ddnsEnabled());
  399. ASSERT_FALSE(amSending());
  400. // There is no error handler at this point, so invoking should not throw.
  401. dhcp_ddns::NameChangeRequestPtr ncr;
  402. ASSERT_NO_THROW(invokeClientErrorHandler(dhcp_ddns::NameChangeSender::ERROR,
  403. ncr));
  404. // Verify we didn't invoke the error handler, error count is zero.
  405. ASSERT_EQ(0, error_handler_count_);
  406. }
  407. } // end of anonymous namespace