client_connection_unittests.cc 6.49 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>
#include <asiolink/asio_wrapper.h>
#include <asiolink/io_service.h>
#include <asiolink/testutils/test_server_unix_socket.h>
#include <cc/json_feed.h>
#include <config/client_connection.h>
#include <gtest/gtest.h>
#include <cstdlib>
#include <sstream>
#include <string>

using namespace isc::asiolink;
using namespace isc::config;

namespace {

/// @brief Test unix socket file name.
const std::string TEST_SOCKET = "test-socket";

/// @brief Test timeout in ms.
const long TEST_TIMEOUT = 10000;

29
/// Test fixture class for @ref ClientConnection.
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class ClientConnectionTest : public ::testing::Test {
public:

    /// @brief Constructor.
    ///
    /// Removes unix socket descriptor before the test.
    ClientConnectionTest() :
        io_service_(),
        test_socket_(new test::TestServerUnixSocket(io_service_,
                                                    unixSocketFilePath())) {
        removeUnixSocketFile();
    }

    /// @brief Destructor.
    ///
    /// Removes unix socket descriptor after the test.
    virtual ~ClientConnectionTest() {
        removeUnixSocketFile();
    }

    /// @brief Returns socket file path.
    ///
    /// If the KEA_SOCKET_TEST_DIR environment variable is specified, the
    /// socket file is created in the location pointed to by this variable.
    /// Otherwise, it is created in the build directory.
55
56
57
58
59
60
61
62
    ///
    /// The KEA_SOCKET_TEST_DIR is typically used to overcome the problem of
    /// a system limit on the unix socket file path (usually 102 or 103 characters).
    /// When Kea build is located in the nested directories with absolute path
    /// exceeding this limit, the test system should be configured to set
    /// the KEA_SOCKET_TEST_DIR environmental variable to point to an alternative
    /// location, e.g. /tmp, with an absolute path length being within the
    /// allowed range.
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
    static std::string unixSocketFilePath() {
        std::ostringstream s;
        const char* env = getenv("KEA_SOCKET_TEST_DIR");
        if (env) {
            s << std::string(env);
        } else {
            s << TEST_DATA_BUILDDIR;
        }

        s << "/" << TEST_SOCKET;
        return (s.str());
    }

    /// @brief Removes unix socket descriptor.
    void removeUnixSocketFile() {
        static_cast<void>(remove(unixSocketFilePath().c_str()));
    }

    /// @brief IO service used by the tests.
    IOService io_service_;

    /// @brief Server side unix socket used in these tests.
    test::TestServerUnixSocketPtr test_socket_;
};

// Tests successful transaction: connect, send command and receive a
// response.
TEST_F(ClientConnectionTest, success) {
    // Start timer protecting against test timeouts.
    test_socket_->startTimer(TEST_TIMEOUT);

    // Start the server.
    test_socket_->bindServerSocket();
    test_socket_->generateCustomResponse(2048);

    // Create some valid command.
    std::string command = "{ \"command\": \"list-commands\" }";

    ClientConnection conn(io_service_);

    // This boolean value will indicate when the callback function is invoked
    // at the end of the transaction (whether it is successful or unsuccessful).
    bool handler_invoked = false;
    conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
               ClientConnection::ControlCommand(command),
        [this, &handler_invoked](const boost::system::error_code& ec,
                                 const ConstJSONFeedPtr& feed) {
        // Indicate that the handler has been called to break from the
        // while loop below.
        handler_invoked = true;
        // The ec should contain no error.
        ASSERT_FALSE(ec);
        // The JSONFeed should be present and it should contain a valid
        // response.
        ASSERT_TRUE(feed);
        EXPECT_TRUE(feed->feedOk()) << feed->getErrorMessage();
    });
    // Run the connection.
    while (!handler_invoked && !test_socket_->isStopped()) {
        io_service_.run_one();
    }
}

// This test checks that a timeout is signalled when the communication
// takes too long.
TEST_F(ClientConnectionTest, timeout) {
    // The server will return only partial JSON response (lacking closing
    // brace). The client will wait for closing brace and eventually the
    // connection should time out.
    test_socket_.reset(new test::TestServerUnixSocket(io_service_,
                                                      unixSocketFilePath(),
                                                      "{ \"command\": \"foo\""));
    test_socket_->startTimer(TEST_TIMEOUT);

    // Start the server.
    test_socket_->bindServerSocket();

    // Command to be sent to the server.
    std::string command = "{ \"command\": \"list-commands\" }";

    ClientConnection conn(io_service_);

    // This boolean value will be set to true when the callback is invoked.
    bool handler_invoked = false;
    conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
              ClientConnection::ControlCommand(command),
    [this, &handler_invoked](const boost::system::error_code& ec,
150
                             const ConstJSONFeedPtr& /*feed*/) {
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
        // Indicate that the callback has been invoked to break the loop
        // below.
        handler_invoked = true;
        ASSERT_TRUE(ec);
        EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
    }, ClientConnection::Timeout(1000));

    while (!handler_invoked && !test_socket_->isStopped()) {
        io_service_.run_one();
    }
}

// This test checks that an error is returned when the client is unable
// to connect to the server.
TEST_F(ClientConnectionTest, connectionError) {
    // Create the new connection but do not bind the server socket.
    // The connection should be refused and an error returned.
    ClientConnection conn(io_service_);

    std::string command = "{ \"command\": \"list-commands\" }";

    bool handler_invoked = false;
    conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
               ClientConnection::ControlCommand(command),
    [this, &handler_invoked](const boost::system::error_code& ec,
176
                             const ConstJSONFeedPtr& /*feed*/) {
177
178
179
180
181
182
183
184
185
186
        handler_invoked = true;
        ASSERT_TRUE(ec);
    });

    while (!handler_invoked && !test_socket_->isStopped()) {
        io_service_.run_one();
    }
}

} // end of anonymous namespace