|
@@ -31,6 +31,8 @@
|
|
|
#include <sys/ioctl.h>
|
|
|
#include <cstdlib>
|
|
|
|
|
|
+#include <thread>
|
|
|
+
|
|
|
using namespace std;
|
|
|
using namespace isc::asiolink;
|
|
|
using namespace isc::config;
|
|
@@ -43,7 +45,31 @@ using namespace isc::test;
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
+/// @brief Simple RAII class which stops IO service upon destruction
|
|
|
+/// of the object.
|
|
|
+class IOServiceWork {
|
|
|
+public:
|
|
|
+
|
|
|
+ /// @brief Constructor.
|
|
|
+ ///
|
|
|
+ /// @param io_service Pointer to the IO service to be stopped.
|
|
|
+ IOServiceWork(const IOServicePtr& io_service)
|
|
|
+ : io_service_(io_service) {
|
|
|
+ }
|
|
|
+
|
|
|
+ /// @brief Destructor.
|
|
|
+ ///
|
|
|
+ /// Stops IO service.
|
|
|
+ ~IOServiceWork() {
|
|
|
+ io_service_->stop();
|
|
|
+ }
|
|
|
|
|
|
+private:
|
|
|
+
|
|
|
+ /// @brief Pointer to the IO service to be stopped upon destruction.
|
|
|
+ IOServicePtr io_service_;
|
|
|
+
|
|
|
+};
|
|
|
|
|
|
class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
|
|
|
// "Naked" DHCPv6 server, exposes internal fields
|
|
@@ -306,6 +332,18 @@ public:
|
|
|
ADD_FAILURE() << "Invalid expected status: " << exp_status;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /// @brief Command handler which generates long response
|
|
|
+ static ConstElementPtr longResponseHandler(const std::string&,
|
|
|
+ const ConstElementPtr&) {
|
|
|
+ ElementPtr arguments = Element::createList();
|
|
|
+ std::string arg = "responseresponseresponseresponseresponseresponse"
|
|
|
+ "response";
|
|
|
+ for (unsigned i = 0; i < 8000; ++i) {
|
|
|
+ arguments->add(Element::create(arg));
|
|
|
+ }
|
|
|
+ return (createAnswer(0, arguments));
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
|
|
@@ -1131,5 +1169,205 @@ TEST_F(CtrlChannelDhcpv6SrvTest, concurrentConnections) {
|
|
|
ASSERT_NO_THROW(getIOService()->poll());
|
|
|
}
|
|
|
|
|
|
+// This test verifies that the server can receive and process a large command.
|
|
|
+TEST_F(CtrlChannelDhcpv6SrvTest, longCommand) {
|
|
|
+ createUnixChannelServer();
|
|
|
+
|
|
|
+ std::string response;
|
|
|
+ std::thread th([this, &response]() {
|
|
|
+
|
|
|
+ // IO service will be stopped automatically when this object goes
|
|
|
+ // out of scope and is destroyed. This is useful because we use
|
|
|
+ // asserts which may break the thread in various exit points.
|
|
|
+ IOServiceWork work(getIOService());
|
|
|
+
|
|
|
+ // Create client which we will use to send command to the server.
|
|
|
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
|
|
|
+ ASSERT_TRUE(client);
|
|
|
+
|
|
|
+ // Connect to the server. This will trigger acceptor handler on the
|
|
|
+ // server side and create a new connection.
|
|
|
+ ASSERT_TRUE(client->connectToServer(socket_path_));
|
|
|
+
|
|
|
+ // This counter will hold the number of bytes transferred to the server
|
|
|
+ // so far.
|
|
|
+ size_t bytes_transferred = 0;
|
|
|
+ // This is the desired size of the command sent to the server (1MB). The
|
|
|
+ // actual size sent will be slightly greater than that.
|
|
|
+ const size_t command_size = 1024 * 1000;
|
|
|
+ bool first_payload = true;
|
|
|
+
|
|
|
+ // If we still haven't sent the entire command, continue sending.
|
|
|
+ while (bytes_transferred < command_size) {
|
|
|
+
|
|
|
+ // We're sending command 'foo' with arguments being a list of
|
|
|
+ // strings. If this is the first transmission, send command name
|
|
|
+ // and open the arguments list.
|
|
|
+ if (bytes_transferred == 0) {
|
|
|
+ std::string preamble = "{ \"command\": \"foo\", \"arguments\": [ ";
|
|
|
+ ASSERT_TRUE(client->sendCommand(preamble));
|
|
|
+ // Store the number of bytes sent.
|
|
|
+ bytes_transferred += preamble.size();
|
|
|
+
|
|
|
+ } else {
|
|
|
+ // We have already transmitted command name and arguments. Now
|
|
|
+ // we send the list of 'blabla' strings.
|
|
|
+ std::ostringstream payload;
|
|
|
+ // If this is not the first parameter in on the list it must be
|
|
|
+ // prefixed with a comma.
|
|
|
+ if (!first_payload) {
|
|
|
+ payload << ", ";
|
|
|
+ }
|
|
|
+
|
|
|
+ first_payload = false;
|
|
|
+ payload << "\"blablablablablablablablablablablablablablablabla\"";
|
|
|
+
|
|
|
+ // If we have hit the limit of the command size, close braces to
|
|
|
+ // get appropriate JSON.
|
|
|
+ if (bytes_transferred + payload.tellp() > command_size) {
|
|
|
+ payload << "] }";
|
|
|
+ }
|
|
|
+ // Send the payload.
|
|
|
+ ASSERT_TRUE(client->sendCommand(payload.str()));
|
|
|
+ // Update the number of bytes sent.
|
|
|
+ bytes_transferred += payload.tellp();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set timeout to 5 seconds to allow the time for the server to send
|
|
|
+ // a response.
|
|
|
+ const unsigned int timeout = 5;
|
|
|
+ ASSERT_TRUE(client->getResponse(response, timeout));
|
|
|
+
|
|
|
+ // We're done. Close the connection to the server.
|
|
|
+ client->disconnectFromServer();
|
|
|
+ });
|
|
|
+
|
|
|
+ // Run the server until the command has been processed and response
|
|
|
+ // received.
|
|
|
+ getIOService()->run();
|
|
|
+
|
|
|
+ // Wait for the thread to complete.
|
|
|
+ th.join();
|
|
|
+
|
|
|
+ EXPECT_EQ("{ \"result\": 2, \"text\": \"'foo' command not supported.\" }",
|
|
|
+ response);
|
|
|
+}
|
|
|
+
|
|
|
+// This test verifies that the server can send long response to the client.
|
|
|
+TEST_F(CtrlChannelDhcpv6SrvTest, longResponse) {
|
|
|
+ // We need to generate large response. The simplest way is to create
|
|
|
+ // a command and a handler which will generate some static response
|
|
|
+ // of a desired size.
|
|
|
+ ASSERT_NO_THROW(
|
|
|
+ CommandMgr::instance().registerCommand("foo",
|
|
|
+ boost::bind(&CtrlChannelDhcpv6SrvTest::longResponseHandler, _1, _2));
|
|
|
+ );
|
|
|
+
|
|
|
+ createUnixChannelServer();
|
|
|
+
|
|
|
+ // The UnixControlClient doesn't have any means to check that the entire
|
|
|
+ // response has been received. What we want to do is to generate a
|
|
|
+ // reference response using our command handler and then compare
|
|
|
+ // what we have received over the unix domain socket with this reference
|
|
|
+ // response to figure out when to stop receiving.
|
|
|
+ std::string reference_response = longResponseHandler("foo", ConstElementPtr())->str();
|
|
|
+
|
|
|
+ // In this stream we're going to collect out partial responses.
|
|
|
+ std::ostringstream response;
|
|
|
+
|
|
|
+ // The client is synchronous so it is useful to run it in a thread.
|
|
|
+ std::thread th([this, &response, reference_response]() {
|
|
|
+
|
|
|
+ // IO service will be stopped automatically when this object goes
|
|
|
+ // out of scope and is destroyed. This is useful because we use
|
|
|
+ // asserts which may break the thread in various exit points.
|
|
|
+ IOServiceWork work(getIOService());
|
|
|
+
|
|
|
+ // Remember the response size so as we know when we should stop
|
|
|
+ // receiving.
|
|
|
+ const size_t long_response_size = reference_response.size();
|
|
|
+
|
|
|
+ // Create the client and connect it to the server.
|
|
|
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
|
|
|
+ ASSERT_TRUE(client);
|
|
|
+ ASSERT_TRUE(client->connectToServer(socket_path_));
|
|
|
+
|
|
|
+ // Send the stub command.
|
|
|
+ std::string command = "{ \"command\": \"foo\", \"arguments\": { } }";
|
|
|
+ ASSERT_TRUE(client->sendCommand(command));
|
|
|
+
|
|
|
+ // Keep receiving response data until we have received the full answer.
|
|
|
+ while (response.tellp() < long_response_size) {
|
|
|
+ std::string partial;
|
|
|
+ const unsigned int timeout = 5;
|
|
|
+ ASSERT_TRUE(client->getResponse(partial, 5));
|
|
|
+ response << partial;
|
|
|
+ }
|
|
|
+
|
|
|
+ // We have received the entire response, so close the connection and
|
|
|
+ // stop the IO service.
|
|
|
+ client->disconnectFromServer();
|
|
|
+ });
|
|
|
+
|
|
|
+ // Run the server until the entire response has been received.
|
|
|
+ getIOService()->run();
|
|
|
+
|
|
|
+ // Wait for the thread to complete.
|
|
|
+ th.join();
|
|
|
+
|
|
|
+ // Make sure we have received correct response.
|
|
|
+ EXPECT_EQ(reference_response, response.str());
|
|
|
+}
|
|
|
+
|
|
|
+// This test verifies that the server signals timeout if the transmission
|
|
|
+// takes too long.
|
|
|
+TEST_F(CtrlChannelDhcpv6SrvTest, connectionTimeout) {
|
|
|
+ createUnixChannelServer();
|
|
|
+
|
|
|
+ // Server's response will be assigned to this variable.
|
|
|
+ std::string response;
|
|
|
+
|
|
|
+ // It is useful to create a thread and run the server and the client
|
|
|
+ // at the same time and independently.
|
|
|
+ std::thread th([this, &response]() {
|
|
|
+
|
|
|
+ // IO service will be stopped automatically when this object goes
|
|
|
+ // out of scope and is destroyed. This is useful because we use
|
|
|
+ // asserts which may break the thread in various exit points.
|
|
|
+ IOServiceWork work(getIOService());
|
|
|
+
|
|
|
+ // Create the client and connect it to the server.
|
|
|
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
|
|
|
+ ASSERT_TRUE(client);
|
|
|
+ ASSERT_TRUE(client->connectToServer(socket_path_));
|
|
|
+
|
|
|
+ // Send partial command. The server will be waiting for the remaining
|
|
|
+ // part to be sent and will eventually signal a timeout.
|
|
|
+ std::string command = "{ \"command\": \"foo\" ";
|
|
|
+ ASSERT_TRUE(client->sendCommand(command));
|
|
|
+
|
|
|
+ // Let's wait up to 10s for the server's response. The response
|
|
|
+ // should arrive sooner assuming that the timeout mechanism for
|
|
|
+ // the server is working properly.
|
|
|
+ const unsigned int timeout = 10;
|
|
|
+ ASSERT_TRUE(client->getResponse(response, 10));
|
|
|
+
|
|
|
+ // Explicitly close the client's connection.
|
|
|
+ client->disconnectFromServer();
|
|
|
+ });
|
|
|
+
|
|
|
+ // Run the server until stopped.
|
|
|
+ getIOService()->run();
|
|
|
+
|
|
|
+ // Wait for the thread to return.
|
|
|
+ th.join();
|
|
|
+
|
|
|
+ // Check that the server has signalled a timeout.
|
|
|
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"Connection over control channel"
|
|
|
+ " timed out\" }", response);
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
} // End of anonymous namespace
|