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