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 NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
// "Naked" DHCPv4 server, exposes internal fields
public:
ASSERT_NO_THROW(getIOService()->poll());
}
+// This test verifies that the server can receive and process a large command.
TEST_F(CtrlChannelDhcpv4SrvTest, longCommand) {
createUnixChannelServer();
- boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
- ASSERT_TRUE(client);
+ std::string response;
+ std::thread th([this, &response]() {
- ASSERT_TRUE(client->connectToServer(socket_path_));
- getIOService()->run_one();
- getIOService()->poll();
+ // 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());
- size_t bytes_transferred = 0;
- const size_t payload_size = 1024 * 1000;
- bool first_payload = true;
- while (bytes_transferred < payload_size) {
- if (bytes_transferred == 0) {
- std::string preamble = "{ \"command\": \"foo\", \"arguments\": [ ";
- ASSERT_TRUE(client->sendCommand(preamble));
- bytes_transferred += preamble.size();
+ // Create client which we will use to send command to the server.
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+ ASSERT_TRUE(client);
- } else {
- std::ostringstream payload;
- if (!first_payload) {
- payload << ", ";
- }
- first_payload = false;
- payload << "\"blablablablablablablablablablablablablablablabla\"";
+ // Connect to the server. This will trigger acceptor handler on the
+ // server side and create a new connection.
+ ASSERT_TRUE(client->connectToServer(socket_path_));
- if (bytes_transferred + payload.tellp() > payload_size) {
- payload << "] }";
+ // 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();
}
- ASSERT_TRUE(client->sendCommand(payload.str()));
- bytes_transferred += payload.tellp();
}
- getIOService()->run_one();
- getIOService()->poll();
- }
+ // 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));
- std::string response;
- ASSERT_TRUE(client->getResponse(response));
+ // 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);
-
- client->disconnectFromServer();
}
+// This test verifies that the server can send long response to the client.
TEST_F(CtrlChannelDhcpv4SrvTest, 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(&CtrlChannelDhcpv4SrvTest::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]() {
- size_t long_response_size = reference_response.size();
+ // 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;
- client->getResponse(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();
-
- getIOService()->stop();
});
+ // 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());
}