diff options
author | Marcin Siodelski <marcin@isc.org> | 2017-07-03 17:47:12 +0200 |
---|---|---|
committer | Marcin Siodelski <marcin@isc.org> | 2017-07-03 17:47:12 +0200 |
commit | 82a6390bb670e94195a757f617c2c4ce0cc79b50 (patch) | |
tree | 2386323f65aedeb72b98be16ee0bec9acd91b21a /src/bin/dhcp6 | |
parent | [5318] Implemented timeouts for CommandMgr. (diff) | |
download | kea-82a6390bb670e94195a757f617c2c4ce0cc79b50.tar.xz kea-82a6390bb670e94195a757f617c2c4ce0cc79b50.zip |
[5318] Added unit tests for long connections in DHCPv6.
Diffstat (limited to 'src/bin/dhcp6')
-rw-r--r-- | src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc index 4d809a920d..1b503dacca 100644 --- a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc @@ -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 |