1 // Copyright (C) 2017-2019 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
7 #include <config.h>
8 #include <testutils/sandbox.h>
9 #include <asiolink/asio_wrapper.h>
10 #include <asiolink/io_service.h>
11 #include <asiolink/testutils/test_server_unix_socket.h>
12 #include <cc/json_feed.h>
13 #include <config/client_connection.h>
14 #include <gtest/gtest.h>
15 #include <cstdlib>
16 #include <sstream>
17 #include <string>
18
19 using namespace isc::asiolink;
20 using namespace isc::config;
21
22 namespace {
23
24 /// @brief Test timeout in ms.
25 const long TEST_TIMEOUT = 10000;
26
27 /// Test fixture class for @ref ClientConnection.
28 class ClientConnectionTest : public ::testing::Test {
29 public:
30 isc::test::Sandbox sandbox;
31
32 /// @brief Constructor.
33 ///
34 /// Removes unix socket descriptor before the test.
ClientConnectionTest()35 ClientConnectionTest() :
36 io_service_(),
37 test_socket_(new test::TestServerUnixSocket(io_service_,
38 unixSocketFilePath())) {
39 removeUnixSocketFile();
40 }
41
42 /// @brief Destructor.
43 ///
44 /// Removes unix socket descriptor after the test.
~ClientConnectionTest()45 virtual ~ClientConnectionTest() {
46 removeUnixSocketFile();
47 }
48
49 /// @brief Returns socket file path.
50 ///
51 /// If the KEA_SOCKET_TEST_DIR environment variable is specified, the
52 /// socket file is created in the location pointed to by this variable.
53 /// Otherwise, it is created in the build directory.
54 ///
55 /// The KEA_SOCKET_TEST_DIR is typically used to overcome the problem of
56 /// a system limit on the unix socket file path (usually 102 or 103 characters).
57 /// When Kea build is located in the nested directories with absolute path
58 /// exceeding this limit, the test system should be configured to set
59 /// the KEA_SOCKET_TEST_DIR environmental variable to point to an alternative
60 /// location, e.g. /tmp, with an absolute path length being within the
61 /// allowed range.
unixSocketFilePath()62 std::string unixSocketFilePath() {
63 std::string socket_path;
64 const char* env = getenv("KEA_SOCKET_TEST_DIR");
65 if (env) {
66 socket_path = std::string(env) + "/test-socket";
67 } else {
68 socket_path = sandbox.join("test-socket");
69 }
70 return (socket_path);
71 }
72
73 /// @brief Removes unix socket descriptor.
removeUnixSocketFile()74 void removeUnixSocketFile() {
75 static_cast<void>(remove(unixSocketFilePath().c_str()));
76 }
77
78 /// @brief IO service used by the tests.
79 IOService io_service_;
80
81 /// @brief Server side unix socket used in these tests.
82 test::TestServerUnixSocketPtr test_socket_;
83 };
84
85 // Tests successful transaction: connect, send command and receive a
86 // response.
TEST_F(ClientConnectionTest,success)87 TEST_F(ClientConnectionTest, success) {
88 // Start timer protecting against test timeouts.
89 test_socket_->startTimer(TEST_TIMEOUT);
90
91 // Start the server.
92 test_socket_->bindServerSocket();
93 test_socket_->generateCustomResponse(2048);
94
95 // Create some valid command.
96 std::string command = "{ \"command\": \"list-commands\" }";
97
98 ClientConnection conn(io_service_);
99
100 // This boolean value will indicate when the callback function is invoked
101 // at the end of the transaction (whether it is successful or unsuccessful).
102 bool handler_invoked = false;
103 conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
104 ClientConnection::ControlCommand(command),
105 [&handler_invoked](const boost::system::error_code& ec,
106 const ConstJSONFeedPtr& feed) {
107 // Indicate that the handler has been called to break from the
108 // while loop below.
109 handler_invoked = true;
110 // The ec should contain no error.
111 ASSERT_FALSE(ec);
112 // The JSONFeed should be present and it should contain a valid
113 // response.
114 ASSERT_TRUE(feed);
115 EXPECT_TRUE(feed->feedOk()) << feed->getErrorMessage();
116 });
117 // Run the connection.
118 while (!handler_invoked && !test_socket_->isStopped()) {
119 io_service_.run_one();
120 }
121 }
122
123 // This test checks that a timeout is signalled when the communication
124 // takes too long.
TEST_F(ClientConnectionTest,timeout)125 TEST_F(ClientConnectionTest, timeout) {
126 // The server will return only partial JSON response (lacking closing
127 // brace). The client will wait for closing brace and eventually the
128 // connection should time out.
129 test_socket_.reset(new test::TestServerUnixSocket(io_service_,
130 unixSocketFilePath(),
131 "{ \"command\": \"foo\""));
132 test_socket_->startTimer(TEST_TIMEOUT);
133
134 // Start the server.
135 test_socket_->bindServerSocket();
136
137 // Command to be sent to the server.
138 std::string command = "{ \"command\": \"list-commands\" }";
139
140 ClientConnection conn(io_service_);
141
142 // This boolean value will be set to true when the callback is invoked.
143 bool handler_invoked = false;
144 conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
145 ClientConnection::ControlCommand(command),
146 [&handler_invoked](const boost::system::error_code& ec,
147 const ConstJSONFeedPtr& /*feed*/) {
148 // Indicate that the callback has been invoked to break the loop
149 // below.
150 handler_invoked = true;
151 ASSERT_TRUE(ec);
152 EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
153 }, ClientConnection::Timeout(1000));
154
155 while (!handler_invoked && !test_socket_->isStopped()) {
156 io_service_.run_one();
157 }
158 }
159
160 // This test checks that an error is returned when the client is unable
161 // to connect to the server.
TEST_F(ClientConnectionTest,connectionError)162 TEST_F(ClientConnectionTest, connectionError) {
163 // Create the new connection but do not bind the server socket.
164 // The connection should be refused and an error returned.
165 ClientConnection conn(io_service_);
166
167 std::string command = "{ \"command\": \"list-commands\" }";
168
169 bool handler_invoked = false;
170 conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
171 ClientConnection::ControlCommand(command),
172 [&handler_invoked](const boost::system::error_code& ec,
173 const ConstJSONFeedPtr& /*feed*/) {
174 handler_invoked = true;
175 ASSERT_TRUE(ec);
176 });
177
178 while (!handler_invoked && !test_socket_->isStopped()) {
179 io_service_.run_one();
180 }
181 }
182
183 } // end of anonymous namespace
184