Commit 5945c9e2 authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

[master] Merge branch 'trac3221'

Integrates use of NameChangeUDPSender in D2ClientMgr.
parents 9559e3c2 412fdad4
......@@ -34,6 +34,7 @@ libb10_dhcp_ddns_la_SOURCES += dhcp_ddns_log.cc dhcp_ddns_log.h
libb10_dhcp_ddns_la_SOURCES += ncr_io.cc ncr_io.h
libb10_dhcp_ddns_la_SOURCES += ncr_msg.cc ncr_msg.h
libb10_dhcp_ddns_la_SOURCES += ncr_udp.cc ncr_udp.h
libb10_dhcp_ddns_la_SOURCES += watch_socket.cc watch_socket.h
nodist_libb10_dhcp_ddns_la_SOURCES = dhcp_ddns_messages.cc dhcp_ddns_messages.h
......
# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
# Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
......@@ -32,19 +32,25 @@ start another read after receiving a request. While possible, this is highly
unlikely and is probably a programmatic error. The application should recover
on its own.
% DHCP_DDNS_NCR_SEND_CLOSE_ERROR DHCP-DDNS client encountered an error while closing the sender connection used to send NameChangeRequests : %1
% DHCP_DDNS_NCR_SEND_CLOSE_ERROR DHCP-DDNS client encountered an error while closing the sender connection used to send NameChangeRequests: %1
This is an error message that indicates the DHCP-DDNS client was unable to
close the connection used to send NameChangeRequests. Closure may occur during
the course of error recovery or during normal shutdown procedure. In either
case the error is unlikely to impair the client's ability to send requests but
it should be reported for analysis.
% DHCP_DDNS_NCR_SEND_NEXT_ERROR DHCP-DDNS client could not initiate the next request send following send completion.
% DHCP_DDNS_NCR_SEND_NEXT_ERROR DHCP-DDNS client could not initiate the next request send following send completion: %1
This is a error message indicating that NameChangeRequest sender could not
start another send after completing the send of the previous request. While
possible, this is highly unlikely and is probably a programmatic error. The
application should recover on its own.
% DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR NCR UDP watch socket failed to clear: %1
This is an error message that indicates the application was unable to reset the
UDP NCR sender ready status after completing a send. This is programmatic error
that should be reported. The application may or may not continue to operate
correctly.
% DHCP_DDNS_NCR_UDP_RECV_CANCELED UDP socket receive was canceled while listening for DNS Update requests: %1
This is an informational message indicating that the listening over a UDP socket for DNS update requests has been canceled. This is a normal part of suspending listening operations.
......@@ -74,3 +80,15 @@ This is an error message that indicates that an exception was thrown but not
caught in the application's send completion handler. This is a programmatic
error that needs to be reported. Dependent upon the nature of the error the
client may or may not continue operating normally.
% DHCP_DDNS_WATCH_SINK_CLOSE_ERROR Sink-side watch socket failed to close: %1
This is an error message that indicates the application was unable to close
the inbound side of a NCR sender's watch socket. While technically possible
this error is highly unlikely to occur and should not impair the application's
ability to process requests.
% DHCP_DDNS_WATCH_SOURCE_CLOSE_ERROR Source-side watch socket failed to close: %1
This is an error message that indicates the application was unable to close
the outbound side of a NCR sender's watch socket. While technically possible
this error is highly unlikely to occur and should not impair the application's
ability to process requests.
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -23,7 +23,7 @@ namespace dhcp_ddns {
NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str) {
if (boost::iequals(protocol_str, "UDP")) {
return (NCR_UDP);
}
}
if (boost::iequals(protocol_str, "TCP")) {
return (NCR_TCP);
......@@ -162,10 +162,7 @@ NameChangeSender::NameChangeSender(RequestSendHandler& send_handler,
send_queue_max_(send_queue_max) {
// Queue size must be big enough to hold at least 1 entry.
if (send_queue_max == 0) {
isc_throw(NcrSenderError, "NameChangeSender constructor"
" queue size must be greater than zero");
}
setQueueMaxSize(send_queue_max);
}
void
......@@ -318,5 +315,57 @@ NameChangeSender::clearSendQueue() {
send_queue_.clear();
}
void
NameChangeSender::setQueueMaxSize(const size_t new_max) {
if (new_max == 0) {
isc_throw(NcrSenderError, "NameChangeSender:"
" queue size must be greater than zero");
}
send_queue_max_ = new_max;
}
const NameChangeRequestPtr&
NameChangeSender::peekAt(const size_t index) const {
if (index >= getQueueSize()) {
isc_throw(NcrSenderError,
"NameChangeSender::peekAt peek beyond end of queue attempted"
<< " index: " << index << " queue size: " << getQueueSize());
}
return (send_queue_.at(index));
}
void
NameChangeSender::assumeQueue(NameChangeSender& source_sender) {
if (source_sender.amSending()) {
isc_throw(NcrSenderError, "Cannot assume queue:"
" source sender is actively sending");
}
if (amSending()) {
isc_throw(NcrSenderError, "Cannot assume queue:"
" target sender is actively sending");
}
if (getQueueMaxSize() < source_sender.getQueueSize()) {
isc_throw(NcrSenderError, "Cannot assume queue:"
" source queue count exceeds target queue max");
}
if (!send_queue_.empty()) {
isc_throw(NcrSenderError, "Cannot assume queue:"
" target queue is not empty");
}
send_queue_.swap(source_sender.getSendQueue());
}
int
NameChangeSender::getSelectFd() {
isc_throw(NotImplemented, "NameChangeSender::getSelectFd is not supported");
}
} // namespace isc::dhcp_ddns
} // namespace isc
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -547,6 +547,35 @@ public:
/// capacity.
void sendRequest(NameChangeRequestPtr& ncr);
/// @brief Move all queued requests from a given sender into the send queue
///
/// Moves all of the entries in the given sender's queue and places them
/// into send queue. This provides a mechanism of reassigning queued
/// messages from one sender to another. This is useful for dealing with
/// dynamic configuration changes.
///
/// @param source_sender from whom the queued messages will be taken
///
/// @throw NcrSenderError if either sender is in send mode, if the number of
/// messages in the source sender's queue is larger than this sender's
/// maxium queue size, or if this sender's queue is not empty.
void assumeQueue(NameChangeSender& source_sender);
/// @brief Returns a file descriptor suitable for use with select
///
/// The value returned is an open file descriptor which can be used with
/// select() system call to monitor the sender for IO events. This allows
/// NameChangeSenders to be used in applications which use select, rather
/// than IOService to wait for IO events to occur.
///
/// @warning Attempting other use of this value may lead to unpredictable
/// behavior in the sender.
///
/// @return Returns an "open" file descriptor
///
/// @throw NcrSenderError if the sender is not in send mode,
virtual int getSelectFd() = 0;
protected:
/// @brief Dequeues and sends the next request on the send queue.
///
......@@ -659,11 +688,39 @@ public:
return (send_queue_max_);
}
/// @brief Sets the maxium queue size to the given value.
///
/// Sets the maximum number of entries allowed in the queue to the
/// the given value.
///
/// @param new_max the new value to use as the maximum
///
/// @throw NcrSenderError if the value is less than one.
void setQueueMaxSize(const size_t new_max);
/// @brief Returns the number of entries currently in the send queue.
size_t getQueueSize() const {
return (send_queue_.size());
}
/// @brief Returns the entry at a given position in the queue.
///
/// Note that the entry is not removed from the queue.
/// @param index the index of the entry in the queue to fetch.
/// Valid values are 0 (front of the queue) to (queue size - 1).
///
/// @return Pointer reference to the queue entry.
///
/// @throw NcrSenderError if the given index is beyond the
/// end of the queue.
const NameChangeRequestPtr& peekAt(const size_t index) const;
protected:
/// @brief Returns a reference to the send queue.
SendQueue& getSendQueue() {
return (send_queue_);
}
private:
/// @brief Sets the sending indicator to the given value.
///
......
......@@ -72,7 +72,7 @@ enum NameChangeFormat {
/// @brief Function which converts labels to NameChangeFormat enum values.
///
/// @param format_str text to convert to an enum.
/// @param fmt_str text to convert to an enum.
/// Valid string values: "JSON"
///
/// @return NameChangeFormat value which maps to the given string.
......
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -255,6 +255,8 @@ NameChangeUDPSender::open(isc::asiolink::IOService& io_service) {
UDPEndpoint(server_address_, server_port_));
send_callback_->setDataSource(server_endpoint_);
watch_socket_.reset(new WatchSocket());
}
void
......@@ -281,6 +283,8 @@ NameChangeUDPSender::close() {
}
socket_.reset();
watch_socket_.reset();
}
void
......@@ -297,11 +301,32 @@ NameChangeUDPSender::doSend(NameChangeRequestPtr& ncr) {
// Call the socket's asychronous send, passing our callback
socket_->asyncSend(send_callback_->getData(), send_callback_->getPutLen(),
send_callback_->getDataSource().get(), *send_callback_);
// Set IO ready marker so sender activity is visible to select() or poll().
// Note, if this call throws it will manifest itself as a throw from
// from sendRequest() which the application calls directly and is documented
// as throwing exceptions; or caught inside invokeSendHandler() which
// will invoke the application's send_handler with an error status.
watch_socket_->markReady();
}
void
NameChangeUDPSender::sendCompletionHandler(const bool successful,
const UDPCallback *send_callback) {
// Clear the IO ready marker.
try {
watch_socket_->clearReady();
} catch (const std::exception& ex) {
// This can only happen if the WatchSocket's select_fd has been
// compromised which is a programmatic error. We'll log the error
// here, then continue on and process the IO result we were given.
// WatchSocket issue will resurface on the next send as a closed
// fd in markReady(). This allows application's handler to deal
// with watch errors more uniformly.
LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR)
.arg(ex.what());
}
Result result;
if (successful) {
result = SUCCESS;
......@@ -323,5 +348,17 @@ NameChangeUDPSender::sendCompletionHandler(const bool successful,
// Call the application's registered request send handler.
invokeSendHandler(result);
}
int
NameChangeUDPSender::getSelectFd() {
if (!amSending()) {
isc_throw(NotImplemented, "NameChangeUDPSender::getSelectFd"
" not in send mode");
}
return(watch_socket_->getSelectFd());
}
}; // end of isc::dhcp_ddns namespace
}; // end of isc namespace
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -112,10 +112,12 @@
#include <asiolink/udp_endpoint.h>
#include <asiolink/udp_socket.h>
#include <dhcp_ddns/ncr_io.h>
#include <dhcp_ddns/watch_socket.h>
#include <util/buffer.h>
#include <boost/shared_array.hpp>
/// responsibility of the completion handler to perform the steps necessary
/// to interpret the raw data provided by the service outcome. The
/// UDPCallback operator implementation is mostly a pass through.
......@@ -502,6 +504,7 @@ public:
/// asyncSend() method is called, passing in send_callback_ member's
/// transfer buffer as the send buffer and the send_callback_ itself
/// as the callback object.
/// @param ncr NameChangeRequest to send.
virtual void doSend(NameChangeRequestPtr& ncr);
/// @brief Implements the NameChangeRequest level send completion handler.
......@@ -524,6 +527,21 @@ public:
void sendCompletionHandler(const bool successful,
const UDPCallback* send_callback);
/// @brief Returns a file descriptor suitable for use with select
///
/// The value returned is an open file descriptor which can be used with
/// select() system call to monitor the sender for IO events. This allows
/// NameChangeUDPSenders to be used in applications which use select,
/// rather than IOService to wait for IO events to occur.
///
/// @warning Attempting other use of this value may lead to unpredictable
/// behavior in the sender.
///
/// @return Returns an "open" file descriptor
///
/// @throw NcrSenderError if the sender is not in send mode,
virtual int getSelectFd();
private:
/// @brief IP address from which to send.
isc::asiolink::IOAddress ip_address_;
......@@ -554,6 +572,9 @@ private:
/// @brief Flag which enables the reuse address socket option if true.
bool reuse_address_;
/// @brief Pointer to WatchSocket instance supplying the "select-fd".
WatchSocketPtr watch_socket_;
};
} // namespace isc::dhcp_ddns
......
......@@ -29,6 +29,8 @@ TESTS += libdhcp_ddns_unittests
libdhcp_ddns_unittests_SOURCES = run_unittests.cc
libdhcp_ddns_unittests_SOURCES += ncr_unittests.cc
libdhcp_ddns_unittests_SOURCES += ncr_udp_unittests.cc
libdhcp_ddns_unittests_SOURCES += test_utils.cc test_utils.h
libdhcp_ddns_unittests_SOURCES += watch_socket_unittests.cc
libdhcp_ddns_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
......
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -16,6 +16,7 @@
#include <dhcp_ddns/ncr_io.h>
#include <dhcp_ddns/ncr_udp.h>
#include <util/time_utilities.h>
#include <test_utils.h>
#include <asio/ip/udp.hpp>
#include <boost/function.hpp>
......@@ -23,6 +24,8 @@
#include <gtest/gtest.h>
#include <algorithm>
#include <sys/select.h>
using namespace std;
using namespace isc;
using namespace isc::dhcp_ddns;
......@@ -113,6 +116,7 @@ TEST(NameChangeUDPListenerBasicTest, basicListenTests) {
// 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.
......@@ -268,9 +272,20 @@ TEST_F(NameChangeUDPListenerTest, basicReceivetest) {
/// @brief A NOP derivation for constructor test purposes.
class SimpleSendHandler : public NameChangeSender::RequestSendHandler {
public:
virtual void operator ()(const NameChangeSender::Result,
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 Tests the NameChangeUDPSender constructors.
......@@ -311,7 +326,6 @@ TEST(NameChangeUDPSenderBasicTest, constructionTests) {
/// This test verifies that:
TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
uint32_t port = SENDER_PORT;
isc::asiolink::IOService io_service;
SimpleSendHandler ncr_handler;
......@@ -320,8 +334,9 @@ TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
// Create the sender, setting the queue max equal to the number of
// messages we will have in the list.
NameChangeUDPSender sender(ip_address, port, ip_address, port,
FMT_JSON, ncr_handler, num_msgs);
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));
......@@ -341,30 +356,55 @@ TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
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(dhcp_ddns::WatchSocket::INVALID_SOCKET, select_fd);
ASSERT_EQ(0, selectCheck(select_fd));
// 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 and invoke IOService::run_one.
// This should send exactly one message and the queue count should
// decrement accordingly.
// 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--) {
// Verify that sender shows IO ready.
ASSERT_TRUE(selectCheck(select_fd) > 0);
// Execute at one ready handler.
io_service.run_one();
// Verify that the queue count decrements in step with each run.
EXPECT_EQ(i-1, sender.getQueueSize());
}
// Verify that sender shows no IO ready.
EXPECT_EQ(0, selectCheck(select_fd));
// Verify that the queue is empty.
EXPECT_EQ(0, sender.getQueueSize());
......@@ -395,6 +435,111 @@ TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
EXPECT_EQ(0, sender.getQueueSize());
}
/// @brief Tests NameChangeUDPSender basic send with INADDR_ANY and port 0.
TEST(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());
// Fetch the sender's select-fd.
int select_fd = sender.getSelectFd();
// 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());
// message and the queue count should decrement accordingly.
// Execute at one ready handler.
ASSERT_TRUE(selectCheck(select_fd) > 0);
ASSERT_NO_THROW(io_service.run_one());
// Verify that sender shows no IO ready.
// and that the queue is empty.
EXPECT_EQ(0, selectCheck(select_fd));
EXPECT_EQ(0, sender.getQueueSize());
}
/// @brief Test the NameChangeSender::assumeQueue method.
TEST(NameChangeSender, 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());
// 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.
......@@ -422,9 +567,9 @@ public:
*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));
sender_.reset(
new NameChangeUDPSender(addr, SENDER_PORT, addr, LISTENER_PORT,