session_unittests.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. // Copyright (C) 2009 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. // for some IPC/network system calls in asio/detail/pipe_select_interrupter.hpp
  16. #include <unistd.h>
  17. // XXX: the ASIO header must be included before others. See session.cc.
  18. #include <asio.hpp>
  19. #include <cc/session.h>
  20. #include <cc/data.h>
  21. #include <cc/tests/session_unittests_config.h>
  22. #include <gtest/gtest.h>
  23. #include <boost/bind.hpp>
  24. #include <exceptions/exceptions.h>
  25. #include <utility>
  26. #include <vector>
  27. #include <string>
  28. using namespace isc::cc;
  29. using std::pair;
  30. using std::vector;
  31. using std::string;
  32. using isc::data::ConstElementPtr;
  33. using isc::data::Element;
  34. TEST(AsioSession, establish) {
  35. asio::io_service io_service_;
  36. Session sess(io_service_);
  37. // can't return socket desciptor before session is established
  38. EXPECT_THROW(sess.getSocketDesc(), isc::InvalidOperation);
  39. EXPECT_THROW(
  40. sess.establish("/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/"
  41. "/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/"
  42. "/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/"
  43. "/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/"
  44. "/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/"
  45. "/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/"
  46. "/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/"
  47. "/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/"
  48. "/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/"
  49. "/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/"
  50. ), isc::cc::SessionError
  51. );
  52. }
  53. // This class sets up a domain socket for the session to connect to
  54. // it will impersonate the msgq a tiny bit (if setSendLname() has
  55. // been called, it will send an 'answer' to the lname query that is
  56. // sent in the initialization of Session objects)
  57. class TestDomainSocket {
  58. public:
  59. TestDomainSocket(asio::io_service& io_service, const char* file) :
  60. io_service_(io_service),
  61. ep_(file),
  62. acceptor_(io_service_, ep_),
  63. socket_(io_service_)
  64. {
  65. acceptor_.async_accept(socket_,
  66. boost::bind(&TestDomainSocket::acceptHandler,
  67. this, _1));
  68. }
  69. ~TestDomainSocket() {
  70. socket_.close();
  71. unlink(BIND10_TEST_SOCKET_FILE);
  72. }
  73. void acceptHandler(const asio::error_code&) const {
  74. }
  75. void sendmsg(isc::data::ElementPtr& env, isc::data::ElementPtr& msg) {
  76. const std::string header_wire = env->toWire();
  77. const std::string body_wire = msg->toWire();
  78. const unsigned int length = 2 + header_wire.length() +
  79. body_wire.length();
  80. const unsigned int length_net = htonl(length);
  81. const unsigned short header_length = header_wire.length();
  82. const unsigned short header_length_net = htons(header_length);
  83. socket_.send(asio::buffer(&length_net, sizeof(length_net)));
  84. socket_.send(asio::buffer(&header_length_net,
  85. sizeof(header_length_net)));
  86. socket_.send(asio::buffer(header_wire.data(), header_length));
  87. socket_.send(asio::buffer(body_wire.data(), body_wire.length()));
  88. }
  89. /// Pair holding header and data of a message sent over the wire.
  90. typedef pair<ConstElementPtr, ConstElementPtr> SentMessage;
  91. /// \brief Read a message from the socket
  92. ///
  93. /// Read a message from the socket and parse it. Block until it is
  94. /// read or error happens. If error happens, it throws isc::Unexpected.
  95. ///
  96. /// This method would block for ever if the sender is not sending.
  97. /// But the whole test has a timeout of 10 seconds (see the
  98. /// SessionTest::SetUp and SessionTest::TearDown).
  99. ///
  100. /// \note The method assumes the wire data are correct and does not check
  101. /// it. Strange things might happen if it is not the case, but the
  102. /// test would likely fail as a result, so we prefer simplicity here.
  103. ///
  104. /// \return Pair containing the header and body elements (in this order).
  105. SentMessage readmsg() {
  106. // The format is:
  107. // <uint32_t in net order = total length>
  108. // <uint16_t in net order = header length>
  109. // <char * header length = the header>
  110. // <char * the rest of the total length = the data>
  111. uint32_t total_len_data;
  112. if (asio::read(socket_, asio::buffer(&total_len_data,
  113. sizeof total_len_data)) !=
  114. sizeof total_len_data) {
  115. isc_throw(isc::Unexpected, "Error while reading total length");
  116. }
  117. const uint32_t total_len = ntohl(total_len_data);
  118. uint16_t header_len_data;
  119. if (asio::read(socket_, asio::buffer(&header_len_data,
  120. sizeof header_len_data)) !=
  121. sizeof header_len_data) {
  122. isc_throw(isc::Unexpected, "Error while reading header length");
  123. }
  124. const uint16_t header_len = ntohs(header_len_data);
  125. // We use char, not unsigned char, because we want to make a string
  126. // out of it.
  127. vector<char> raw_data;
  128. raw_data.resize(total_len - sizeof header_len_data);
  129. if (asio::read(socket_,
  130. asio::buffer(&raw_data[0],
  131. total_len - sizeof header_len_data)) !=
  132. total_len - header_len_data) {
  133. isc_throw(isc::Unexpected, "Error while reading data");
  134. }
  135. // Extract the right data into each string and convert.
  136. return (SentMessage(
  137. Element::fromWire(string(raw_data.begin(),
  138. raw_data.begin() + header_len)),
  139. Element::fromWire(string(raw_data.begin() + header_len,
  140. raw_data.end()))));
  141. }
  142. void sendLname() {
  143. isc::data::ElementPtr lname_answer1 =
  144. isc::data::Element::fromJSON("{ \"type\": \"lname\" }");
  145. isc::data::ElementPtr lname_answer2 =
  146. isc::data::Element::fromJSON("{ \"lname\": \"foobar\" }");
  147. sendmsg(lname_answer1, lname_answer2);
  148. }
  149. void setSendLname() {
  150. // ignore whatever data we get, send back an lname
  151. asio::async_read(socket_, asio::buffer(data_buf, 0),
  152. boost::bind(&TestDomainSocket::sendLname, this));
  153. }
  154. private:
  155. asio::io_service& io_service_;
  156. asio::local::stream_protocol::endpoint ep_;
  157. asio::local::stream_protocol::acceptor acceptor_;
  158. asio::local::stream_protocol::socket socket_;
  159. char data_buf[1024];
  160. };
  161. class SessionTest : public ::testing::Test {
  162. protected:
  163. SessionTest() : sess(my_io_service), work(my_io_service) {
  164. // The TestDomainSocket is held as a 'new'-ed pointer,
  165. // so we can call unlink() first.
  166. unlink(BIND10_TEST_SOCKET_FILE);
  167. tds = new TestDomainSocket(my_io_service, BIND10_TEST_SOCKET_FILE);
  168. }
  169. ~SessionTest() {
  170. delete tds;
  171. }
  172. void SetUp() {
  173. // There are blocking reads in some tests. We want to have a safety
  174. // catch in case the sender didn't write enough. We set a timeout of
  175. // 10 seconds per one test (which should really be enough even on
  176. // slow machines). If the timeout happens, it kills the test and
  177. // the whole test fails.
  178. alarm(10);
  179. }
  180. void TearDown() {
  181. // Cancel the timeout scheduled in SetUp. We don't want to kill any
  182. // of the other tests by it by accident.
  183. alarm(0);
  184. }
  185. public:
  186. // used in the handler test
  187. // This handler first reads (and ignores) whatever message caused
  188. // it to be invoked. Then it calls group_recv for a second message.
  189. // If this message is { "command": "stop" } it'll tell the
  190. // io_service it is done. Otherwise it'll re-register this handler
  191. void someHandler() {
  192. isc::data::ConstElementPtr env, msg;
  193. sess.group_recvmsg(env, msg, false, -1);
  194. sess.group_recvmsg(env, msg, false, -1);
  195. if (msg && msg->contains("command") &&
  196. msg->get("command")->stringValue() == "stop") {
  197. my_io_service.stop();
  198. } else {
  199. sess.startRead(boost::bind(&SessionTest::someHandler, this));
  200. }
  201. }
  202. protected:
  203. asio::io_service my_io_service;
  204. TestDomainSocket* tds;
  205. Session sess;
  206. // Keep run() from stopping right away by informing it it has work to do
  207. asio::io_service::work work;
  208. };
  209. TEST_F(SessionTest, timeout_on_connect) {
  210. // set to a short timeout so the test doesn't take too long
  211. EXPECT_EQ(4000, sess.getTimeout());
  212. sess.setTimeout(100);
  213. EXPECT_EQ(100, sess.getTimeout());
  214. // no answer, should timeout
  215. EXPECT_THROW(sess.establish(BIND10_TEST_SOCKET_FILE), SessionTimeout);
  216. }
  217. TEST_F(SessionTest, connect_ok) {
  218. tds->setSendLname();
  219. sess.establish(BIND10_TEST_SOCKET_FILE);
  220. }
  221. TEST_F(SessionTest, connect_ok_no_timeout) {
  222. tds->setSendLname();
  223. sess.setTimeout(0);
  224. sess.establish(BIND10_TEST_SOCKET_FILE);
  225. }
  226. TEST_F(SessionTest, connect_ok_connection_reset) {
  227. tds->setSendLname();
  228. sess.establish(BIND10_TEST_SOCKET_FILE);
  229. // Close the session again, so the next recv() should throw
  230. sess.disconnect();
  231. isc::data::ConstElementPtr env, msg;
  232. EXPECT_THROW(sess.group_recvmsg(env, msg, false, -1), SessionError);
  233. }
  234. TEST_F(SessionTest, run_with_handler) {
  235. tds->setSendLname();
  236. sess.establish(BIND10_TEST_SOCKET_FILE);
  237. sess.startRead(boost::bind(&SessionTest::someHandler, this));
  238. isc::data::ElementPtr env = isc::data::Element::fromJSON("{ \"to\": \"me\" }");
  239. isc::data::ElementPtr msg = isc::data::Element::fromJSON("{ \"some\": \"message\" }");
  240. tds->sendmsg(env, msg);
  241. msg = isc::data::Element::fromJSON("{ \"another\": \"message\" }");
  242. tds->sendmsg(env, msg);
  243. msg = isc::data::Element::fromJSON("{ \"a third\": \"message\" }");
  244. tds->sendmsg(env, msg);
  245. msg = isc::data::Element::fromJSON("{ \"command\": \"stop\" }");
  246. tds->sendmsg(env, msg);
  247. size_t count = my_io_service.run();
  248. ASSERT_EQ(2, count);
  249. }
  250. TEST_F(SessionTest, run_with_handler_timeout) {
  251. tds->setSendLname();
  252. sess.establish(BIND10_TEST_SOCKET_FILE);
  253. sess.startRead(boost::bind(&SessionTest::someHandler, this));
  254. sess.setTimeout(100);
  255. isc::data::ElementPtr env = isc::data::Element::fromJSON("{ \"to\": \"me\" }");
  256. isc::data::ElementPtr msg = isc::data::Element::fromJSON("{ \"some\": \"message\" }");
  257. tds->sendmsg(env, msg);
  258. msg = isc::data::Element::fromJSON("{ \"another\": \"message\" }");
  259. tds->sendmsg(env, msg);
  260. msg = isc::data::Element::fromJSON("{ \"a third\": \"message\" }");
  261. tds->sendmsg(env, msg);
  262. // No followup message, should time out.
  263. ASSERT_THROW(my_io_service.run(), SessionTimeout);
  264. }
  265. TEST_F(SessionTest, get_socket_descr) {
  266. tds->setSendLname();
  267. sess.establish(BIND10_TEST_SOCKET_FILE);
  268. int socket = 0;
  269. // session is established, so getSocketDesc() should work
  270. EXPECT_NO_THROW(socket = sess.getSocketDesc());
  271. // expect actual socket handle to be returned, not 0
  272. EXPECT_LT(0, socket);
  273. }