diff options
Diffstat (limited to 'src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc')
-rw-r--r-- | src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc | 1428 |
1 files changed, 1428 insertions, 0 deletions
diff --git a/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc new file mode 100644 index 0000000..b63ed8c --- /dev/null +++ b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc @@ -0,0 +1,1428 @@ +// Copyright (C) 2013-2021 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 <gtest/gtest.h> + +#include <asiolink/interval_timer.h> +#include <dhcp_ddns/ncr_io.h> +#include <dhcp_ddns/ncr_udp.h> +#include <util/multi_threading_mgr.h> +#include <util/time_utilities.h> +#include <test_utils.h> + +#include <boost/asio/ip/udp.hpp> + +#include <functional> +#include <algorithm> + +#include <sys/select.h> + +using namespace std; +using namespace isc; +using namespace isc::util; +using namespace isc::dhcp_ddns; + +namespace { + +/// @brief Defines a list of valid JSON NameChangeRequest test messages. +const char *valid_msgs[] = +{ + // Valid Add. + "{" + " \"change-type\" : 0 , " + " \"forward-change\" : true , " + " \"reverse-change\" : false , " + " \"fqdn\" : \"walah.walah.com\" , " + " \"ip-address\" : \"192.168.2.1\" , " + " \"dhcid\" : \"010203040A7F8E3D\" , " + " \"lease-expires-on\" : \"20130121132405\" , " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" + "}", + // Valid Remove. + "{" + " \"change-type\" : 1 , " + " \"forward-change\" : true , " + " \"reverse-change\" : false , " + " \"fqdn\" : \"walah.walah.com\" , " + " \"ip-address\" : \"192.168.2.1\" , " + " \"dhcid\" : \"010203040A7F8E3D\" , " + " \"lease-expires-on\" : \"20130121132405\" , " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": false" + "}", + // Valid Add with IPv6 address + "{" + " \"change-type\" : 0 , " + " \"forward-change\" : true , " + " \"reverse-change\" : false , " + " \"fqdn\" : \"walah.walah.com\" , " + " \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , " + " \"dhcid\" : \"010203040A7F8E3D\" , " + " \"lease-expires-on\" : \"20130121132405\" , " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true" + "}" +}; + +const char* TEST_ADDRESS = "127.0.0.1"; +const uint32_t LISTENER_PORT = 5301; +const uint32_t SENDER_PORT = LISTENER_PORT+1; +const long TEST_TIMEOUT = 5 * 1000; + +/// @brief A NOP derivation for constructor test purposes. +class SimpleListenHandler : public NameChangeListener::RequestReceiveHandler { +public: + virtual void operator ()(const NameChangeListener::Result, + NameChangeRequestPtr&) { + } +}; + +/// @brief Tests the NameChangeUDPListener constructors. +/// This test verifies that: +/// 1. Given valid parameters, the listener constructor works +TEST(NameChangeUDPListenerBasicTest, constructionTests) { + // Verify the default constructor works. + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + uint32_t port = LISTENER_PORT; + isc::asiolink::IOService io_service; + SimpleListenHandler ncr_handler; + // Verify that valid constructor works. + EXPECT_NO_THROW(NameChangeUDPListener(ip_address, port, FMT_JSON, + ncr_handler)); +} + +/// @brief Tests NameChangeUDPListener starting and stopping listening . +/// This test verifies that the listener will: +/// 1. Enter listening state +/// 2. If in the listening state, does not allow calls to start listening +/// 3. Exist the listening state +/// 4. Return to the listening state after stopping +TEST(NameChangeUDPListenerBasicTest, basicListenTests) { + // Verify the default constructor works. + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + uint32_t port = LISTENER_PORT; + isc::asiolink::IOService io_service; + SimpleListenHandler ncr_handler; + + NameChangeListenerPtr listener; + ASSERT_NO_THROW(listener.reset( + new NameChangeUDPListener(ip_address, port, FMT_JSON, ncr_handler))); + + // Verify that we can start listening. + EXPECT_NO_THROW(listener->startListening(io_service)); + + // Verify that we are in listening mode. + EXPECT_TRUE(listener->amListening()); + // Verify that a read is in progress. + EXPECT_TRUE(listener->isIoPending()); + + // Verify that attempting to listen when we already are is an error. + EXPECT_THROW(listener->startListening(io_service), NcrListenerError); + + // Verify that we can stop listening. + EXPECT_NO_THROW(listener->stopListening()); + EXPECT_FALSE(listener->amListening()); + + // Verify that IO pending is still true, as IO cancel event has not yet + // occurred. + EXPECT_TRUE(listener->isIoPending()); + + // Verify that IO pending is false, after cancel event occurs. + EXPECT_NO_THROW(io_service.run_one()); + EXPECT_FALSE(listener->isIoPending()); + + // Verify that attempting to stop listening when we are not is ok. + EXPECT_NO_THROW(listener->stopListening()); + + // Verify that we can re-enter listening. + EXPECT_NO_THROW(listener->startListening(io_service)); + EXPECT_TRUE(listener->amListening()); +} + +/// @brief Compares two NameChangeRequests for equality. +bool checkSendVsReceived(NameChangeRequestPtr sent_ncr, + NameChangeRequestPtr received_ncr) { + return ((sent_ncr && received_ncr) && + (*sent_ncr == *received_ncr)); +} + +/// @brief Text fixture for testing NameChangeUDPListener +class NameChangeUDPListenerTest : public virtual ::testing::Test, + NameChangeListener::RequestReceiveHandler { +public: + isc::asiolink::IOService io_service_; + NameChangeListener::Result result_; + NameChangeRequestPtr sent_ncr_; + NameChangeRequestPtr received_ncr_; + NameChangeListenerPtr listener_; + isc::asiolink::IntervalTimer test_timer_; + + /// @brief Constructor + // + // Instantiates the listener member and the test timer. The timer is used + // to ensure a test doesn't go awry and hang forever. + NameChangeUDPListenerTest() + : io_service_(), result_(NameChangeListener::SUCCESS), + test_timer_(io_service_) { + isc::asiolink::IOAddress addr(TEST_ADDRESS); + listener_.reset(new NameChangeUDPListener(addr, LISTENER_PORT, + FMT_JSON, *this, true)); + + // Set the test timeout to break any running tasks if they hang. + test_timer_.setup(std::bind(&NameChangeUDPListenerTest:: + testTimeoutHandler, this), + TEST_TIMEOUT); + } + + virtual ~NameChangeUDPListenerTest(){ + } + + + /// @brief Converts JSON string into an NCR and sends it to the listener. + /// + void sendNcr(const std::string& msg) { + // Build an NCR from json string. This verifies that the + // test string is valid. + ASSERT_NO_THROW(sent_ncr_ = NameChangeRequest::fromJSON(msg)); + + // Now use the NCR to write JSON to an output buffer. + isc::util::OutputBuffer ncr_buffer(1024); + ASSERT_NO_THROW(sent_ncr_->toFormat(FMT_JSON, ncr_buffer)); + + // Create a UDP socket through which our "sender" will send the NCR. + boost::asio::ip::udp::socket + udp_socket(io_service_.get_io_service(), boost::asio::ip::udp::v4()); + + // Create an endpoint pointed at the listener. + boost::asio::ip::udp::endpoint + listener_endpoint(boost::asio::ip::address::from_string(TEST_ADDRESS), + LISTENER_PORT); + + // A response message is now ready to send. Send it! + // Note this uses a synchronous send so it ships immediately. + // If listener isn't in listening mode, it will get missed. + udp_socket.send_to(boost::asio::buffer(ncr_buffer.getData(), + ncr_buffer.getLength()), + listener_endpoint); + } + + /// @brief RequestReceiveHandler operator implementation for receiving NCRs. + /// + /// The fixture acts as the "application" layer. It derives from + /// RequestReceiveHandler and as such implements operator() in order to + /// receive NCRs. + virtual void operator ()(const NameChangeListener::Result result, + NameChangeRequestPtr& ncr) { + // save the result and the NCR we received + result_ = result; + received_ncr_ = ncr; + } + + /// @brief Handler invoked when test timeout is hit + /// + /// This callback stops all running (hanging) tasks on IO service. + void testTimeoutHandler() { + io_service_.stop(); + FAIL() << "Test timeout hit."; + } +}; + +/// @brief Tests NameChangeUDPListener ability to receive NCRs. +/// This test verifies that a listener can enter listening mode and +/// receive NCRs in wire format on its UDP socket; reconstruct the +/// NCRs and delivery them to the "application" layer. +TEST_F(NameChangeUDPListenerTest, basicReceiveTests) { + // Verify we can enter listening mode. + ASSERT_FALSE(listener_->amListening()); + ASSERT_NO_THROW(listener_->startListening(io_service_)); + ASSERT_TRUE(listener_->amListening()); + ASSERT_TRUE(listener_->isIoPending()); + + // Iterate over a series of requests, sending and receiving one + /// at time. + int num_msgs = sizeof(valid_msgs)/sizeof(char*); + for (int i = 0; i < num_msgs; i++) { + // We are not verifying ability to send, so if we can't test is over. + ASSERT_NO_THROW(sendNcr(valid_msgs[i])); + + // Execute no more then one event, which should be receive complete. + EXPECT_NO_THROW(io_service_.run_one()); + + // Verify the "application" status value for a successful complete. + EXPECT_EQ(NameChangeListener::SUCCESS, result_); + + // Verify the received request matches the sent request. + EXPECT_TRUE(checkSendVsReceived(sent_ncr_, received_ncr_)); + } + + // Verify we can gracefully stop listening. + EXPECT_NO_THROW(listener_->stopListening()); + EXPECT_FALSE(listener_->amListening()); + + // Verify that IO pending is false, after cancel event occurs. + EXPECT_NO_THROW(io_service_.run_one()); + EXPECT_FALSE(listener_->isIoPending()); +} + +/// @brief A NOP derivation for constructor test purposes. +class SimpleSendHandler : public NameChangeSender::RequestSendHandler { +public: + SimpleSendHandler() : pass_count_(0), error_count_(0) { + } + + virtual void operator ()(const NameChangeSender::Result result, + NameChangeRequestPtr&) { + if (result == NameChangeSender::SUCCESS) { + ++pass_count_; + } else { + ++error_count_; + } + } + + int pass_count_; + int error_count_; +}; + +/// @brief Text fixture for testing NameChangeUDPListener +class NameChangeUDPSenderBasicTest : public virtual ::testing::Test { +public: + NameChangeUDPSenderBasicTest() { + // Disable multi-threading + MultiThreadingMgr::instance().setMode(false); + } + + ~NameChangeUDPSenderBasicTest() { + // Disable multi-threading + MultiThreadingMgr::instance().setMode(false); + } +}; + +/// @brief Tests the NameChangeUDPSender constructors. +/// This test verifies that: +/// 1. Constructing with a max queue size of 0 is not allowed +/// 2. Given valid parameters, the sender constructor works +/// 3. Default construction provides default max queue size +/// 4. Construction with a custom max queue size works +TEST_F(NameChangeUDPSenderBasicTest, constructionTests) { + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + uint32_t port = SENDER_PORT; + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + + // Verify that constructing with an queue size of zero is not allowed. + EXPECT_THROW(NameChangeUDPSender(ip_address, port, + ip_address, port, FMT_JSON, ncr_handler, 0), NcrSenderError); + + NameChangeSenderPtr sender; + // Verify that valid constructor works. + EXPECT_NO_THROW(sender.reset( + new NameChangeUDPSender(ip_address, port, ip_address, port, + FMT_JSON, ncr_handler))); + + // Verify that send queue default max is correct. + size_t expected = NameChangeSender::MAX_QUEUE_DEFAULT; + EXPECT_EQ(expected, sender->getQueueMaxSize()); + + // Verify that constructor with a valid custom queue size works. + EXPECT_NO_THROW(sender.reset( + new NameChangeUDPSender(ip_address, port, ip_address, port, + FMT_JSON, ncr_handler, 100))); + + EXPECT_EQ(100, sender->getQueueMaxSize()); +} + +/// @brief Tests the NameChangeUDPSender constructors. +/// This test verifies that: +/// 1. Constructing with a max queue size of 0 is not allowed +/// 2. Given valid parameters, the sender constructor works +/// 3. Default construction provides default max queue size +/// 4. Construction with a custom max queue size works +TEST_F(NameChangeUDPSenderBasicTest, constructionTestsMultiThreading) { + // Enable multi-threading + MultiThreadingMgr::instance().setMode(true); + + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + uint32_t port = SENDER_PORT; + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + + // Verify that constructing with an queue size of zero is not allowed. + EXPECT_THROW(NameChangeUDPSender(ip_address, port, + ip_address, port, FMT_JSON, ncr_handler, 0), NcrSenderError); + + NameChangeSenderPtr sender; + // Verify that valid constructor works. + EXPECT_NO_THROW(sender.reset( + new NameChangeUDPSender(ip_address, port, ip_address, port, + FMT_JSON, ncr_handler))); + + // Verify that send queue default max is correct. + size_t expected = NameChangeSender::MAX_QUEUE_DEFAULT; + EXPECT_EQ(expected, sender->getQueueMaxSize()); + + // Verify that constructor with a valid custom queue size works. + EXPECT_NO_THROW(sender.reset( + new NameChangeUDPSender(ip_address, port, ip_address, port, + FMT_JSON, ncr_handler, 100))); + + EXPECT_EQ(100, sender->getQueueMaxSize()); +} + +/// @brief Tests NameChangeUDPSender basic send functionality +TEST_F(NameChangeUDPSenderBasicTest, basicSendTests) { + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + + // Tests are based on a list of messages, get the count now. + int num_msgs = sizeof(valid_msgs)/sizeof(char*); + + // Create the sender, setting the queue max equal to the number of + // messages we will have in the list. + NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address, + LISTENER_PORT, FMT_JSON, ncr_handler, + num_msgs, true); + + // Verify that we can start sending. + EXPECT_NO_THROW(sender.startSending(io_service)); + EXPECT_TRUE(sender.amSending()); + + // Verify that attempting to send when we already are is an error. + EXPECT_THROW(sender.startSending(io_service), NcrSenderError); + + // Verify that we can stop sending. + EXPECT_NO_THROW(sender.stopSending()); + EXPECT_FALSE(sender.amSending()); + + // Verify that attempting to stop sending when we are not is ok. + EXPECT_NO_THROW(sender.stopSending()); + + // Verify that we can re-enter sending after stopping. + EXPECT_NO_THROW(sender.startSending(io_service)); + EXPECT_TRUE(sender.amSending()); + + // Fetch the sender's select-fd. + int select_fd = sender.getSelectFd(); + + // Verify select_fd is valid and currently shows no ready to read. + ASSERT_NE(util::WatchSocket::SOCKET_NOT_VALID, select_fd); + + // Make sure select_fd does evaluates to not ready via select and + // that ioReady() method agrees. + ASSERT_EQ(0, selectCheck(select_fd)); + ASSERT_FALSE(sender.ioReady()); + + // Iterate over a series of messages, sending each one. Since we + // do not invoke IOService::run, then the messages should accumulate + // in the queue. + NameChangeRequestPtr ncr; + NameChangeRequestPtr ncr2; + for (int i = 0; i < num_msgs; i++) { + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i])); + EXPECT_NO_THROW(sender.sendRequest(ncr)); + // Verify that the queue count increments in step with each send. + EXPECT_EQ(i+1, sender.getQueueSize()); + + // Verify that peekAt(i) returns the NCR we just added. + ASSERT_NO_THROW(ncr2 = sender.peekAt(i)); + ASSERT_TRUE(ncr2); + EXPECT_TRUE(*ncr == *ncr2); + } + + // Verify that attempting to peek beyond the end of the queue, throws. + ASSERT_THROW(sender.peekAt(sender.getQueueSize()+1), NcrSenderError); + + // Verify that attempting to send an additional message results in a + // queue full exception. + EXPECT_THROW(sender.sendRequest(ncr), NcrSenderQueueFull); + + // Loop for the number of valid messages. So long as there is at least + // on NCR in the queue, select-fd indicate ready to read. Invoke + // IOService::run_one. This should complete the send of exactly one + // message and the queue count should decrement accordingly. + for (int i = num_msgs; i > 0; i--) { + // Make sure select_fd does evaluates to ready via select and + // that ioReady() method agrees. + ASSERT_TRUE(selectCheck(select_fd) > 0); + ASSERT_TRUE(sender.ioReady()); + + // Execute at one ready handler. + ASSERT_NO_THROW(sender.runReadyIO()); + + // Verify that the queue count decrements in step with each run. + EXPECT_EQ(i-1, sender.getQueueSize()); + } + + // Make sure select_fd does evaluates to not ready via select and + // that ioReady() method agrees. + ASSERT_EQ(0, selectCheck(select_fd)); + ASSERT_FALSE(sender.ioReady()); + + // Verify that the queue is empty. + EXPECT_EQ(0, sender.getQueueSize()); + + // Verify that we can add back to the queue + EXPECT_NO_THROW(sender.sendRequest(ncr)); + EXPECT_EQ(1, sender.getQueueSize()); + + // Verify that we can remove the current entry at the front of the queue. + EXPECT_NO_THROW(sender.skipNext()); + EXPECT_EQ(0, sender.getQueueSize()); + + // Verify that flushing the queue is not allowed in sending state. + EXPECT_THROW(sender.clearSendQueue(), NcrSenderError); + + // Put num_msgs messages on the queue. + for (int i = 0; i < num_msgs; i++) { + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i])); + EXPECT_NO_THROW(sender.sendRequest(ncr)); + } + + // Make sure we have number of messages expected. + EXPECT_EQ(num_msgs, sender.getQueueSize()); + + // Verify that we can gracefully stop sending. + EXPECT_NO_THROW(sender.stopSending()); + EXPECT_FALSE(sender.amSending()); + + // Verify that the queue is preserved after leaving sending state. + EXPECT_EQ(num_msgs - 1, sender.getQueueSize()); + + // Verify that flushing the queue works when not sending. + EXPECT_NO_THROW(sender.clearSendQueue()); + EXPECT_EQ(0, sender.getQueueSize()); +} + +/// @brief Tests NameChangeUDPSender basic send functionality +TEST_F(NameChangeUDPSenderBasicTest, basicSendTestsMultiThreading) { + // Enable multi-threading + MultiThreadingMgr::instance().setMode(true); + + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + + // Tests are based on a list of messages, get the count now. + int num_msgs = sizeof(valid_msgs)/sizeof(char*); + + // Create the sender, setting the queue max equal to the number of + // messages we will have in the list. + NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address, + LISTENER_PORT, FMT_JSON, ncr_handler, + num_msgs, true); + + // Verify that we can start sending. + EXPECT_NO_THROW(sender.startSending(io_service)); + EXPECT_TRUE(sender.amSending()); + + // Verify that attempting to send when we already are is an error. + EXPECT_THROW(sender.startSending(io_service), NcrSenderError); + + // Verify that we can stop sending. + EXPECT_NO_THROW(sender.stopSending()); + EXPECT_FALSE(sender.amSending()); + + // Verify that attempting to stop sending when we are not is ok. + EXPECT_NO_THROW(sender.stopSending()); + + // Verify that we can re-enter sending after stopping. + EXPECT_NO_THROW(sender.startSending(io_service)); + EXPECT_TRUE(sender.amSending()); + + // Fetch the sender's select-fd. + int select_fd = sender.getSelectFd(); + + // Verify select_fd is valid and currently shows no ready to read. + ASSERT_NE(util::WatchSocket::SOCKET_NOT_VALID, select_fd); + + // Make sure select_fd does evaluates to not ready via select and + // that ioReady() method agrees. + ASSERT_EQ(0, selectCheck(select_fd)); + ASSERT_FALSE(sender.ioReady()); + + // Iterate over a series of messages, sending each one. Since we + // do not invoke IOService::run, then the messages should accumulate + // in the queue. + NameChangeRequestPtr ncr; + NameChangeRequestPtr ncr2; + for (int i = 0; i < num_msgs; i++) { + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i])); + EXPECT_NO_THROW(sender.sendRequest(ncr)); + // Verify that the queue count increments in step with each send. + EXPECT_EQ(i+1, sender.getQueueSize()); + + // Verify that peekAt(i) returns the NCR we just added. + ASSERT_NO_THROW(ncr2 = sender.peekAt(i)); + ASSERT_TRUE(ncr2); + EXPECT_TRUE(*ncr == *ncr2); + } + + // Verify that attempting to peek beyond the end of the queue, throws. + ASSERT_THROW(sender.peekAt(sender.getQueueSize()+1), NcrSenderError); + + // Verify that attempting to send an additional message results in a + // queue full exception. + EXPECT_THROW(sender.sendRequest(ncr), NcrSenderQueueFull); + + // Loop for the number of valid messages. So long as there is at least + // on NCR in the queue, select-fd indicate ready to read. Invoke + // IOService::run_one. This should complete the send of exactly one + // message and the queue count should decrement accordingly. + for (int i = num_msgs; i > 0; i--) { + // Make sure select_fd does evaluates to ready via select and + // that ioReady() method agrees. + ASSERT_TRUE(selectCheck(select_fd) > 0); + ASSERT_TRUE(sender.ioReady()); + + // Execute at one ready handler. + ASSERT_NO_THROW(sender.runReadyIO()); + + // Verify that the queue count decrements in step with each run. + EXPECT_EQ(i-1, sender.getQueueSize()); + } + + // Make sure select_fd does evaluates to not ready via select and + // that ioReady() method agrees. + ASSERT_EQ(0, selectCheck(select_fd)); + ASSERT_FALSE(sender.ioReady()); + + // Verify that the queue is empty. + EXPECT_EQ(0, sender.getQueueSize()); + + // Verify that we can add back to the queue + EXPECT_NO_THROW(sender.sendRequest(ncr)); + EXPECT_EQ(1, sender.getQueueSize()); + + // Verify that we can remove the current entry at the front of the queue. + EXPECT_NO_THROW(sender.skipNext()); + EXPECT_EQ(0, sender.getQueueSize()); + + // Verify that flushing the queue is not allowed in sending state. + EXPECT_THROW(sender.clearSendQueue(), NcrSenderError); + + // Put num_msgs messages on the queue. + for (int i = 0; i < num_msgs; i++) { + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i])); + EXPECT_NO_THROW(sender.sendRequest(ncr)); + } + + // Make sure we have number of messages expected. + EXPECT_EQ(num_msgs, sender.getQueueSize()); + + // Verify that we can gracefully stop sending. + EXPECT_NO_THROW(sender.stopSending()); + EXPECT_FALSE(sender.amSending()); + + // Verify that the queue is preserved after leaving sending state. + EXPECT_EQ(num_msgs - 1, sender.getQueueSize()); + + // Verify that flushing the queue works when not sending. + EXPECT_NO_THROW(sender.clearSendQueue()); + EXPECT_EQ(0, sender.getQueueSize()); +} + +/// @brief Tests that sending gets kick-started if the queue isn't empty +/// when startSending is called. +TEST_F(NameChangeUDPSenderBasicTest, autoStart) { + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + + // Tests are based on a list of messages, get the count now. + int num_msgs = sizeof(valid_msgs)/sizeof(char*); + + // Create the sender, setting the queue max equal to the number of + // messages we will have in the list. + NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address, + LISTENER_PORT, FMT_JSON, ncr_handler, + num_msgs, true); + + // Verify that we can start sending. + EXPECT_NO_THROW(sender.startSending(io_service)); + EXPECT_TRUE(sender.amSending()); + + // Queue up messages. + NameChangeRequestPtr ncr; + for (int i = 0; i < num_msgs; i++) { + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i])); + EXPECT_NO_THROW(sender.sendRequest(ncr)); + } + // Make sure queue count is what we expect. + EXPECT_EQ(num_msgs, sender.getQueueSize()); + + // Stop sending. + ASSERT_NO_THROW(sender.stopSending()); + ASSERT_FALSE(sender.amSending()); + + // We should have completed the first message only. + EXPECT_EQ(--num_msgs, sender.getQueueSize()); + + // Restart sending. + EXPECT_NO_THROW(sender.startSending(io_service)); + + // We should be able to loop through remaining messages and send them. + for (int i = num_msgs; i > 0; i--) { + // ioReady() should evaluate to true. + ASSERT_TRUE(sender.ioReady()); + + // Execute at one ready handler. + ASSERT_NO_THROW(sender.runReadyIO()); + } + + // Verify that the queue is empty. + EXPECT_EQ(0, sender.getQueueSize()); +} + +/// @brief Tests that sending gets kick-started if the queue isn't empty +/// when startSending is called. +TEST_F(NameChangeUDPSenderBasicTest, autoStartMultiThreading) { + // Enable multi-threading + MultiThreadingMgr::instance().setMode(true); + + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + + // Tests are based on a list of messages, get the count now. + int num_msgs = sizeof(valid_msgs)/sizeof(char*); + + // Create the sender, setting the queue max equal to the number of + // messages we will have in the list. + NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address, + LISTENER_PORT, FMT_JSON, ncr_handler, + num_msgs, true); + + // Verify that we can start sending. + EXPECT_NO_THROW(sender.startSending(io_service)); + EXPECT_TRUE(sender.amSending()); + + // Queue up messages. + NameChangeRequestPtr ncr; + for (int i = 0; i < num_msgs; i++) { + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i])); + EXPECT_NO_THROW(sender.sendRequest(ncr)); + } + // Make sure queue count is what we expect. + EXPECT_EQ(num_msgs, sender.getQueueSize()); + + // Stop sending. + ASSERT_NO_THROW(sender.stopSending()); + ASSERT_FALSE(sender.amSending()); + + // We should have completed the first message only. + EXPECT_EQ(--num_msgs, sender.getQueueSize()); + + // Restart sending. + EXPECT_NO_THROW(sender.startSending(io_service)); + + // We should be able to loop through remaining messages and send them. + for (int i = num_msgs; i > 0; i--) { + // ioReady() should evaluate to true. + ASSERT_TRUE(sender.ioReady()); + + // Execute at one ready handler. + ASSERT_NO_THROW(sender.runReadyIO()); + } + + // Verify that the queue is empty. + EXPECT_EQ(0, sender.getQueueSize()); +} + +/// @brief Tests NameChangeUDPSender basic send with INADDR_ANY and port 0. +TEST_F(NameChangeUDPSenderBasicTest, anyAddressSend) { + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + isc::asiolink::IOAddress any_address("0.0.0.0"); + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + + // Tests are based on a list of messages, get the count now. + int num_msgs = sizeof(valid_msgs)/sizeof(char*); + + // Create the sender, setting the queue max equal to the number of + // messages we will have in the list. + NameChangeUDPSender sender(any_address, 0, ip_address, LISTENER_PORT, + FMT_JSON, ncr_handler, num_msgs); + + // Enter send mode. + ASSERT_NO_THROW(sender.startSending(io_service)); + EXPECT_TRUE(sender.amSending()); + + // Create and queue up a message. + NameChangeRequestPtr ncr; + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0])); + EXPECT_NO_THROW(sender.sendRequest(ncr)); + EXPECT_EQ(1, sender.getQueueSize()); + + // Verify we have a ready IO, then execute at one ready handler. + ASSERT_TRUE(sender.ioReady()); + ASSERT_NO_THROW(sender.runReadyIO()); + + // Verify that sender shows no IO ready. + // and that the queue is empty. + ASSERT_FALSE(sender.ioReady()); + EXPECT_EQ(0, sender.getQueueSize()); +} + +/// @brief Tests NameChangeUDPSender basic send with INADDR_ANY and port 0. +TEST_F(NameChangeUDPSenderBasicTest, anyAddressSendMultiThreading) { + // Enable multi-threading + MultiThreadingMgr::instance().setMode(true); + + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + isc::asiolink::IOAddress any_address("0.0.0.0"); + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + + // Tests are based on a list of messages, get the count now. + int num_msgs = sizeof(valid_msgs)/sizeof(char*); + + // Create the sender, setting the queue max equal to the number of + // messages we will have in the list. + NameChangeUDPSender sender(any_address, 0, ip_address, LISTENER_PORT, + FMT_JSON, ncr_handler, num_msgs); + + // Enter send mode. + ASSERT_NO_THROW(sender.startSending(io_service)); + EXPECT_TRUE(sender.amSending()); + + // Create and queue up a message. + NameChangeRequestPtr ncr; + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0])); + EXPECT_NO_THROW(sender.sendRequest(ncr)); + EXPECT_EQ(1, sender.getQueueSize()); + + // Verify we have a ready IO, then execute at one ready handler. + ASSERT_TRUE(sender.ioReady()); + ASSERT_NO_THROW(sender.runReadyIO()); + + // Verify that sender shows no IO ready. + // and that the queue is empty. + ASSERT_FALSE(sender.ioReady()); + EXPECT_EQ(0, sender.getQueueSize()); +} + +/// @brief Test the NameChangeSender::assumeQueue method. +TEST_F(NameChangeUDPSenderBasicTest, assumeQueue) { + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + uint32_t port = SENDER_PORT; + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + NameChangeRequestPtr ncr; + + // Tests are based on a list of messages, get the count now. + int num_msgs = sizeof(valid_msgs)/sizeof(char*); + + // Create two senders with queue max equal to the number of + // messages we will have in the list. + NameChangeUDPSender sender1(ip_address, port, ip_address, port, + FMT_JSON, ncr_handler, num_msgs); + + NameChangeUDPSender sender2(ip_address, port+1, ip_address, port, + FMT_JSON, ncr_handler, num_msgs); + + // Place sender1 into send mode and queue up messages. + ASSERT_NO_THROW(sender1.startSending(io_service)); + for (int i = 0; i < num_msgs; i++) { + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i])); + ASSERT_NO_THROW(sender1.sendRequest(ncr)); + } + + // Make sure sender1's queue count is as expected. + ASSERT_EQ(num_msgs, sender1.getQueueSize()); + + // Verify sender1 is sending, sender2 is not. + ASSERT_TRUE(sender1.amSending()); + ASSERT_FALSE(sender2.amSending()); + + // Transfer from sender1 to sender2 should fail because + // sender1 is in send mode. + ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError); + + // Take sender1 out of send mode. + ASSERT_NO_THROW(sender1.stopSending()); + ASSERT_FALSE(sender1.amSending()); + // Stopping should have completed the first message. + --num_msgs; + EXPECT_EQ(num_msgs, sender1.getQueueSize()); + + // Transfer should succeed. Verify sender1 has none, + // and sender2 has num_msgs queued. + EXPECT_NO_THROW(sender2.assumeQueue(sender1)); + EXPECT_EQ(0, sender1.getQueueSize()); + EXPECT_EQ(num_msgs, sender2.getQueueSize()); + + // Reduce sender1's max queue size. + ASSERT_NO_THROW(sender1.setQueueMaxSize(num_msgs - 1)); + + // Transfer should fail as sender1's queue is not large enough. + ASSERT_THROW(sender1.assumeQueue(sender2), NcrSenderError); + + // Place sender1 into send mode and queue up a message. + ASSERT_NO_THROW(sender1.startSending(io_service)); + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0])); + ASSERT_NO_THROW(sender1.sendRequest(ncr)); + + // Take sender1 out of send mode. + ASSERT_NO_THROW(sender1.stopSending()); + + // Try to transfer from sender1 to sender2. This should fail + // as sender2's queue is not empty. + ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError); +} + +/// @brief Test the NameChangeSender::assumeQueue method. +TEST_F(NameChangeUDPSenderBasicTest, assumeQueueMultiThreading) { + // Enable multi-threading + MultiThreadingMgr::instance().setMode(true); + + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + uint32_t port = SENDER_PORT; + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + NameChangeRequestPtr ncr; + + // Tests are based on a list of messages, get the count now. + int num_msgs = sizeof(valid_msgs)/sizeof(char*); + + // Create two senders with queue max equal to the number of + // messages we will have in the list. + NameChangeUDPSender sender1(ip_address, port, ip_address, port, + FMT_JSON, ncr_handler, num_msgs); + + NameChangeUDPSender sender2(ip_address, port+1, ip_address, port, + FMT_JSON, ncr_handler, num_msgs); + + // Place sender1 into send mode and queue up messages. + ASSERT_NO_THROW(sender1.startSending(io_service)); + for (int i = 0; i < num_msgs; i++) { + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i])); + ASSERT_NO_THROW(sender1.sendRequest(ncr)); + } + + // Make sure sender1's queue count is as expected. + ASSERT_EQ(num_msgs, sender1.getQueueSize()); + + // Verify sender1 is sending, sender2 is not. + ASSERT_TRUE(sender1.amSending()); + ASSERT_FALSE(sender2.amSending()); + + // Transfer from sender1 to sender2 should fail because + // sender1 is in send mode. + ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError); + + // Take sender1 out of send mode. + ASSERT_NO_THROW(sender1.stopSending()); + ASSERT_FALSE(sender1.amSending()); + // Stopping should have completed the first message. + --num_msgs; + EXPECT_EQ(num_msgs, sender1.getQueueSize()); + + // Transfer should succeed. Verify sender1 has none, + // and sender2 has num_msgs queued. + EXPECT_NO_THROW(sender2.assumeQueue(sender1)); + EXPECT_EQ(0, sender1.getQueueSize()); + EXPECT_EQ(num_msgs, sender2.getQueueSize()); + + // Reduce sender1's max queue size. + ASSERT_NO_THROW(sender1.setQueueMaxSize(num_msgs - 1)); + + // Transfer should fail as sender1's queue is not large enough. + ASSERT_THROW(sender1.assumeQueue(sender2), NcrSenderError); + + // Place sender1 into send mode and queue up a message. + ASSERT_NO_THROW(sender1.startSending(io_service)); + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0])); + ASSERT_NO_THROW(sender1.sendRequest(ncr)); + + // Take sender1 out of send mode. + ASSERT_NO_THROW(sender1.stopSending()); + + // Try to transfer from sender1 to sender2. This should fail + // as sender2's queue is not empty. + ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError); +} + +/// @brief Text fixture that allows testing a listener and sender together +/// It derives from both the receive and send handler classes and contains +/// and instance of UDP listener and UDP sender. +class NameChangeUDPTest : public virtual ::testing::Test, + NameChangeListener::RequestReceiveHandler, + NameChangeSender::RequestSendHandler { +public: + isc::asiolink::IOService io_service_; + NameChangeListener::Result recv_result_; + NameChangeSender::Result send_result_; + NameChangeListenerPtr listener_; + NameChangeSenderPtr sender_; + isc::asiolink::IntervalTimer test_timer_; + + std::vector<NameChangeRequestPtr> sent_ncrs_; + std::vector<NameChangeRequestPtr> received_ncrs_; + + NameChangeUDPTest() + : io_service_(), recv_result_(NameChangeListener::SUCCESS), + send_result_(NameChangeSender::SUCCESS), test_timer_(io_service_) { + isc::asiolink::IOAddress addr(TEST_ADDRESS); + // Create our listener instance. Note that reuse_address is true. + listener_.reset( + new NameChangeUDPListener(addr, LISTENER_PORT, FMT_JSON, + *this, true)); + + // Create our sender instance. Note that reuse_address is true. + sender_.reset( + new NameChangeUDPSender(addr, SENDER_PORT, addr, LISTENER_PORT, + FMT_JSON, *this, 100, true)); + + // Set the test timeout to break any running tasks if they hang. + test_timer_.setup(std::bind(&NameChangeUDPTest::testTimeoutHandler, + this), + TEST_TIMEOUT); + // Disable multi-threading + MultiThreadingMgr::instance().setMode(false); + } + + ~NameChangeUDPTest() { + // Disable multi-threading + MultiThreadingMgr::instance().setMode(false); + } + + void reset_results() { + sent_ncrs_.clear(); + received_ncrs_.clear(); + } + + /// @brief Implements the receive completion handler. + virtual void operator ()(const NameChangeListener::Result result, + NameChangeRequestPtr& ncr) { + // save the result and the NCR received. + recv_result_ = result; + received_ncrs_.push_back(ncr); + } + + /// @brief Implements the send completion handler. + virtual void operator ()(const NameChangeSender::Result result, + NameChangeRequestPtr& ncr) { + // save the result and the NCR sent. + send_result_ = result; + sent_ncrs_.push_back(ncr); + } + + /// @brief Handler invoked when test timeout is hit + /// + /// This callback stops all running (hanging) tasks on IO service. + void testTimeoutHandler() { + io_service_.stop(); + FAIL() << "Test timeout hit."; + } + + /// @brief checks the received NCR content, ignoring the order if necessary. + /// + /// The UDP does not provide any guarantees regarding order. While in most cases + /// the NCRs are received in the order they're sent, that's not guaranteed. As such, + /// the test does the normal ordered check, which will pass in most cases. However, + /// we have documented Jenkins history that it sometimes fail. In those cases, we + /// need to go through a full check assuming the packets were received not in order. + /// If that fails, the test will print detailed information about the failure. + /// + /// This function returns a status, but it's not really necessary to check it as + /// it calls gtest macros to indicate failures. + /// + /// @param num_msgs number of received and sent messages + /// @param sent vector of sent NCRs + /// @param rcvd vector of received NCRs + /// @return true if all packets were received, false otherwise + bool checkUnordered(size_t num_msgs, const std::vector<NameChangeRequestPtr>& sent, + const std::vector<NameChangeRequestPtr>& rcvd) { + // Verify that what we sent matches what we received. + // WRONG ASSUMPTION HERE: UDP does not guarantee ordered delivery. + bool ok = true; + for (int i = 0; i < num_msgs; i++) { + if (!checkSendVsReceived(sent[i], rcvd[i])) { + // Ok, the data was not received in order. + ok = false; + break; + } + } + if (!ok) { + std::cout << "UDP data received not in order! Checking un ordered delivery" + << std::endl; + // We need to double iterate through the messages to check every one + // against one another. + for (int i = 0; i < num_msgs; i++) { + ok = false; + for (int j = 0; j < num_msgs; j++) { + if (checkSendVsReceived(sent[i], rcvd[j])) { + std::cout << "Found UDP packet " << i << ", received as " << j << "th" + << std::endl; + ok = true; + } + } + EXPECT_TRUE(ok) << "failed to find UDP packet " << i << " among total of " + << num_msgs << " messages received"; + } + } + return (ok); + } +}; + +/// @brief Uses a sender and listener to test UDP-based NCR delivery +/// Conducts a "round-trip" test using a sender to transmit a set of valid +/// NCRs to a listener. The test verifies that what was sent matches what +/// was received both in quantity and in content. +TEST_F(NameChangeUDPTest, roundTripTest) { + // Place the listener into listening state. + ASSERT_NO_THROW(listener_->startListening(io_service_)); + EXPECT_TRUE(listener_->amListening()); + + // Get the number of messages in the list of test messages. + int num_msgs = sizeof(valid_msgs)/sizeof(char*); + + // Place the sender into sending state. + ASSERT_NO_THROW(sender_->startSending(io_service_)); + EXPECT_TRUE(sender_->amSending()); + + for (int i = 0; i < num_msgs; i++) { + NameChangeRequestPtr ncr; + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i])); + sender_->sendRequest(ncr); + EXPECT_EQ(i+1, sender_->getQueueSize()); + } + + // Execute callbacks until we have sent and received all of messages. + while (sender_->getQueueSize() > 0 || (received_ncrs_.size() < num_msgs)) { + EXPECT_NO_THROW(io_service_.run_one()); + } + + // Send queue should be empty. + EXPECT_EQ(0, sender_->getQueueSize()); + + // We should have the same number of sends and receives as we do messages. + ASSERT_EQ(num_msgs, sent_ncrs_.size()); + ASSERT_EQ(num_msgs, received_ncrs_.size()); + + // Check if the payload was received, ignoring the order if necessary. + checkUnordered(num_msgs, sent_ncrs_, received_ncrs_); + + // Verify that we can gracefully stop listening. + EXPECT_NO_THROW(listener_->stopListening()); + EXPECT_FALSE(listener_->amListening()); + + // Verify that IO pending is false, after cancel event occurs. + EXPECT_NO_THROW(io_service_.run_one()); + EXPECT_FALSE(listener_->isIoPending()); + + // Verify that we can gracefully stop sending. + EXPECT_NO_THROW(sender_->stopSending()); + EXPECT_FALSE(sender_->amSending()); +} + +/// @brief Uses a sender and listener to test UDP-based NCR delivery +/// Conducts a "round-trip" test using a sender to transmit a set of valid +/// NCRs to a listener. The test verifies that what was sent matches what +/// was received both in quantity and in content. +TEST_F(NameChangeUDPTest, roundTripTestMultiThreading) { + // Enable multi-threading + MultiThreadingMgr::instance().setMode(true); + + // Place the listener into listening state. + ASSERT_NO_THROW(listener_->startListening(io_service_)); + EXPECT_TRUE(listener_->amListening()); + + // Get the number of messages in the list of test messages. + int num_msgs = sizeof(valid_msgs)/sizeof(char*); + + // Place the sender into sending state. + ASSERT_NO_THROW(sender_->startSending(io_service_)); + EXPECT_TRUE(sender_->amSending()); + + for (int i = 0; i < num_msgs; i++) { + NameChangeRequestPtr ncr; + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i])); + sender_->sendRequest(ncr); + EXPECT_EQ(i+1, sender_->getQueueSize()); + } + + // Execute callbacks until we have sent and received all of messages. + while (sender_->getQueueSize() > 0 || (received_ncrs_.size() < num_msgs)) { + EXPECT_NO_THROW(io_service_.run_one()); + } + + // Send queue should be empty. + EXPECT_EQ(0, sender_->getQueueSize()); + + // We should have the same number of sends and receives as we do messages. + ASSERT_EQ(num_msgs, sent_ncrs_.size()); + ASSERT_EQ(num_msgs, received_ncrs_.size()); + + // Verify that what we sent matches what we received. Ignore the order + // if necessary. + checkUnordered(num_msgs, sent_ncrs_, received_ncrs_); + + // Verify that we can gracefully stop listening. + EXPECT_NO_THROW(listener_->stopListening()); + EXPECT_FALSE(listener_->amListening()); + + // Verify that IO pending is false, after cancel event occurs. + EXPECT_NO_THROW(io_service_.run_one()); + EXPECT_FALSE(listener_->isIoPending()); + + // Verify that we can gracefully stop sending. + EXPECT_NO_THROW(sender_->stopSending()); + EXPECT_FALSE(sender_->amSending()); +} + +// Tests error handling of a failure to mark the watch socket ready, when +// sendRequest() is called. +TEST_F(NameChangeUDPSenderBasicTest, watchClosedBeforeSendRequest) { + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + + // Create the sender and put into send mode. + NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT, + FMT_JSON, ncr_handler, 100, true); + ASSERT_NO_THROW(sender.startSending(io_service)); + ASSERT_TRUE(sender.amSending()); + + // Create an NCR. + NameChangeRequestPtr ncr; + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0])); + + // Tamper with the watch socket by closing the select-fd. + close(sender.getSelectFd()); + + // Send should fail as we interfered by closing the select-fd. + ASSERT_THROW(sender.sendRequest(ncr), util::WatchSocketError); + + // Verify we didn't invoke the handler. + EXPECT_EQ(0, ncr_handler.pass_count_); + EXPECT_EQ(0, ncr_handler.error_count_); + + // Request remains in the queue. Technically it was sent but its + // completion handler won't get called. + EXPECT_EQ(1, sender.getQueueSize()); +} + +// Tests error handling of a failure to mark the watch socket ready, when +// sendRequest() is called. +TEST_F(NameChangeUDPSenderBasicTest, watchClosedBeforeSendRequestMultiThreading) { + // Enable multi-threading + MultiThreadingMgr::instance().setMode(true); + + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + + // Create the sender and put into send mode. + NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT, + FMT_JSON, ncr_handler, 100, true); + ASSERT_NO_THROW(sender.startSending(io_service)); + ASSERT_TRUE(sender.amSending()); + + // Create an NCR. + NameChangeRequestPtr ncr; + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0])); + + // Tamper with the watch socket by closing the select-fd. + close(sender.getSelectFd()); + + // Send should fail as we interfered by closing the select-fd. + ASSERT_THROW(sender.sendRequest(ncr), util::WatchSocketError); + + // Verify we didn't invoke the handler. + EXPECT_EQ(0, ncr_handler.pass_count_); + EXPECT_EQ(0, ncr_handler.error_count_); + + // Request remains in the queue. Technically it was sent but its + // completion handler won't get called. + EXPECT_EQ(1, sender.getQueueSize()); +} + +// Tests error handling of a failure to mark the watch socket ready, when +// sendNext() is called during completion handling. +TEST_F(NameChangeUDPSenderBasicTest, watchClosedAfterSendRequest) { + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + + // Create the sender and put into send mode. + NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT, + FMT_JSON, ncr_handler, 100, true); + ASSERT_NO_THROW(sender.startSending(io_service)); + ASSERT_TRUE(sender.amSending()); + + // Build and queue up 2 messages. No handlers will get called yet. + for (int i = 0; i < 2; i++) { + NameChangeRequestPtr ncr; + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i])); + sender.sendRequest(ncr); + EXPECT_EQ(i+1, sender.getQueueSize()); + } + + // Tamper with the watch socket by closing the select-fd. + close (sender.getSelectFd()); + + // Run one handler. This should execute the send completion handler + // after sending the first message. Doing completion handling, we will + // attempt to queue the second message which should fail. + ASSERT_NO_THROW(sender.runReadyIO()); + + // Verify handler got called twice. First request should have be sent + // without error, second call should have failed to send due to watch + // socket markReady failure. + EXPECT_EQ(1, ncr_handler.pass_count_); + EXPECT_EQ(1, ncr_handler.error_count_); + + // The second request should still be in the queue. + EXPECT_EQ(1, sender.getQueueSize()); +} + +// Tests error handling of a failure to mark the watch socket ready, when +// sendNext() is called during completion handling. +TEST_F(NameChangeUDPSenderBasicTest, watchClosedAfterSendRequestMultiThreading) { + // Enable multi-threading + MultiThreadingMgr::instance().setMode(true); + + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + + // Create the sender and put into send mode. + NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT, + FMT_JSON, ncr_handler, 100, true); + ASSERT_NO_THROW(sender.startSending(io_service)); + ASSERT_TRUE(sender.amSending()); + + // Build and queue up 2 messages. No handlers will get called yet. + for (int i = 0; i < 2; i++) { + NameChangeRequestPtr ncr; + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i])); + sender.sendRequest(ncr); + EXPECT_EQ(i+1, sender.getQueueSize()); + } + + // Tamper with the watch socket by closing the select-fd. + close (sender.getSelectFd()); + + // Run one handler. This should execute the send completion handler + // after sending the first message. Doing completion handling, we will + // attempt to queue the second message which should fail. + ASSERT_NO_THROW(sender.runReadyIO()); + + // Verify handler got called twice. First request should have be sent + // without error, second call should have failed to send due to watch + // socket markReady failure. + EXPECT_EQ(1, ncr_handler.pass_count_); + EXPECT_EQ(1, ncr_handler.error_count_); + + // The second request should still be in the queue. + EXPECT_EQ(1, sender.getQueueSize()); +} + + +// Tests error handling of a failure to clear the watch socket during +// completion handling. +TEST_F(NameChangeUDPSenderBasicTest, watchSocketBadRead) { + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + + // Create the sender and put into send mode. + NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT, + FMT_JSON, ncr_handler, 100, true); + ASSERT_NO_THROW(sender.startSending(io_service)); + ASSERT_TRUE(sender.amSending()); + + // Build and queue up 2 messages. No handlers will get called yet. + for (int i = 0; i < 2; i++) { + NameChangeRequestPtr ncr; + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i])); + sender.sendRequest(ncr); + EXPECT_EQ(i+1, sender.getQueueSize()); + } + + // Fetch the sender's select-fd. + int select_fd = sender.getSelectFd(); + + // Verify that select_fd appears ready. + ASSERT_TRUE(selectCheck(select_fd) > 0); + + // Interfere by reading part of the marker from the select-fd. + uint32_t buf = 0; + ASSERT_EQ((read (select_fd, &buf, 1)), 1); + ASSERT_NE(util::WatchSocket::MARKER, buf); + + // Run one handler. This should execute the send completion handler + // after sending the message. Doing completion handling clearing the + // watch socket should fail, which will close the socket, but not + // result in a throw. + ASSERT_NO_THROW(sender.runReadyIO()); + + // Verify handler got called twice. First request should have be sent + // without error, second call should have failed to send due to watch + // socket markReady failure. + EXPECT_EQ(1, ncr_handler.pass_count_); + EXPECT_EQ(1, ncr_handler.error_count_); + + // The second request should still be in the queue. + EXPECT_EQ(1, sender.getQueueSize()); +} + +// Tests error handling of a failure to clear the watch socket during +// completion handling. +TEST_F(NameChangeUDPSenderBasicTest, watchSocketBadReadMultiThreading) { + // Enable multi-threading + MultiThreadingMgr::instance().setMode(true); + + isc::asiolink::IOAddress ip_address(TEST_ADDRESS); + isc::asiolink::IOService io_service; + SimpleSendHandler ncr_handler; + + // Create the sender and put into send mode. + NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT, + FMT_JSON, ncr_handler, 100, true); + ASSERT_NO_THROW(sender.startSending(io_service)); + ASSERT_TRUE(sender.amSending()); + + // Build and queue up 2 messages. No handlers will get called yet. + for (int i = 0; i < 2; i++) { + NameChangeRequestPtr ncr; + ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i])); + sender.sendRequest(ncr); + EXPECT_EQ(i+1, sender.getQueueSize()); + } + + // Fetch the sender's select-fd. + int select_fd = sender.getSelectFd(); + + // Verify that select_fd appears ready. + ASSERT_TRUE(selectCheck(select_fd) > 0); + + // Interfere by reading part of the marker from the select-fd. + uint32_t buf = 0; + ASSERT_EQ((read (select_fd, &buf, 1)), 1); + ASSERT_NE(util::WatchSocket::MARKER, buf); + + // Run one handler. This should execute the send completion handler + // after sending the message. Doing completion handling clearing the + // watch socket should fail, which will close the socket, but not + // result in a throw. + ASSERT_NO_THROW(sender.runReadyIO()); + + // Verify handler got called twice. First request should have be sent + // without error, second call should have failed to send due to watch + // socket markReady failure. + EXPECT_EQ(1, ncr_handler.pass_count_); + EXPECT_EQ(1, ncr_handler.error_count_); + + // The second request should still be in the queue. + EXPECT_EQ(1, sender.getQueueSize()); +} + +} // end of anonymous namespace |