session_unittests.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  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 <list>
  27. #include <string>
  28. #include <iostream>
  29. using namespace isc::cc;
  30. using std::pair;
  31. using std::list;
  32. using std::string;
  33. using isc::data::ConstElementPtr;
  34. using isc::data::Element;
  35. namespace {
  36. TEST(AsioSession, establish) {
  37. asio::io_service io_service_;
  38. Session sess(io_service_);
  39. // can't return socket desciptor before session is established
  40. EXPECT_THROW(sess.getSocketDesc(), isc::InvalidOperation);
  41. EXPECT_THROW(
  42. sess.establish("/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. "/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/"
  51. "/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa/"
  52. ), isc::cc::SessionError
  53. );
  54. }
  55. // This class sets up a domain socket for the session to connect to
  56. // it will impersonate the msgq a tiny bit (if setSendLname() has
  57. // been called, it will send an 'answer' to the lname query that is
  58. // sent in the initialization of Session objects)
  59. class TestDomainSocket {
  60. public:
  61. TestDomainSocket(asio::io_service& io_service, const char* file) :
  62. io_service_(io_service),
  63. ep_(file),
  64. acceptor_(io_service_, ep_),
  65. socket_(io_service_)
  66. {
  67. acceptor_.async_accept(socket_,
  68. boost::bind(&TestDomainSocket::acceptHandler,
  69. this, _1));
  70. }
  71. ~TestDomainSocket() {
  72. socket_.close();
  73. unlink(BIND10_TEST_SOCKET_FILE);
  74. }
  75. void acceptHandler(const asio::error_code&) const {
  76. }
  77. void sendmsg(isc::data::ElementPtr& env, isc::data::ElementPtr& msg) {
  78. const std::string header_wire = env->toWire();
  79. const std::string body_wire = msg->toWire();
  80. const unsigned int length = 2 + header_wire.length() +
  81. body_wire.length();
  82. const unsigned int length_net = htonl(length);
  83. const unsigned short header_length = header_wire.length();
  84. const unsigned short header_length_net = htons(header_length);
  85. socket_.send(asio::buffer(&length_net, sizeof(length_net)));
  86. socket_.send(asio::buffer(&header_length_net,
  87. sizeof(header_length_net)));
  88. socket_.send(asio::buffer(header_wire.data(), header_length));
  89. socket_.send(asio::buffer(body_wire.data(), body_wire.length()));
  90. }
  91. void sendLname() {
  92. isc::data::ElementPtr lname_answer1 =
  93. isc::data::Element::fromJSON("{ \"type\": \"lname\" }");
  94. isc::data::ElementPtr lname_answer2 =
  95. isc::data::Element::fromJSON("{ \"lname\": \"foobar\" }");
  96. sendmsg(lname_answer1, lname_answer2);
  97. }
  98. void setSendLname() {
  99. // ignore whatever data we get, send back an lname
  100. asio::async_read(socket_, asio::buffer(data_buf, 0),
  101. boost::bind(&TestDomainSocket::sendLname, this));
  102. }
  103. private:
  104. asio::io_service& io_service_;
  105. asio::local::stream_protocol::endpoint ep_;
  106. asio::local::stream_protocol::acceptor acceptor_;
  107. asio::local::stream_protocol::socket socket_;
  108. char data_buf[1024];
  109. };
  110. /// \brief Pair holding header and data of a message sent over the connection.
  111. typedef pair<ConstElementPtr, ConstElementPtr> SentMessage;
  112. // We specialize the tested class a little. We replace some low-level
  113. // methods so we can examine the rest without relying on real network IO
  114. class TestSession : public Session {
  115. public:
  116. TestSession(asio::io_service& ioservice) :
  117. Session(ioservice)
  118. {}
  119. // Get first message previously sent by sendmsg and remove it from the
  120. // buffer. Expects there's at leas one message in the buffer.
  121. SentMessage getSentMessage() {
  122. assert(!sent_messages_.empty());
  123. const SentMessage result(sent_messages_.front());
  124. sent_messages_.pop_front();
  125. return (result);
  126. }
  127. private:
  128. // Override the sendmsg. They are not sent over the real connection, but
  129. // stored locally and can be extracted by getSentMessage()
  130. virtual void sendmsg(ConstElementPtr header) {
  131. sendmsg(header, ConstElementPtr(new isc::data::NullElement));
  132. }
  133. virtual void sendmsg(ConstElementPtr header, ConstElementPtr payload) {
  134. sent_messages_.push_back(SentMessage(header, payload));
  135. }
  136. // The sendmsg stores data here.
  137. list<SentMessage> sent_messages_;
  138. };
  139. class SessionTest : public ::testing::Test {
  140. protected:
  141. SessionTest() : sess(my_io_service), work(my_io_service) {
  142. // The TestDomainSocket is held as a 'new'-ed pointer,
  143. // so we can call unlink() first.
  144. unlink(BIND10_TEST_SOCKET_FILE);
  145. tds = new TestDomainSocket(my_io_service, BIND10_TEST_SOCKET_FILE);
  146. }
  147. ~SessionTest() {
  148. delete tds;
  149. }
  150. // Check the session sent a message with the given header. The
  151. // message is hardcoded.
  152. void checkSentMessage(const string& expected_hdr, const char* description)
  153. {
  154. SCOPED_TRACE(description);
  155. const SentMessage& msg(sess.getSentMessage());
  156. elementsEqual(expected_hdr, msg.first);
  157. elementsEqual("{\"test\": 42}", msg.second);
  158. }
  159. private:
  160. // Check two elements are equal
  161. void elementsEqual(const string& expected,
  162. const ConstElementPtr& actual) const
  163. {
  164. EXPECT_TRUE(Element::fromJSON(expected)->equals(*actual)) <<
  165. "Elements differ, expected: " << expected <<
  166. ", got: " << actual->toWire();
  167. }
  168. public:
  169. // used in the handler test
  170. // This handler first reads (and ignores) whatever message caused
  171. // it to be invoked. Then it calls group_recv for a second message.
  172. // If this message is { "command": "stop" } it'll tell the
  173. // io_service it is done. Otherwise it'll re-register this handler
  174. void someHandler() {
  175. isc::data::ConstElementPtr env, msg;
  176. sess.group_recvmsg(env, msg, false, -1);
  177. sess.group_recvmsg(env, msg, false, -1);
  178. if (msg && msg->contains("command") &&
  179. msg->get("command")->stringValue() == "stop") {
  180. my_io_service.stop();
  181. } else {
  182. sess.startRead(boost::bind(&SessionTest::someHandler, this));
  183. }
  184. }
  185. protected:
  186. asio::io_service my_io_service;
  187. TestDomainSocket* tds;
  188. TestSession sess;
  189. // Keep run() from stopping right away by informing it it has work to do
  190. asio::io_service::work work;
  191. };
  192. TEST_F(SessionTest, timeout_on_connect) {
  193. // set to a short timeout so the test doesn't take too long
  194. EXPECT_EQ(4000, sess.getTimeout());
  195. sess.setTimeout(100);
  196. EXPECT_EQ(100, sess.getTimeout());
  197. // no answer, should timeout
  198. EXPECT_THROW(sess.establish(BIND10_TEST_SOCKET_FILE), SessionTimeout);
  199. }
  200. TEST_F(SessionTest, connect_ok) {
  201. tds->setSendLname();
  202. sess.establish(BIND10_TEST_SOCKET_FILE);
  203. }
  204. TEST_F(SessionTest, connect_ok_no_timeout) {
  205. tds->setSendLname();
  206. sess.setTimeout(0);
  207. sess.establish(BIND10_TEST_SOCKET_FILE);
  208. }
  209. TEST_F(SessionTest, connect_ok_connection_reset) {
  210. tds->setSendLname();
  211. sess.establish(BIND10_TEST_SOCKET_FILE);
  212. // Close the session again, so the next recv() should throw
  213. sess.disconnect();
  214. isc::data::ConstElementPtr env, msg;
  215. EXPECT_THROW(sess.group_recvmsg(env, msg, false, -1), SessionError);
  216. }
  217. TEST_F(SessionTest, run_with_handler) {
  218. tds->setSendLname();
  219. sess.establish(BIND10_TEST_SOCKET_FILE);
  220. sess.startRead(boost::bind(&SessionTest::someHandler, this));
  221. isc::data::ElementPtr env = isc::data::Element::fromJSON("{ \"to\": \"me\" }");
  222. isc::data::ElementPtr msg = isc::data::Element::fromJSON("{ \"some\": \"message\" }");
  223. tds->sendmsg(env, msg);
  224. msg = isc::data::Element::fromJSON("{ \"another\": \"message\" }");
  225. tds->sendmsg(env, msg);
  226. msg = isc::data::Element::fromJSON("{ \"a third\": \"message\" }");
  227. tds->sendmsg(env, msg);
  228. msg = isc::data::Element::fromJSON("{ \"command\": \"stop\" }");
  229. tds->sendmsg(env, msg);
  230. size_t count = my_io_service.run();
  231. ASSERT_EQ(2, count);
  232. }
  233. TEST_F(SessionTest, run_with_handler_timeout) {
  234. tds->setSendLname();
  235. sess.establish(BIND10_TEST_SOCKET_FILE);
  236. sess.startRead(boost::bind(&SessionTest::someHandler, this));
  237. sess.setTimeout(100);
  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. // No followup message, should time out.
  246. ASSERT_THROW(my_io_service.run(), SessionTimeout);
  247. }
  248. TEST_F(SessionTest, get_socket_descr) {
  249. tds->setSendLname();
  250. sess.establish(BIND10_TEST_SOCKET_FILE);
  251. int socket = 0;
  252. // session is established, so getSocketDesc() should work
  253. EXPECT_NO_THROW(socket = sess.getSocketDesc());
  254. // expect actual socket handle to be returned, not 0
  255. EXPECT_LT(0, socket);
  256. }
  257. // Test the group_sendmsg sends the correct data.
  258. TEST_F(SessionTest, group_sendmsg) {
  259. // Connect (to set the lname, so we can see it sets the from)
  260. tds->setSendLname();
  261. sess.establish(BIND10_TEST_SOCKET_FILE);
  262. // Eat the "get_lname" message, so it doesn't confuse the
  263. // test below.
  264. sess.getSentMessage();
  265. const ConstElementPtr msg(Element::fromJSON("{\"test\": 42}"));
  266. sess.group_sendmsg(msg, "group");
  267. checkSentMessage("{"
  268. " \"from\": \"foobar\","
  269. " \"group\": \"group\","
  270. " \"instance\": \"*\","
  271. " \"seq\": 0,"
  272. " \"to\": \"*\","
  273. " \"type\": \"send\","
  274. " \"want_answer\": False"
  275. "}", "No instance");
  276. sess.group_sendmsg(msg, "group", "instance", "recipient");
  277. checkSentMessage("{"
  278. " \"from\": \"foobar\","
  279. " \"group\": \"group\","
  280. " \"instance\": \"instance\","
  281. " \"seq\": 1,"
  282. " \"to\": \"recipient\","
  283. " \"type\": \"send\","
  284. " \"want_answer\": False"
  285. "}", "With instance");
  286. sess.group_sendmsg(msg, "group", "*", "*", true);
  287. checkSentMessage("{"
  288. " \"from\": \"foobar\","
  289. " \"group\": \"group\","
  290. " \"instance\": \"*\","
  291. " \"seq\": 2,"
  292. " \"to\": \"*\","
  293. " \"type\": \"send\","
  294. " \"want_answer\": True"
  295. "}", "Want answer");
  296. sess.group_sendmsg(msg, "group", "*", "*", false);
  297. checkSentMessage("{"
  298. " \"from\": \"foobar\","
  299. " \"group\": \"group\","
  300. " \"instance\": \"*\","
  301. " \"seq\": 3,"
  302. " \"to\": \"*\","
  303. " \"type\": \"send\","
  304. " \"want_answer\": False"
  305. "}", "Doesn't want answer");
  306. }
  307. }