Browse Source

Merge branch 'trac3087'

Thomas Markwalder 11 years ago
parent
commit
8f99da735a

+ 2 - 0
src/bin/d2/Makefile.am

@@ -50,6 +50,7 @@ BUILT_SOURCES = spec_config.h d2_messages.h d2_messages.cc
 pkglibexec_PROGRAMS = b10-dhcp-ddns
 pkglibexec_PROGRAMS = b10-dhcp-ddns
 
 
 b10_dhcp_ddns_SOURCES  = main.cc
 b10_dhcp_ddns_SOURCES  = main.cc
+b10_dhcp_ddns_SOURCES += d2_asio.h
 b10_dhcp_ddns_SOURCES += d2_log.cc d2_log.h
 b10_dhcp_ddns_SOURCES += d2_log.cc d2_log.h
 b10_dhcp_ddns_SOURCES += d2_process.cc d2_process.h
 b10_dhcp_ddns_SOURCES += d2_process.cc d2_process.h
 b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
 b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
@@ -63,6 +64,7 @@ b10_dhcp_ddns_SOURCES += d2_update_mgr.cc d2_update_mgr.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
 b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
 b10_dhcp_ddns_SOURCES += labeled_value.cc labeled_value.h
 b10_dhcp_ddns_SOURCES += labeled_value.cc labeled_value.h
+b10_dhcp_ddns_SOURCES += nc_add.cc nc_add.h
 b10_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
 b10_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
 b10_dhcp_ddns_SOURCES += state_model.cc state_model.h
 b10_dhcp_ddns_SOURCES += state_model.cc state_model.h
 
 

+ 31 - 0
src/bin/d2/d2_asio.h

@@ -0,0 +1,31 @@
+// Copyright (C) 2013  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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_ASIO_H
+#define D2_ASIO_H
+
+#include <asiolink/asiolink.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines a smart pointer to an IOService instance.
+typedef boost::shared_ptr<isc::asiolink::IOService> IOServicePtr;
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif

+ 1 - 1
src/bin/d2/d2_cfg_mgr.h

@@ -15,9 +15,9 @@
 #ifndef D2_CFG_MGR_H
 #ifndef D2_CFG_MGR_H
 #define D2_CFG_MGR_H
 #define D2_CFG_MGR_H
 
 
-#include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <cc/data.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
+#include <d2/d2_asio.h>
 #include <d2/d_cfg_mgr.h>
 #include <d2/d_cfg_mgr.h>
 #include <d2/d2_config.h>
 #include <d2/d2_config.h>
 
 

+ 1 - 1
src/bin/d2/d2_config.h

@@ -15,8 +15,8 @@
 #ifndef D2_CONFIG_H
 #ifndef D2_CONFIG_H
 #define D2_CONFIG_H
 #define D2_CONFIG_H
 
 
-#include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <cc/data.h>
+#include <d2/d2_asio.h>
 #include <d2/d_cfg_mgr.h>
 #include <d2/d_cfg_mgr.h>
 #include <dhcpsrv/dhcp_parsers.h>
 #include <dhcpsrv/dhcp_parsers.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>

+ 67 - 1
src/bin/d2/d2_messages.mes

@@ -190,7 +190,7 @@ indicate a network connectivity or system resource issue.
 
 
 % DHCP_DDNS_QUEUE_MGR_RESUME_ERROR application could not restart the queue manager, reason: %1
 % DHCP_DDNS_QUEUE_MGR_RESUME_ERROR application could not restart the queue manager, reason: %1
 This is an error message indicating that DHCP_DDNS's Queue Manager could not
 This is an error message indicating that DHCP_DDNS's Queue Manager could not
-be restarted after stopping due to an a full receive queue.  This means that
+be restarted after stopping due to a full receive queue.  This means that
 the application cannot receive requests. This is most likely due to DHCP_DDNS
 the application cannot receive requests. This is most likely due to DHCP_DDNS
 configuration parameters referring to resources such as an IP address or port,
 configuration parameters referring to resources such as an IP address or port,
 that is no longer unavailable.  DHCP_DDNS will attempt to restart the queue
 that is no longer unavailable.  DHCP_DDNS will attempt to restart the queue
@@ -258,3 +258,69 @@ This is error message issued when the application fails to process a
 NameChangeRequest correctly. Some or all of the DNS updates requested as part
 NameChangeRequest correctly. Some or all of the DNS updates requested as part
 of this update did not succeed. This is a programmatic error and should be
 of this update did not succeed. This is a programmatic error and should be
 reported.
 reported.
+
+% DHCP_DDNS_FORWARD_ADD_REJECTED DNS Server, %1, rejected a DNS update request to add the address mapping for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_ADD_IO_ERROR DHCP_DDNS encountered an IO error sending a forward mapping add for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward address update.  The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while adding forward address mapping for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to add a forward address mapping,  is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while adding a forward address mapping for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was adding a forward address mapping.  The request will be
+aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_REPLACE_REJECTED DNS Server, %1, rejected a DNS update request to replace the address mapping for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REPLACE_IO_ERROR DHCP_DDNS encountered an IO error sending a forward mapping replace for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward address update.  The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while replacing forward address mapping for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to replace a forward address mapping,  is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while replacing forward address mapping for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was replacing a forward address mapping.  The request will be
+aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_REVERSE_REPLACE_REJECTED DNS Server, %1, rejected a DNS update request to replace the reverse mapping for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_REVERSE_REPLACE_IO_ERROR DHCP_DDNS encountered an IO error sending a reverse mapping replacement for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a reverse address update.  The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while replacing reverse address mapping for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to replace a reverse address,  is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while replacing reverse address mapping for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was replacing a reverse address mapping.  The request will be
+aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_TRANS_SEND_ERROR application encountered an unexpected error while attempting to send a DNS update: %1
+This is error message issued when the application is able to construct an update
+message but the attempt to send it suffered a unexpected error. This is most
+likely a programmatic error, rather than a communications issue. Some or all
+of the DNS updates requested as part of this request did not succeed.

+ 2 - 2
src/bin/d2/d2_process.cc

@@ -35,13 +35,13 @@ D2Process::D2Process(const char* name, IOServicePtr io_service)
     // been received.  This means that until we receive the configuration,
     // been received.  This means that until we receive the configuration,
     // D2 will neither receive nor process NameChangeRequests.
     // D2 will neither receive nor process NameChangeRequests.
     // Pass in IOService for NCR IO event processing.
     // Pass in IOService for NCR IO event processing.
-    queue_mgr_.reset(new D2QueueMgr(*getIoService()));
+    queue_mgr_.reset(new D2QueueMgr(getIoService()));
 
 
     // Instantiate update manager.
     // Instantiate update manager.
     // Pass in both queue manager and configuration manager.
     // Pass in both queue manager and configuration manager.
     // Pass in IOService for DNS update transaction IO event processing.
     // Pass in IOService for DNS update transaction IO event processing.
     D2CfgMgrPtr tmp = getD2CfgMgr();
     D2CfgMgrPtr tmp = getD2CfgMgr();
-    update_mgr_.reset(new D2UpdateMgr(queue_mgr_,  tmp,  *getIoService()));
+    update_mgr_.reset(new D2UpdateMgr(queue_mgr_,  tmp,  getIoService()));
 };
 };
 
 
 void
 void

+ 6 - 3
src/bin/d2/d2_queue_mgr.cc

@@ -22,10 +22,13 @@ namespace d2 {
 // Makes constant visible to Google test macros.
 // Makes constant visible to Google test macros.
 const size_t D2QueueMgr::MAX_QUEUE_DEFAULT;
 const size_t D2QueueMgr::MAX_QUEUE_DEFAULT;
 
 
-D2QueueMgr::D2QueueMgr(isc::asiolink::IOService& io_service,
-                       const size_t max_queue_size)
+D2QueueMgr::D2QueueMgr(IOServicePtr& io_service, const size_t max_queue_size)
     : io_service_(io_service), max_queue_size_(max_queue_size),
     : io_service_(io_service), max_queue_size_(max_queue_size),
       mgr_state_(NOT_INITTED), target_stop_state_(NOT_INITTED) {
       mgr_state_(NOT_INITTED), target_stop_state_(NOT_INITTED) {
+    if (!io_service_) {
+        isc_throw(D2QueueMgrError, "IOServicePtr cannot be null");
+    }
+
     // Use setter to do validation.
     // Use setter to do validation.
     setMaxQueueSize(max_queue_size);
     setMaxQueueSize(max_queue_size);
 }
 }
@@ -129,7 +132,7 @@ D2QueueMgr::startListening() {
 
 
     // Instruct the listener to start listening and set state accordingly.
     // Instruct the listener to start listening and set state accordingly.
     try {
     try {
-        listener_->startListening(io_service_);
+        listener_->startListening(*io_service_);
         mgr_state_ = RUNNING;
         mgr_state_ = RUNNING;
     } catch (const isc::Exception& ex) {
     } catch (const isc::Exception& ex) {
         isc_throw(D2QueueMgrError, "D2QueueMgr listener start failed: "
         isc_throw(D2QueueMgrError, "D2QueueMgr listener start failed: "

+ 3 - 4
src/bin/d2/d2_queue_mgr.h

@@ -17,9 +17,8 @@
 
 
 /// @file d2_queue_mgr.h This file defines the class D2QueueMgr.
 /// @file d2_queue_mgr.h This file defines the class D2QueueMgr.
 
 
-#include <asiolink/io_address.h>
-#include <asiolink/io_service.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
+#include <d2/d2_asio.h>
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcp_ddns/ncr_io.h>
 #include <dhcp_ddns/ncr_io.h>
 
 
@@ -166,7 +165,7 @@ public:
     /// This value must be greater than zero. It defaults to MAX_QUEUE_DEFAULT.
     /// This value must be greater than zero. It defaults to MAX_QUEUE_DEFAULT.
     ///
     ///
     /// @throw D2QueueMgrError if max_queue_size is zero.
     /// @throw D2QueueMgrError if max_queue_size is zero.
-    D2QueueMgr(isc::asiolink::IOService& io_service,
+    D2QueueMgr(IOServicePtr& io_service,
                const size_t max_queue_size = MAX_QUEUE_DEFAULT);
                const size_t max_queue_size = MAX_QUEUE_DEFAULT);
 
 
     /// @brief Destructor
     /// @brief Destructor
@@ -328,7 +327,7 @@ public:
     void updateStopState();
     void updateStopState();
 
 
     /// @brief IOService that our listener should use for IO management.
     /// @brief IOService that our listener should use for IO management.
-    isc::asiolink::IOService& io_service_;
+    IOServicePtr io_service_;
 
 
     /// @brief Dictates the maximum number of entries allowed in the queue.
     /// @brief Dictates the maximum number of entries allowed in the queue.
     size_t max_queue_size_;
     size_t max_queue_size_;

+ 5 - 1
src/bin/d2/d2_update_mgr.cc

@@ -24,7 +24,7 @@ namespace d2 {
 const size_t D2UpdateMgr::MAX_TRANSACTIONS_DEFAULT;
 const size_t D2UpdateMgr::MAX_TRANSACTIONS_DEFAULT;
 
 
 D2UpdateMgr::D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
 D2UpdateMgr::D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
-                         isc::asiolink::IOService& io_service,
+                         IOServicePtr& io_service,
                          const size_t max_transactions)
                          const size_t max_transactions)
     :queue_mgr_(queue_mgr), cfg_mgr_(cfg_mgr), io_service_(io_service) {
     :queue_mgr_(queue_mgr), cfg_mgr_(cfg_mgr), io_service_(io_service) {
     if (!queue_mgr_) {
     if (!queue_mgr_) {
@@ -36,6 +36,10 @@ D2UpdateMgr::D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
                   "D2UpdateMgr configuration manager cannot be null");
                   "D2UpdateMgr configuration manager cannot be null");
     }
     }
 
 
+    if (!io_service_) {
+        isc_throw(D2UpdateMgrError, "IOServicePtr cannot be null");
+    }
+
     // Use setter to do validation.
     // Use setter to do validation.
     setMaxTransactions(max_transactions);
     setMaxTransactions(max_transactions);
 }
 }

+ 3 - 3
src/bin/d2/d2_update_mgr.h

@@ -17,8 +17,8 @@
 
 
 /// @file d2_update_mgr.h This file defines the class D2UpdateMgr.
 /// @file d2_update_mgr.h This file defines the class D2UpdateMgr.
 
 
-#include <asiolink/io_service.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
+#include <d2/d2_asio.h>
 #include <d2/d2_log.h>
 #include <d2/d2_log.h>
 #include <d2/d2_queue_mgr.h>
 #include <d2/d2_queue_mgr.h>
 #include <d2/d2_cfg_mgr.h>
 #include <d2/d2_cfg_mgr.h>
@@ -100,7 +100,7 @@ public:
     /// @throw D2UpdateMgrError if either the queue manager or configuration
     /// @throw D2UpdateMgrError if either the queue manager or configuration
     /// managers are NULL, or max transactions is less than one.
     /// managers are NULL, or max transactions is less than one.
     D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
     D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
-                isc::asiolink::IOService& io_service,
+                IOServicePtr& io_service,
                 const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT);
                 const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT);
 
 
     /// @brief Destructor
     /// @brief Destructor
@@ -228,7 +228,7 @@ private:
     /// passed into transactions to manager their IO events.
     /// passed into transactions to manager their IO events.
     /// (For future reference, multi-threaded transactions would each use their
     /// (For future reference, multi-threaded transactions would each use their
     /// own IOService instance.)
     /// own IOService instance.)
-    isc::asiolink::IOService& io_service_;
+    IOServicePtr io_service_;
 
 
     /// @brief Maximum number of concurrent transactions.
     /// @brief Maximum number of concurrent transactions.
     size_t max_transactions_;
     size_t max_transactions_;

+ 1 - 1
src/bin/d2/d_controller.h

@@ -15,10 +15,10 @@
 #ifndef D_CONTROLLER_H
 #ifndef D_CONTROLLER_H
 #define D_CONTROLLER_H
 #define D_CONTROLLER_H
 
 
-#include <asiolink/asiolink.h>
 #include <cc/data.h>
 #include <cc/data.h>
 #include <cc/session.h>
 #include <cc/session.h>
 #include <config/ccsession.h>
 #include <config/ccsession.h>
+#include <d2/d2_asio.h>
 #include <d2/d2_log.h>
 #include <d2/d2_log.h>
 #include <d2/d_process.h>
 #include <d2/d_process.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>

+ 1 - 3
src/bin/d2/d_process.h

@@ -15,16 +15,14 @@
 #ifndef D_PROCESS_H
 #ifndef D_PROCESS_H
 #define D_PROCESS_H
 #define D_PROCESS_H
 
 
-#include <asiolink/asiolink.h>
 #include <cc/data.h>
 #include <cc/data.h>
+#include <d2/d2_asio.h>
 #include <d2/d_cfg_mgr.h>
 #include <d2/d_cfg_mgr.h>
 
 
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 
 
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
-typedef boost::shared_ptr<isc::asiolink::IOService> IOServicePtr;
-
 namespace isc {
 namespace isc {
 namespace d2 {
 namespace d2 {
 
 

+ 4 - 3
src/bin/d2/dns_client.cc

@@ -162,7 +162,7 @@ DNSClientImpl::getStatus(const asiodns::IOFetch::Result result) {
 }
 }
 
 
 void
 void
-DNSClientImpl::doUpdate(IOService& io_service,
+DNSClientImpl::doUpdate(asiolink::IOService& io_service,
                         const IOAddress& ns_addr,
                         const IOAddress& ns_addr,
                         const uint16_t ns_port,
                         const uint16_t ns_port,
                         D2UpdateMessage& update,
                         D2UpdateMessage& update,
@@ -191,6 +191,7 @@ DNSClientImpl::doUpdate(IOService& io_service,
     // caller that the unsigned timeout value will fit into int.
     // caller that the unsigned timeout value will fit into int.
     IOFetch io_fetch(IOFetch::UDP, io_service, msg_buf, ns_addr, ns_port,
     IOFetch io_fetch(IOFetch::UDP, io_service, msg_buf, ns_addr, ns_port,
                      in_buf_, this, static_cast<int>(wait));
                      in_buf_, this, static_cast<int>(wait));
+
     // Post the task to the task queue in the IO service. Caller will actually
     // Post the task to the task queue in the IO service. Caller will actually
     // run these tasks by executing IOService::run.
     // run these tasks by executing IOService::run.
     io_service.post(io_fetch);
     io_service.post(io_fetch);
@@ -213,7 +214,7 @@ DNSClient::getMaxTimeout() {
 }
 }
 
 
 void
 void
-DNSClient::doUpdate(IOService&,
+DNSClient::doUpdate(asiolink::IOService&,
                     const IOAddress&,
                     const IOAddress&,
                     const uint16_t,
                     const uint16_t,
                     D2UpdateMessage&,
                     D2UpdateMessage&,
@@ -224,7 +225,7 @@ DNSClient::doUpdate(IOService&,
 }
 }
 
 
 void
 void
-DNSClient::doUpdate(IOService& io_service,
+DNSClient::doUpdate(asiolink::IOService& io_service,
                     const IOAddress& ns_addr,
                     const IOAddress& ns_addr,
                     const uint16_t ns_port,
                     const uint16_t ns_port,
                     D2UpdateMessage& update,
                     D2UpdateMessage& update,

+ 539 - 0
src/bin/d2/nc_add.cc

@@ -0,0 +1,539 @@
+// Copyright (C) 2013 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_log.h>
+#include <d2/nc_add.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+
+namespace isc {
+namespace d2 {
+
+// NameAddTransaction states
+const int NameAddTransaction::ADDING_FWD_ADDRS_ST;
+const int NameAddTransaction::REPLACING_FWD_ADDRS_ST;
+const int NameAddTransaction::REPLACING_REV_PTRS_ST;
+
+// NameAddTransaction events
+const int NameAddTransaction::FQDN_IN_USE_EVT;
+const int NameAddTransaction::FQDN_NOT_IN_USE_EVT;
+
+NameAddTransaction::
+NameAddTransaction(IOServicePtr& io_service,
+                   dhcp_ddns::NameChangeRequestPtr& ncr,
+                   DdnsDomainPtr& forward_domain,
+                   DdnsDomainPtr& reverse_domain)
+    : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain) {
+    if (ncr->getChangeType() != isc::dhcp_ddns::CHG_ADD) {
+        isc_throw (NameAddTransactionError,
+                   "NameAddTransaction, request type must be CHG_ADD");
+    }
+}
+
+NameAddTransaction::~NameAddTransaction(){
+}
+
+void
+NameAddTransaction::defineEvents() {
+    // Call superclass impl first.
+    NameChangeTransaction::defineEvents();
+
+    // Define NCT events.
+    defineEvent(FQDN_IN_USE_EVT, "FQDN_IN_USE_EVT");
+    defineEvent(FQDN_NOT_IN_USE_EVT, "FQDN_NOT_IN_USE_EVT");
+}
+
+void
+NameAddTransaction::verifyEvents() {
+    // Call superclass impl first.
+    NameChangeTransaction::verifyEvents();
+
+    // Verify NCT events.
+    getEvent(FQDN_IN_USE_EVT);
+    getEvent(FQDN_NOT_IN_USE_EVT);
+}
+
+void
+NameAddTransaction::defineStates() {
+    // Call superclass impl first.
+    NameChangeTransaction::defineStates();
+
+    // Define the states.
+    defineState(READY_ST, "READY_ST",
+             boost::bind(&NameAddTransaction::readyHandler, this));
+
+    defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST",
+             boost::bind(&NameAddTransaction::selectingFwdServerHandler, this));
+
+    defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST",
+             boost::bind(&NameAddTransaction::selectingRevServerHandler, this));
+
+    defineState(ADDING_FWD_ADDRS_ST, "ADDING_FWD_ADDRS_ST",
+             boost::bind(&NameAddTransaction::addingFwdAddrsHandler, this));
+
+    defineState(REPLACING_FWD_ADDRS_ST, "REPLACING_FWD_ADDRS_ST",
+             boost::bind(&NameAddTransaction::replacingFwdAddrsHandler, this));
+
+    defineState(REPLACING_REV_PTRS_ST, "REPLACING_REV_PTRS_ST",
+             boost::bind(&NameAddTransaction::replacingRevPtrsHandler, this));
+
+    defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST",
+             boost::bind(&NameAddTransaction::processAddOkHandler, this));
+
+    defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST",
+             boost::bind(&NameAddTransaction::processAddFailedHandler, this));
+
+}
+void
+NameAddTransaction::verifyStates() {
+    // Call superclass impl first.
+    NameChangeTransaction::verifyStates();
+
+    // Verify NCT states. This ensures that derivations provide the handlers.
+    getState(ADDING_FWD_ADDRS_ST);
+    getState(REPLACING_FWD_ADDRS_ST);
+    getState(REPLACING_REV_PTRS_ST);
+}
+
+void
+NameAddTransaction::readyHandler() {
+    switch(getNextEvent()) {
+    case START_EVT:
+        if (getForwardDomain()) {
+            // Request includes a forward change, do that first.
+            transition(SELECTING_FWD_SERVER_ST, SELECT_SERVER_EVT);
+        } else {
+            // Reverse change only, transition accordingly.
+            transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+        }
+
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameAddTransaction::selectingFwdServerHandler() {
+    switch(getNextEvent()) {
+    case SELECT_SERVER_EVT:
+        // First time through for this transaction, so initialize server
+        // selection.
+        initServerSelection(getForwardDomain());
+        break;
+    case SERVER_IO_ERROR_EVT:
+        // We failed to communicate with current server. Attempt to select
+        // another one below.
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+
+    // Select the next server from the list of forward servers.
+    if (selectNextServer()) {
+        // We have a server to try.
+        transition(ADDING_FWD_ADDRS_ST, SERVER_SELECTED_EVT);
+    }
+    else {
+        // Server list is exhausted, so fail the transaction.
+        transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+    }
+}
+
+void
+NameAddTransaction::addingFwdAddrsHandler() {
+    if (doOnEntry()) {
+        // Clear the request on initial transition. This allows us to reuse
+        // the request on retries if necessary.
+        clearDnsUpdateRequest();
+    }
+
+    switch(getNextEvent()) {
+    case SERVER_SELECTED_EVT:
+        if (!getDnsUpdateRequest()) {
+            // Request hasn't been constructed yet, so build it.
+            buildAddFwdAddressRequest();
+        }
+
+        // Call sendUpdate() to initiate the async send. Note it also sets
+        // next event to NOP_EVT.
+        sendUpdate();
+        break;
+
+    case IO_COMPLETED_EVT: {
+        switch (getDnsUpdateStatus()) {
+        case DNSClient::SUCCESS: {
+            // We successfully received a response packet from the server.
+            const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+            if (rcode == dns::Rcode::NOERROR()) {
+                // We were able to add it. Mark it as done.
+                setForwardChangeCompleted(true);
+
+                // If request calls for reverse update then do that next,
+                // otherwise we can process ok.
+                if (getReverseDomain()) {
+                    transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+                } else {
+                    transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+                }
+            } else if (rcode == dns::Rcode::YXDOMAIN()) {
+                // FQDN is in use so we need to attempt to replace
+                // forward address.
+                transition(REPLACING_FWD_ADDRS_ST, FQDN_IN_USE_EVT);
+            } else {
+                // Per RFC4703 any other value means cease.
+                // If we get not authorized should we try the next server in
+                // the list? @todo  This needs some discussion perhaps.
+                LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_ADD_REJECTED)
+                          .arg(getCurrentServer()->getIpAddress())
+                          .arg(getNcr()->getFqdn())
+                          .arg(rcode.getCode());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            }
+
+            break;
+        }
+
+        case DNSClient::TIMEOUT:
+        case DNSClient::OTHER:
+            // We couldn't send to the current server, log it and set up
+            // to select the next server for a retry.
+            // @note For now we treat OTHER as an IO error like TIMEOUT. It
+            // is not entirely clear if this is accurate.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_ADD_IO_ERROR)
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            retryTransition(SELECTING_FWD_SERVER_ST);
+            break;
+
+        case DNSClient::INVALID_RESPONSE:
+            // A response was received but was corrupt. Retry it like an IO
+            // error.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT)
+                      .arg(getCurrentServer()->getIpAddress())
+                      .arg(getNcr()->getFqdn());
+
+            retryTransition(SELECTING_FWD_SERVER_ST);
+            break;
+
+        default:
+            // Any other value and we will fail this transaction, something
+            // bigger is wrong.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS)
+                      .arg(getDnsUpdateStatus())
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            break;
+        } // end switch on dns_status
+
+        break;
+    } // end case IO_COMPLETE_EVT
+
+    default:
+        // Event is invalid.
+        isc_throw(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameAddTransaction::replacingFwdAddrsHandler() {
+    if (doOnEntry()) {
+        // Clear the request on initial transition. This allows us to reuse
+        // the request on retries if necessary.
+        clearDnsUpdateRequest();
+    }
+
+    switch(getNextEvent()) {
+    case FQDN_IN_USE_EVT:
+    case SERVER_SELECTED_EVT:
+        if (!getDnsUpdateRequest()) {
+            // Request hasn't been constructed yet, so build it.
+            buildReplaceFwdAddressRequest();
+        }
+
+        // Call sendUpdate() to initiate the async send. Note it also sets
+        // next event to NOP_EVT.
+        sendUpdate();
+        break;
+
+    case IO_COMPLETED_EVT: {
+        switch (getDnsUpdateStatus()) {
+        case DNSClient::SUCCESS: {
+            // We successfully received a response packet from the server.
+            const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+            if (rcode == dns::Rcode::NOERROR()) {
+                // We were able to replace the forward mapping. Mark it as done.
+                setForwardChangeCompleted(true);
+
+                // If request calls for reverse update then do that next,
+                // otherwise we can process ok.
+                if (getReverseDomain()) {
+                    transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+                } else {
+                    transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+                }
+            } else if (rcode == dns::Rcode::NXDOMAIN()) {
+                // FQDN is NOT in use so go back and do the forward add address.
+                // Covers the case that it was there when we tried to add it,
+                // but has since been removed per RFC 4703.
+                transition(ADDING_FWD_ADDRS_ST, SERVER_SELECTED_EVT);
+            } else {
+                // Per RFC4703 any other value means cease.
+                // If we get not authorized should try the next server in
+                // the list? @todo  This needs some discussion perhaps.
+                LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_REPLACE_REJECTED)
+                          .arg(getCurrentServer()->getIpAddress())
+                          .arg(getNcr()->getFqdn())
+                          .arg(rcode.getCode());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            }
+
+            break;
+        }
+
+        case DNSClient::TIMEOUT:
+        case DNSClient::OTHER:
+            // We couldn't send to the current server, log it and set up
+            // to select the next server for a retry.
+            // @note For now we treat OTHER as an IO error like TIMEOUT. It
+            // is not entirely clear if this is accurate.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_REPLACE_IO_ERROR)
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            // If we are out of retries on this server, we go back and start
+            // all over on a new server.
+            retryTransition(SELECTING_FWD_SERVER_ST);
+            break;
+
+        case DNSClient::INVALID_RESPONSE:
+            // A response was received but was corrupt. Retry it like an IO
+            // error.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT)
+                      .arg(getCurrentServer()->getIpAddress())
+                      .arg(getNcr()->getFqdn());
+
+            // If we are out of retries on this server, we go back and start
+            // all over on a new server.
+            retryTransition(SELECTING_FWD_SERVER_ST);
+            break;
+
+        default:
+            // Any other value and we will fail this transaction, something
+            // bigger is wrong.
+            LOG_ERROR(dctl_logger,
+                      DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS)
+                      .arg(getDnsUpdateStatus())
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            break;
+        } // end switch on dns_status
+
+        break;
+    } // end case IO_COMPLETE_EVT
+
+    default:
+        // Event is invalid.
+        isc_throw(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameAddTransaction::selectingRevServerHandler() {
+    switch(getNextEvent()) {
+    case SELECT_SERVER_EVT:
+        // First time through for this transaction, so initialize server
+        // selection.
+        initServerSelection(getReverseDomain());
+        break;
+    case SERVER_IO_ERROR_EVT:
+        // We failed to communicate with current server. Attempt to select
+        // another one below.
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+
+    // Select the next server from the list of forward servers.
+    if (selectNextServer()) {
+        // We have a server to try.
+        transition(REPLACING_REV_PTRS_ST, SERVER_SELECTED_EVT);
+    }
+    else {
+        // Server list is exhausted, so fail the transaction.
+        transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+    }
+}
+
+
+void
+NameAddTransaction::replacingRevPtrsHandler() {
+    if (doOnEntry()) {
+        // Clear the request on initial transition. This allows us to reuse
+        // the request on retries if necessary.
+        clearDnsUpdateRequest();
+    }
+
+    switch(getNextEvent()) {
+    case SERVER_SELECTED_EVT:
+        if (!getDnsUpdateRequest()) {
+            // Request hasn't been constructed yet, so build it.
+            buildReplaceRevPtrsRequest();
+        }
+
+        // Call sendUpdate() to initiate the async send. Note it also sets
+        // next event to NOP_EVT.
+        sendUpdate();
+        break;
+
+    case IO_COMPLETED_EVT: {
+        switch (getDnsUpdateStatus()) {
+        case DNSClient::SUCCESS: {
+            // We successfully received a response packet from the server.
+            const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+            if (rcode == dns::Rcode::NOERROR()) {
+                // We were able to update the reverse mapping. Mark it as done.
+                setReverseChangeCompleted(true);
+                transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+            } else {
+                // Per RFC4703 any other value means cease.
+                // If we get not authorized should try the next server in
+                // the list? @todo  This needs some discussion perhaps.
+                LOG_ERROR(dctl_logger, DHCP_DDNS_REVERSE_REPLACE_REJECTED)
+                          .arg(getCurrentServer()->getIpAddress())
+                          .arg(getNcr()->getFqdn())
+                          .arg(rcode.getCode());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            }
+
+            break;
+        }
+
+        case DNSClient::TIMEOUT:
+        case DNSClient::OTHER:
+            // We couldn't send to the current server, log it and set up
+            // to select the next server for a retry.
+            // @note For now we treat OTHER as an IO error like TIMEOUT. It
+            // is not entirely clear if this is accurate.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_REVERSE_REPLACE_IO_ERROR)
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            // If we are out of retries on this server, we go back and start
+            // all over on a new server.
+            retryTransition(SELECTING_REV_SERVER_ST);
+            break;
+
+        case DNSClient::INVALID_RESPONSE:
+            // A response was received but was corrupt. Retry it like an IO
+            // error.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT)
+                      .arg(getCurrentServer()->getIpAddress())
+                      .arg(getNcr()->getFqdn());
+
+            // If we are out of retries on this server, we go back and start
+            // all over on a new server.
+            retryTransition(SELECTING_REV_SERVER_ST);
+            break;
+
+        default:
+            // Any other value and we will fail this transaction, something
+            // bigger is wrong.
+            LOG_ERROR(dctl_logger,
+                      DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS)
+                      .arg(getDnsUpdateStatus())
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            break;
+        } // end switch on dns_status
+
+        break;
+    } // end case IO_COMPLETE_EVT
+
+    default:
+        // Event is invalid.
+        isc_throw(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameAddTransaction::processAddOkHandler() {
+    switch(getNextEvent()) {
+    case UPDATE_OK_EVT:
+        // @todo do we need a log statement here?
+        setNcrStatus(dhcp_ddns::ST_COMPLETED);
+        endModel();
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameAddTransaction::processAddFailedHandler() {
+    switch(getNextEvent()) {
+    case UPDATE_FAILED_EVT:
+        // @todo do we need a log statement here?
+        setNcrStatus(dhcp_ddns::ST_FAILED);
+        endModel();
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameAddTransaction::buildAddFwdAddressRequest() {
+    // @todo For now construct a blank outbound message.
+    D2UpdateMessagePtr msg(new D2UpdateMessage(D2UpdateMessage::OUTBOUND));
+    setDnsUpdateRequest(msg);
+}
+
+void
+NameAddTransaction::buildReplaceFwdAddressRequest() {
+    // @todo For now construct a blank outbound message.
+    D2UpdateMessagePtr msg(new D2UpdateMessage(D2UpdateMessage::OUTBOUND));
+    setDnsUpdateRequest(msg);
+}
+
+void
+NameAddTransaction::buildReplaceRevPtrsRequest() {
+    // @todo For now construct a blank outbound message.
+    D2UpdateMessagePtr msg(new D2UpdateMessage(D2UpdateMessage::OUTBOUND));
+    setDnsUpdateRequest(msg);
+}
+
+} // namespace isc::d2
+} // namespace isc

+ 409 - 0
src/bin/d2/nc_add.h

@@ -0,0 +1,409 @@
+// Copyright (C) 2013 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef NC_ADD_H
+#define NC_ADD_H
+
+/// @file nc_add.h This file defines the class NameAddTransaction.
+
+#include <d2/nc_trans.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the NameAddTransaction encounters a general error.
+class NameAddTransactionError : public isc::Exception {
+public:
+    NameAddTransactionError(const char* file, size_t line,
+                               const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Embodies the "life-cycle" required to carry out a DDNS Add update.
+///
+/// NameAddTransaction implements a state machine for adding (or replacing) a
+/// forward DNS mapping. This state machine is based upon the processing logic
+/// described in RFC 4703, Sections 5.3 and 5.4.  That logic may be paraphrased
+/// as follows:
+/// @code
+///
+/// If the request includes a forward change:
+///     Select a forward server
+///     Send the server a request to add the forward entry
+///     If the server responds with already in use:
+///         Send a server a request to delete and then add forward entry
+///
+///     If the forward update is unsuccessful:
+///         abandon the update
+///
+/// If the request includes a reverse change:
+///     Select a reverse server
+///     Send a server a request to delete and then add reverse entry
+///
+/// @endcode
+///
+/// This class derives from NameChangeTransaction from which it inherits
+/// states, events, and methods common to NameChangeRequest processing.
+class NameAddTransaction : public NameChangeTransaction {
+public:
+
+    //@{  Additional states needed for NameAdd state model.
+    /// @brief State that attempts to add forward address records.
+    static const int ADDING_FWD_ADDRS_ST = NCT_DERIVED_STATE_MIN + 1;
+
+    /// @brief State that attempts to replace forward address records.
+    static const int REPLACING_FWD_ADDRS_ST = NCT_DERIVED_STATE_MIN + 2;
+
+    /// @brief State that attempts to replace reverse PTR records
+    static const int REPLACING_REV_PTRS_ST = NCT_DERIVED_STATE_MIN + 3;
+    //@}
+
+    //@{ Additional events needed for NameAdd state model.
+    /// @brief Event sent when an add attempt fails with address in use.
+    static const int FQDN_IN_USE_EVT = NCT_DERIVED_EVENT_MIN + 1;
+
+    /// @brief Event sent when replace attempt to fails with address not in use.
+    static const int FQDN_NOT_IN_USE_EVT = NCT_DERIVED_EVENT_MIN + 2;
+    //@}
+
+    /// @brief Constructor
+    ///
+    /// Instantiates an Add transaction that is ready to be started.
+    ///
+    /// @param io_service IO service to be used for IO processing
+    /// @param ncr is the NameChangeRequest to fulfill
+    /// @param forward_domain is the domain to use for forward DNS updates
+    /// @param reverse_domain is the domain to use for reverse DNS updates
+    ///
+    /// @throw NameAddTransaction error if given request is not a CHG_ADD,
+    /// NameChangeTransaction error for base class construction errors.
+    NameAddTransaction(IOServicePtr& io_service,
+                       dhcp_ddns::NameChangeRequestPtr& ncr,
+                       DdnsDomainPtr& forward_domain,
+                       DdnsDomainPtr& reverse_domain);
+
+    /// @brief Destructor
+    virtual ~NameAddTransaction();
+
+protected:
+    /// @brief Adds events defined by NameAddTransaction to the event set.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then defines the
+    /// events unique to NCR Add transaction processing.
+    ///
+    /// @throw StateModelError if an event definition is invalid or a duplicate.
+    virtual void defineEvents();
+
+    /// @brief Validates the contents of the set of events.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then verifies the
+    /// Add transaction's events.
+    ///
+    /// @throw StateModelError if an event value is undefined.
+    virtual void verifyEvents();
+
+    /// @brief Adds states defined by NameAddTransaction to the state set.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then defines the
+    /// states unique to NCR Add transaction processing.
+    ///
+    /// @throw StateModelError if an state definition is invalid or a duplicate.
+    virtual void defineStates();
+
+    /// @brief Validates the contents of the set of states.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then verifies the
+    /// Add transaction's states.
+    ///
+    /// @throw StateModelError if an event value is undefined.
+    virtual void verifyStates();
+
+    /// @brief State handler for READY_ST.
+    ///
+    /// Entered from:
+    /// - INIT_ST with next event of START_EVT
+    ///
+    /// The READY_ST is the state the model transitions into when the inherited
+    /// method, startTransaction() is invoked.  This handler, therefore, is the
+    /// entry point into the state model execution.h  Its primary task is to
+    /// determine whether to start with a forward DNS change or a reverse DNS
+    /// change.
+    ///
+    /// Transitions to:
+    /// - SELECTING_FWD_SERVER_ST with next event of SERVER_SELECT_ST if request
+    /// includes a forward change.
+    ///
+    /// - SELECTING_REV_SERVER_ST with next event of SERVER_SELECT_ST if request
+    /// includes only a reverse change.
+    ///
+    /// @throw NameAddTransactionError if upon entry next event is not
+    /// START_EVT.
+    void readyHandler();
+
+    /// @brief State handler for SELECTING_FWD_SERVER_ST.
+    ///
+    /// Entered from:
+    /// - READY_ST with next event of SELECT_SERVER_EVT
+    /// - ADDING_FWD_ADDRS_ST with next event of SERVER_IO_ERROR_EVT
+    /// - REPLACING_FWD_ADDRS_ST with next event of SERVER_IO_ERROR_EVT
+    ///
+    /// Selects the server to be used from the forward domain for the forward
+    /// DNS update.  If next event is SELECT_SERVER_EVT the handler initializes
+    /// the forward domain's server selection mechanism and then attempts to
+    /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+    /// handler simply attempts to select the next server.
+    ///
+    /// Transitions to:
+    /// - ADDING_REV_PTRS_ST with next event of SERVER_SELECTED upon successful
+    /// server selection
+    ///
+    /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+    /// failure to select a server
+    ///
+    /// @throw NameAddTransactionError if upon entry next event is not
+    /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+    void selectingFwdServerHandler();
+
+    /// @brief State handler for SELECTING_REV_SERVER_ST.
+    ///
+    /// Entered from:
+    /// - READY_ST with next event of SELECT_SERVER_EVT
+    /// - ADDING_FWD_ADDRS_ST with next event of SELECT_SERVER_EVT
+    /// - REPLACING_FWD_ADDRS_ST with next event of SELECT_SERVER_EVT
+    /// - REPLACING_REV_PTRS_ST with next event of SERVER_IO_ERROR_EVT
+    ///
+    /// Selects the server to be used from the reverse domain for the reverse
+    /// DNS update.  If next event is SELECT_SERVER_EVT the handler initializes
+    /// the reverse domain's server selection mechanism and then attempts to
+    /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+    /// handler simply attempts to select the next server.
+    ///
+    /// Transitions to:
+    /// - ADDING_REV_PTRS_ST with next event of SERVER_SELECTED upon successful
+    /// server selection
+    ///
+    /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+    /// failure to select a server
+    ///
+    /// @throw NameAddTransactionError if upon entry next event is not
+    /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+    void selectingRevServerHandler();
+
+    /// @brief State handler for ADD_FWD_ADDRS_ST.
+    ///
+    /// Entered from:
+    /// - SELECTING_FWD_SERVER with next event of SERVER_SELECTED_EVT
+    /// - REPLACING_FWD_ADDRS_ST with next event of SERVER_SELECTED_EVT
+    ///
+    /// Attempts to add a forward DNS entry for a given FQDN.  If this is
+    /// first invocation of the handler after transitioning into this state,
+    /// any previous update request context is deleted.   If next event
+    /// is SERVER_SELECTED_EVT, the handler builds the forward add request,
+    /// schedules an asynchronous send via sendUpdate(), and returns.  Note
+    /// that sendUpdate will post NOP_EVT as next event.
+    ///
+    /// Posting the NOP_EVT will cause runModel() to suspend execution of
+    /// the state model thus affecting a "wait" for the update IO to complete.
+    /// Update completion occurs via the DNSClient callback operator() method
+    /// inherited from NameChangeTransaction.  When invoked this callback will
+    /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+    /// resumes execution of the state model.
+    ///
+    /// When the handler is invoked with a next event of IO_COMPELTED_EVT,
+    /// the DNS update status is checked and acted upon accordingly:
+    ///
+    /// Transitions to:
+    /// - SELECTING_REV_SERVER_ST with next event of SELECT_SERVER_EVT upon
+    /// successful addition and the request includes a reverse DNS update.
+    ///
+    /// - PROCESS_TRANS_OK_ST with next event of UPDATE_OK_EVT upon successful
+    /// addition and no reverse DNS update is required.
+    ///
+    /// - REPLACING_FWD_ADDRS_ST with next event of FQDN_IN_USE_EVT if the DNS
+    /// server response indicates that an entry for the given FQDN already
+    /// exists.
+    ///
+    /// - PROCESS_TRANS_FAILED_ST with next event of UPDATE_FAILED_EVT if the
+    /// DNS server rejected the update for any other reason or the IO completed
+    /// with an unrecognized status.
+    ///
+    /// - RE-ENTER this states with next event of SERVER_SELECTED_EVT_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has not been exhausted.
+    ///
+    /// - SELECTING_FWD_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has been exhausted.
+    ///
+    /// @throw NameAddTransactionError if upon entry next event is not
+    /// SERVER_SELECTED_EVT or IO_COMPLETE_EVT.
+    void addingFwdAddrsHandler();
+
+    /// @brief State handler for REPLACING_FWD_ADDRS_ST.
+    ///
+    /// Entered from:
+    /// - ADDING_FWD_ADDRS_ST with next event of FQDN_IN_USE_EVT
+    ///
+    /// Attempts to delete and then add a forward DNS entry for a given
+    /// FQDN.  If this is first invocation of the handler after transitioning
+    /// into this state, any previous update request context is deleted.   If
+    /// next event is FDQN_IN_USE_EVT or SERVER_SELECTED_EVT, the handler
+    /// builds the forward replacement request, schedules an asynchronous send
+    /// via sendUpdate(), and returns.  Note that sendUpdate will post NOP_EVT
+    /// as the next event.
+    ///
+    /// Posting the NOP_EVT will cause runModel() to suspend execution of
+    /// the state model thus affecting a "wait" for the update IO to complete.
+    /// Update completion occurs via the DNSClient callback operator() method
+    /// inherited from NameChangeTransaction.  When invoked this callback will
+    /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+    /// resumes execution of the state model.
+    ///
+    /// When the handler is invoked with a next event of IO_COMPELTED_EVT,
+    /// the DNS update status is checked and acted upon accordingly:
+    ///
+    /// Transitions to:
+    /// - SELECTING_REV_SERVER_ST with a next event of SELECT_SERVER_EVT upon
+    /// successful replacement and the request includes a reverse DNS update.
+    ///
+    /// - PROCESS_TRANS_OK_ST with next event of UPDATE_OK_EVT upon successful
+    /// replacement and the request does not include a reverse DNS update.
+    ///
+    /// - ADDING_FWD_ADDR_STR with a next event of SERVER_SELECTED_EVT  if the
+    /// DNS server response indicates that the FQDN is not in use.  This could
+    /// occur if a previous add attempt indicated the FQDN was in use, but
+    /// that entry has since been removed by another entity prior to this
+    /// replacement attempt.
+    ///
+    /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT if the
+    /// DNS server rejected the update for any other reason or the IO completed
+    /// with an unrecognized status.
+    ///
+    /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has not been exhausted.
+    ///
+    /// - SELECTING_FWD_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has been exhausted.
+    ///
+    /// @throw NameAddTransactionError if upon entry next event is not:
+    /// FQDN_IN_USE_EVT, SERVER_SELECTED_EVT or IO_COMPLETE_EVT.
+    void replacingFwdAddrsHandler();
+
+    /// @brief State handler for REPLACING_REV_PTRS_ST.
+    ///
+    /// Entered from:
+    /// - SELECTING_REV_SERVER_ST with a next event of SERVER_SELECTED_EVT
+    ///
+    /// Attempts to delete and then add a reverse DNS entry for a given FQDN.
+    /// If this is first invocation of the handler after transitioning into
+    /// this state, any previous update request context is deleted.  If next
+    /// event is SERVER_SELECTED_EVT, the handler builds the reverse replacement
+    /// add request, schedules an asynchronous send via sendUpdate(), and
+    /// returns.  Note that sendUpdate will post NOP_EVT as next event.
+    ///
+    /// Posting the NOP_EVT will cause runModel() to suspend execution of
+    /// the state model thus affecting a "wait" for the update IO to complete.
+    /// Update completion occurs via the DNSClient callback operator() method
+    /// inherited from NameChangeTransaction.  When invoked this callback will
+    /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+    /// resumes execution of the state model.
+    ///
+    /// When the handler is invoked with a next event of IO_COMPELTED_EVT,
+    /// the DNS update status is checked and acted upon accordingly:
+    ///
+    /// Transitions to:
+    /// - PROCESS_TRANS_OK_ST with a next event of UPDATE_OK_EVT upon
+    /// successful replacement.
+    ///
+    /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_OK_EVT If the
+    /// DNS server rejected the update for any reason or the IO completed
+    /// with an unrecognized status.
+    ///
+    /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has not been exhausted.
+    ///
+    /// - SELECTING_REV_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has been exhausted.
+    ///
+    /// @throw NameAddTransactionError if upon entry next event is not:
+    /// SERVER_SELECTED_EVT or IO_COMPLETED_EVT
+    void replacingRevPtrsHandler();
+
+    /// @brief State handler for PROCESS_TRANS_OK_ST.
+    ///
+    /// Entered from:
+    /// - ADDING_FWD_ADDRS_ST with a next event of UPDATE_OK_EVT
+    /// - REPLACING_FWD_ADDRS_ST with a next event of UPDATE_OK_EVT
+    /// - REPLACING_REV_PTRS_ST with a next event of UPDATE_OK_EVT
+    ///
+    /// Sets the transaction action status to indicate success and ends
+    /// model execution.
+    ///
+    /// Transitions to:
+    /// - END_ST with a next event of END_EVT.
+    ///
+    /// @throw NameAddTransactionError if upon entry next event is not:
+    /// UPDATE_OK_EVT
+    void processAddOkHandler();
+
+    /// @brief State handler for PROCESS_TRANS_FAILED_ST.
+    ///
+    /// Entered from:
+    /// - ADDING_FWD_ADDRS_ST with a next event of UPDATE_FAILED_EVT
+    /// - REPLACING_FWD_ADDRS_ST with a next event of UPDATE_FAILED_EVT
+    /// - REPLACING_REV_PTRS_ST with a next event of UPDATE_FAILED_EVT
+    ///
+    /// Sets the transaction status to indicate failure and ends
+    /// model execution.
+    ///
+    /// Transitions to:
+    /// - END_ST with a next event of FAIL_EVT.
+    ///
+    /// @throw NameAddTransactionError if upon entry next event is not:
+    /// UPDATE_FAILED_EVT
+    void processAddFailedHandler();
+
+    /// @brief Builds a DNS request to add an forward DNS entry for an FQDN
+    ///
+    /// @todo - Method not implemented yet
+    ///
+    /// @throw isc::NotImplemented
+    void buildAddFwdAddressRequest();
+
+    /// @brief Builds a DNS request to replace forward DNS entry for an FQDN
+    ///
+    /// @todo - Method not implemented yet
+    ///
+    /// @throw isc::NotImplemented
+    void buildReplaceFwdAddressRequest();
+
+    /// @brief Builds a DNS request to replace a reverse DNS entry for an FQDN
+    ///
+    /// @todo - Method not implemented yet
+    ///
+    /// @throw isc::NotImplemented
+    void buildReplaceRevPtrsRequest();
+
+};
+
+/// @brief Defines a pointer to a NameChangeTransaction.
+typedef boost::shared_ptr<NameAddTransaction> NameAddTransactionPtr;
+
+} // namespace isc::d2
+} // namespace isc
+#endif

+ 92 - 5
src/bin/d2/nc_trans.cc

@@ -38,18 +38,29 @@ const int NameChangeTransaction::UPDATE_FAILED_EVT;
 
 
 const int NameChangeTransaction::NCT_DERIVED_EVENT_MIN;
 const int NameChangeTransaction::NCT_DERIVED_EVENT_MIN;
 
 
+const unsigned int NameChangeTransaction::DNS_UPDATE_DEFAULT_TIMEOUT;
+const unsigned int NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+
 NameChangeTransaction::
 NameChangeTransaction::
-NameChangeTransaction(isc::asiolink::IOService& io_service,
+NameChangeTransaction(IOServicePtr& io_service,
                       dhcp_ddns::NameChangeRequestPtr& ncr,
                       dhcp_ddns::NameChangeRequestPtr& ncr,
                       DdnsDomainPtr& forward_domain,
                       DdnsDomainPtr& forward_domain,
                       DdnsDomainPtr& reverse_domain)
                       DdnsDomainPtr& reverse_domain)
     : io_service_(io_service), ncr_(ncr), forward_domain_(forward_domain),
     : io_service_(io_service), ncr_(ncr), forward_domain_(forward_domain),
-     reverse_domain_(reverse_domain), dns_client_(),
+     reverse_domain_(reverse_domain), dns_client_(), dns_update_request_(),
      dns_update_status_(DNSClient::OTHER), dns_update_response_(),
      dns_update_status_(DNSClient::OTHER), dns_update_response_(),
      forward_change_completed_(false), reverse_change_completed_(false),
      forward_change_completed_(false), reverse_change_completed_(false),
-     current_server_list_(), current_server_(), next_server_pos_(0) {
+     current_server_list_(), current_server_(), next_server_pos_(0),
+     update_attempts_(0) {
+    // @todo if io_service is NULL we are multi-threading and should
+    // instantiate our own
+    if (!io_service_) {
+        isc_throw(NameChangeTransactionError, "IOServicePtr cannot be null");
+    }
+
     if (!ncr_) {
     if (!ncr_) {
-        isc_throw(NameChangeTransactionError, "NameChangeRequest cannot null");
+        isc_throw(NameChangeTransactionError,
+                  "NameChangeRequest cannot be null");
     }
     }
 
 
     if (ncr_->isForwardChange() && !(forward_domain_)) {
     if (ncr_->isForwardChange() && !(forward_domain_)) {
@@ -82,6 +93,36 @@ NameChangeTransaction::operator()(DNSClient::Status status) {
 }
 }
 
 
 void
 void
+NameChangeTransaction::sendUpdate(bool /* use_tsig_ */) {
+    try {
+        ++update_attempts_;
+        // @todo add logic to add/replace TSIG key info in request if
+        // use_tsig_ is true. We should be able to navigate to the TSIG key
+        // for the current server.  If not we would need to add that.
+
+        // @todo time out should ultimately be configurable, down to
+        // server level?
+        dns_client_->doUpdate(*io_service_, current_server_->getIpAddress(),
+                              current_server_->getPort(), *dns_update_request_,
+                              DNS_UPDATE_DEFAULT_TIMEOUT);
+
+        // Message is on its way, so the next event should be NOP_EVT.
+        postNextEvent(NOP_EVT);
+    } catch (const std::exception& ex) {
+        // We were unable to initiate the send.
+        // It is presumed that any throw from doUpdate is due to a programmatic
+        // error, such as an unforeseen permutation of data, rather than an IO
+        // failure. IO errors should be caught by the underlying asiolink
+        // mechansisms and manifested as an unsuccessful IO statu in the
+        // DNSClient callback.  Any problem here most likely means the request
+        // is corrupt in some way and cannot be completed, therefore we will
+        // log it and transition it to failure.
+        LOG_ERROR(dctl_logger, DHCP_DDNS_TRANS_SEND_ERROR).arg(ex.what());
+        transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+    }
+}
+
+void
 NameChangeTransaction::defineEvents() {
 NameChangeTransaction::defineEvents() {
     // Call superclass impl first.
     // Call superclass impl first.
     StateModel::defineEvents();
     StateModel::defineEvents();
@@ -140,11 +181,43 @@ NameChangeTransaction::onModelFailure(const std::string& explanation) {
 }
 }
 
 
 void
 void
+NameChangeTransaction::retryTransition(const int server_sel_state) {
+    if (update_attempts_ < MAX_UPDATE_TRIES_PER_SERVER) {
+        // Re-enter the current state with same server selected.
+        transition(getCurrState(), SERVER_SELECTED_EVT);
+    } else  {
+        // Transition to given server selection state if we are out
+        // of retries.
+        transition(server_sel_state, SERVER_IO_ERROR_EVT);
+    }
+}
+
+void
+NameChangeTransaction::setDnsUpdateRequest(D2UpdateMessagePtr& request) {
+    dns_update_request_ = request;
+}
+
+void
+NameChangeTransaction::clearDnsUpdateRequest() {
+    dns_update_request_.reset();
+}
+
+void
 NameChangeTransaction::setDnsUpdateStatus(const DNSClient::Status& status) {
 NameChangeTransaction::setDnsUpdateStatus(const DNSClient::Status& status) {
     dns_update_status_ = status;
     dns_update_status_ = status;
 }
 }
 
 
 void
 void
+NameChangeTransaction::setDnsUpdateResponse(D2UpdateMessagePtr& response) {
+    dns_update_response_ = response;
+}
+
+void
+NameChangeTransaction::clearDnsUpdateResponse() {
+    dns_update_response_.reset();
+}
+
+void
 NameChangeTransaction::setForwardChangeCompleted(const bool value) {
 NameChangeTransaction::setForwardChangeCompleted(const bool value) {
     forward_change_completed_ = value;
     forward_change_completed_ = value;
 }
 }
@@ -154,6 +227,11 @@ NameChangeTransaction::setReverseChangeCompleted(const bool value) {
     reverse_change_completed_ = value;
     reverse_change_completed_ = value;
 }
 }
 
 
+void
+NameChangeTransaction::setUpdateAttempts(const size_t value) {
+    update_attempts_ = value;
+}
+
 const dhcp_ddns::NameChangeRequestPtr&
 const dhcp_ddns::NameChangeRequestPtr&
 NameChangeTransaction::getNcr() const {
 NameChangeTransaction::getNcr() const {
     return (ncr_);
     return (ncr_);
@@ -220,12 +298,16 @@ NameChangeTransaction::getCurrentServer() const {
     return (current_server_);
     return (current_server_);
 }
 }
 
 
-
 void
 void
 NameChangeTransaction::setNcrStatus(const dhcp_ddns::NameChangeStatus& status) {
 NameChangeTransaction::setNcrStatus(const dhcp_ddns::NameChangeStatus& status) {
     return (ncr_->setStatus(status));
     return (ncr_->setStatus(status));
 }
 }
 
 
+const D2UpdateMessagePtr&
+NameChangeTransaction::getDnsUpdateRequest() const {
+    return (dns_update_request_);
+}
+
 DNSClient::Status
 DNSClient::Status
 NameChangeTransaction::getDnsUpdateStatus() const {
 NameChangeTransaction::getDnsUpdateStatus() const {
     return (dns_update_status_);
     return (dns_update_status_);
@@ -246,5 +328,10 @@ NameChangeTransaction::getReverseChangeCompleted() const {
     return (reverse_change_completed_);
     return (reverse_change_completed_);
 }
 }
 
 
+size_t
+NameChangeTransaction::getUpdateAttempts() const {
+    return (update_attempts_);
+}
+
 } // namespace isc::d2
 } // namespace isc::d2
 } // namespace isc
 } // namespace isc

+ 79 - 3
src/bin/d2/nc_trans.h

@@ -17,8 +17,8 @@
 
 
 /// @file nc_trans.h This file defines the class NameChangeTransaction.
 /// @file nc_trans.h This file defines the class NameChangeTransaction.
 
 
-#include <asiolink/io_service.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
+#include <d2/d2_asio.h>
 #include <d2/d2_config.h>
 #include <d2/d2_config.h>
 #include <d2/dns_client.h>
 #include <d2/dns_client.h>
 #include <d2/state_model.h>
 #include <d2/state_model.h>
@@ -151,6 +151,12 @@ public:
     static const int NCT_DERIVED_EVENT_MIN = SM_DERIVED_EVENT_MIN + 101;
     static const int NCT_DERIVED_EVENT_MIN = SM_DERIVED_EVENT_MIN + 101;
     //@}
     //@}
 
 
+    /// @brief Defualt time to assign to a single DNS udpate.
+    static const unsigned int DNS_UPDATE_DEFAULT_TIMEOUT = 5 * 1000;
+
+    /// @brief Maximum times to attempt a single update on a given server.
+    static const unsigned int MAX_UPDATE_TRIES_PER_SERVER = 3;
+
     /// @brief Constructor
     /// @brief Constructor
     ///
     ///
     /// Instantiates a transaction that is ready to be started.
     /// Instantiates a transaction that is ready to be started.
@@ -163,7 +169,7 @@ public:
     /// @throw NameChangeTransactionError if given an null request,
     /// @throw NameChangeTransactionError if given an null request,
     /// if forward change is enabled but forward domain is null, if
     /// if forward change is enabled but forward domain is null, if
     /// reverse change is enabled but reverse domain is null.
     /// reverse change is enabled but reverse domain is null.
-    NameChangeTransaction(isc::asiolink::IOService& io_service,
+    NameChangeTransaction(IOServicePtr& io_service,
                           dhcp_ddns::NameChangeRequestPtr& ncr,
                           dhcp_ddns::NameChangeRequestPtr& ncr,
                           DdnsDomainPtr& forward_domain,
                           DdnsDomainPtr& forward_domain,
                           DdnsDomainPtr& reverse_domain);
                           DdnsDomainPtr& reverse_domain);
@@ -191,6 +197,20 @@ public:
     virtual void operator()(DNSClient::Status status);
     virtual void operator()(DNSClient::Status status);
 
 
 protected:
 protected:
+    /// @brief Send the update request to the current server.
+    ///
+    /// This method increments the update attempt count and then passes the
+    /// current update request to the DNSClient instance to be sent to the
+    /// currently selected server.  Since the send is asynchronous, the method
+    /// posts NOP_EVT as the next event and then returns.
+    ///
+    /// @param use_tsig True if the udpate should be include a TSIG key. This
+    /// is not yet implemented.
+    ///
+    /// If an exception occurs it will be logged and and the transaction will
+    /// be failed.
+    virtual void sendUpdate(bool use_tsig = false);
+
     /// @brief Adds events defined by NameChangeTransaction to the event set.
     /// @brief Adds events defined by NameChangeTransaction to the event set.
     ///
     ///
     /// This method adds the events common to NCR transaction processing to
     /// This method adds the events common to NCR transaction processing to
@@ -248,6 +268,28 @@ protected:
     /// @param explanation is text detailing the error
     /// @param explanation is text detailing the error
     virtual void onModelFailure(const std::string& explanation);
     virtual void onModelFailure(const std::string& explanation);
 
 
+    /// @brief Determines the state and next event based on update attempts.
+    ///
+    /// This method will post a next event of SERVER_SELECTED_EVT to the
+    /// current state if the number of udpate attempts has not reached the
+    /// maximum allowed.
+    ///
+    /// If the maximum number of attempts has been reached, it will transition
+    /// to the given state with a next event of SERVER_IO_ERROR_EVT.
+    ///
+    /// @param server_sel_state  State to transition to if maximum attempts
+    /// have been tried.
+    ///
+    void retryTransition(const int server_sel_state);
+
+    /// @brief Sets the update request packet to the given packet.
+    ///
+    /// @param request is the new request packet to assign.
+    void setDnsUpdateRequest(D2UpdateMessagePtr& request);
+
+    /// @brief Destroys the current update request packet.
+    void clearDnsUpdateRequest();
+
     /// @brief Sets the update status to the given status value.
     /// @brief Sets the update status to the given status value.
     ///
     ///
     /// @param status is the new value for the update status.
     /// @param status is the new value for the update status.
@@ -258,6 +300,9 @@ protected:
     /// @param response is the new response packet to assign.
     /// @param response is the new response packet to assign.
     void setDnsUpdateResponse(D2UpdateMessagePtr& response);
     void setDnsUpdateResponse(D2UpdateMessagePtr& response);
 
 
+    /// @brief Destroys the current update response packet.
+    void clearDnsUpdateResponse();
+
     /// @brief Sets the forward change completion flag to the given value.
     /// @brief Sets the forward change completion flag to the given value.
     ///
     ///
     /// @param value is the new value to assign to the flag.
     /// @param value is the new value to assign to the flag.
@@ -307,6 +352,19 @@ protected:
     /// @return A const pointer reference to the DNSClient
     /// @return A const pointer reference to the DNSClient
     const DNSClientPtr& getDNSClient() const;
     const DNSClientPtr& getDNSClient() const;
 
 
+    /// @brief Sets the update attempt count to the given value.
+    ///
+    /// @param value is the new value to assign.
+    void setUpdateAttempts(const size_t value);
+
+    /// @brief Fetches the IOService the transaction uses for IO processing.
+    ///
+    /// @return returns a const pointer to the IOService.
+    const IOServicePtr& getIOService() {
+        return (io_service_);
+    }
+
+
 public:
 public:
     /// @brief Fetches the NameChangeRequest for this transaction.
     /// @brief Fetches the NameChangeRequest for this transaction.
     ///
     ///
@@ -344,6 +402,12 @@ public:
     /// the request does not include a reverse change, the pointer will empty.
     /// the request does not include a reverse change, the pointer will empty.
     DdnsDomainPtr& getReverseDomain();
     DdnsDomainPtr& getReverseDomain();
 
 
+    /// @brief Fetches the current DNS update request packet.
+    ///
+    /// @return A const pointer reference to the current D2UpdateMessage
+    /// request.
+    const D2UpdateMessagePtr& getDnsUpdateRequest() const;
+
     /// @brief Fetches the most recent DNS update status.
     /// @brief Fetches the most recent DNS update status.
     ///
     ///
     /// @return A DNSClient::Status indicating the result of the most recent
     /// @return A DNSClient::Status indicating the result of the most recent
@@ -374,9 +438,15 @@ public:
     /// @return True if the reverse change has been completed, false otherwise.
     /// @return True if the reverse change has been completed, false otherwise.
     bool getReverseChangeCompleted() const;
     bool getReverseChangeCompleted() const;
 
 
+    /// @brief Fetches the update attempt count for the current update.
+    ///
+    /// @return size_t which is the number of times the current request has
+    /// been attempted against the current server.
+    size_t getUpdateAttempts() const;
+
 private:
 private:
     /// @brief The IOService which should be used to for IO processing.
     /// @brief The IOService which should be used to for IO processing.
-    isc::asiolink::IOService& io_service_;
+    IOServicePtr io_service_;
 
 
     /// @brief The NameChangeRequest that the transaction is to fulfill.
     /// @brief The NameChangeRequest that the transaction is to fulfill.
     dhcp_ddns::NameChangeRequestPtr ncr_;
     dhcp_ddns::NameChangeRequestPtr ncr_;
@@ -398,6 +468,9 @@ private:
     /// @brief The DNSClient instance that will carry out DNS packet exchanges.
     /// @brief The DNSClient instance that will carry out DNS packet exchanges.
     DNSClientPtr dns_client_;
     DNSClientPtr dns_client_;
 
 
+    /// @brief The DNS current update request packet.
+    D2UpdateMessagePtr dns_update_request_;
+
     /// @brief The outcome of the most recently completed DNS packet exchange.
     /// @brief The outcome of the most recently completed DNS packet exchange.
     DNSClient::Status dns_update_status_;
     DNSClient::Status dns_update_status_;
 
 
@@ -421,6 +494,9 @@ private:
     /// This value is always the position of the next selection in the server
     /// This value is always the position of the next selection in the server
     /// list, which may be beyond the end of the list.
     /// list, which may be beyond the end of the list.
     size_t next_server_pos_;
     size_t next_server_pos_;
+
+    /// @brief Number of transmit attempts for the current request.
+    size_t update_attempts_;
 };
 };
 
 
 /// @brief Defines a pointer to a NameChangeTransaction.
 /// @brief Defines a pointer to a NameChangeTransaction.

+ 25 - 18
src/bin/d2/state_model.cc

@@ -92,27 +92,13 @@ StateModel::~StateModel(){
 
 
 void
 void
 StateModel::startModel(const int start_state) {
 StateModel::startModel(const int start_state) {
-    // First let's build and verify the dictionary of events.
-    try {
-        defineEvents();
-        verifyEvents();
-    } catch (const std::exception& ex) {
-        isc_throw(StateModelError, "Event set is invalid: " << ex.what());
-    }
-
-    // Next let's build and verify the dictionary of states.
-    try {
-        defineStates();
-        verifyStates();
-    } catch (const std::exception& ex) {
-        isc_throw(StateModelError, "State set is invalid: " << ex.what());
-    }
-
-    // Record that we are good to go.
-    dictionaries_initted_ = true;
+    // Intialize dictionaries of events and states.
+    initDictionaries();
 
 
     // Set the current state to starting state and enter the run loop.
     // Set the current state to starting state and enter the run loop.
     setState(start_state);
     setState(start_state);
+
+    // Start running the model.
     runModel(START_EVT);
     runModel(START_EVT);
 }
 }
 
 
@@ -149,6 +135,27 @@ void
 StateModel::nopStateHandler() {
 StateModel::nopStateHandler() {
 }
 }
 
 
+void
+StateModel::initDictionaries() {
+    // First let's build and verify the dictionary of events.
+    try {
+        defineEvents();
+        verifyEvents();
+    } catch (const std::exception& ex) {
+        isc_throw(StateModelError, "Event set is invalid: " << ex.what());
+    }
+
+    // Next let's build and verify the dictionary of states.
+    try {
+        defineStates();
+        verifyStates();
+    } catch (const std::exception& ex) {
+        isc_throw(StateModelError, "State set is invalid: " << ex.what());
+    }
+
+    // Record that we are good to go.
+    dictionaries_initted_ = true;
+}
 
 
 void
 void
 StateModel::defineEvent(unsigned int event_value, const std::string& label) {
 StateModel::defineEvent(unsigned int event_value, const std::string& label) {

+ 12 - 5
src/bin/d2/state_model.h

@@ -17,7 +17,6 @@
 
 
 /// @file state_model.h This file defines the class StateModel.
 /// @file state_model.h This file defines the class StateModel.
 
 
-#include <asiolink/io_service.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <d2/d2_config.h>
 #include <d2/d2_config.h>
 #include <d2/dns_client.h>
 #include <d2/dns_client.h>
@@ -276,10 +275,9 @@ public:
 
 
     /// @brief Begins execution of the model.
     /// @brief Begins execution of the model.
     ///
     ///
-    /// This method invokes the define and verify methods for both events and
-    /// states to initialize their respective dictionaries. It then starts
-    /// the model execution setting the current state to the given start state,
-    /// and the event to START_EVT.
+    /// This method invokes initDictionaries method to initialize the event
+    /// and state dictionaries and then starts the model execution setting 
+    /// the current state to the given start state, and the event to START_EVT.
     ///
     ///
     /// @param start_state is the state in which to begin execution.
     /// @param start_state is the state in which to begin execution.
     ///
     ///
@@ -324,6 +322,15 @@ public:
     void nopStateHandler();
     void nopStateHandler();
 
 
 protected:
 protected:
+    /// @brief Initializes the event and state dictionaries.
+    ///
+    /// This method invokes the define and verify methods for both events and
+    /// states to initialize their respective dictionaries. 
+    ///
+    /// @throw StateModelError or others indirectly, as this method calls
+    /// dictionary define and verify methods.
+    void initDictionaries();
+
     /// @brief Populates the set of events.
     /// @brief Populates the set of events.
     ///
     ///
     /// This method is used to construct the set of valid events. Each class
     /// This method is used to construct the set of valid events. Each class

+ 4 - 1
src/bin/d2/tests/Makefile.am

@@ -51,7 +51,8 @@ if HAVE_GTEST
 
 
 TESTS += d2_unittests
 TESTS += d2_unittests
 
 
-d2_unittests_SOURCES = ../d2_log.h ../d2_log.cc
+d2_unittests_SOURCES = ../d2_asio.h
+d2_unittests_SOURCES += ../d2_log.h ../d2_log.cc
 d2_unittests_SOURCES += ../d_process.h
 d2_unittests_SOURCES += ../d_process.h
 d2_unittests_SOURCES += ../d_controller.cc ../d2_controller.h
 d2_unittests_SOURCES += ../d_controller.cc ../d2_controller.h
 d2_unittests_SOURCES += ../d2_process.cc ../d2_process.h
 d2_unittests_SOURCES += ../d2_process.cc ../d2_process.h
@@ -65,6 +66,7 @@ d2_unittests_SOURCES += ../d2_update_mgr.cc ../d2_update_mgr.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
 d2_unittests_SOURCES += ../labeled_value.cc ../labeled_value.h
 d2_unittests_SOURCES += ../labeled_value.cc ../labeled_value.h
+d2_unittests_SOURCES += ../nc_add.cc ../nc_add.h
 d2_unittests_SOURCES += ../nc_trans.cc ../nc_trans.h
 d2_unittests_SOURCES += ../nc_trans.cc ../nc_trans.h
 d2_unittests_SOURCES += ../state_model.cc ../state_model.h
 d2_unittests_SOURCES += ../state_model.cc ../state_model.h
 d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
 d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
@@ -80,6 +82,7 @@ d2_unittests_SOURCES += d2_update_mgr_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
 d2_unittests_SOURCES += labeled_value_unittests.cc
 d2_unittests_SOURCES += labeled_value_unittests.cc
+d2_unittests_SOURCES += nc_add_unittests.cc
 d2_unittests_SOURCES += nc_trans_unittests.cc
 d2_unittests_SOURCES += nc_trans_unittests.cc
 d2_unittests_SOURCES += state_model_unittests.cc
 d2_unittests_SOURCES += state_model_unittests.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc

+ 24 - 19
src/bin/d2/tests/d2_queue_mgr_unittests.cc

@@ -12,7 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
-#include <asiolink/interval_timer.h>
+#include <d2/d2_asio.h>
 #include <d2/d2_queue_mgr.h>
 #include <d2/d2_queue_mgr.h>
 #include <dhcp_ddns/ncr_udp.h>
 #include <dhcp_ddns/ncr_udp.h>
 #include <util/time_utilities.h>
 #include <util/time_utilities.h>
@@ -78,15 +78,19 @@ const long TEST_TIMEOUT = 5 * 1000;
 
 
 /// @brief Tests that construction with max queue size of zero is not allowed.
 /// @brief Tests that construction with max queue size of zero is not allowed.
 TEST(D2QueueMgrBasicTest, construction1) {
 TEST(D2QueueMgrBasicTest, construction1) {
-    isc::asiolink::IOService io_service;
+    IOServicePtr io_service;
 
 
+    // Verify that constructing with null IOServicePtr is not allowed.
+    EXPECT_THROW((D2QueueMgr(io_service)), D2QueueMgrError);
+
+    io_service.reset(new isc::asiolink::IOService());
     // Verify that constructing with max queue size of zero is not allowed.
     // Verify that constructing with max queue size of zero is not allowed.
     EXPECT_THROW(D2QueueMgr(io_service, 0), D2QueueMgrError);
     EXPECT_THROW(D2QueueMgr(io_service, 0), D2QueueMgrError);
 }
 }
 
 
 /// @brief Tests default construction works.
 /// @brief Tests default construction works.
 TEST(D2QueueMgrBasicTest, construction2) {
 TEST(D2QueueMgrBasicTest, construction2) {
-    isc::asiolink::IOService io_service;
+    IOServicePtr io_service(new isc::asiolink::IOService());
 
 
     // Verify that valid constructor works.
     // Verify that valid constructor works.
     D2QueueMgrPtr queue_mgr;
     D2QueueMgrPtr queue_mgr;
@@ -97,7 +101,7 @@ TEST(D2QueueMgrBasicTest, construction2) {
 
 
 /// @brief Tests construction with custom queue size works properly
 /// @brief Tests construction with custom queue size works properly
 TEST(D2QueueMgrBasicTest, construction3) {
 TEST(D2QueueMgrBasicTest, construction3) {
-    isc::asiolink::IOService io_service;
+    IOServicePtr io_service(new isc::asiolink::IOService());
 
 
     // Verify that custom queue size constructor works.
     // Verify that custom queue size constructor works.
     D2QueueMgrPtr queue_mgr;
     D2QueueMgrPtr queue_mgr;
@@ -114,7 +118,7 @@ TEST(D2QueueMgrBasicTest, construction3) {
 /// 4. Peek returns the first entry on the queue without altering queue content
 /// 4. Peek returns the first entry on the queue without altering queue content
 /// 5. Dequeue removes the first entry on the queue
 /// 5. Dequeue removes the first entry on the queue
 TEST(D2QueueMgrBasicTest, basicQueue) {
 TEST(D2QueueMgrBasicTest, basicQueue) {
-    isc::asiolink::IOService io_service;
+    IOServicePtr io_service(new isc::asiolink::IOService());
 
 
     // Construct the manager with max queue size set to number of messages
     // Construct the manager with max queue size set to number of messages
     // we'll use.
     // we'll use.
@@ -206,7 +210,7 @@ bool checkSendVsReceived(NameChangeRequestPtr sent_ncr,
 class QueueMgrUDPTest : public virtual ::testing::Test,
 class QueueMgrUDPTest : public virtual ::testing::Test,
                         NameChangeSender::RequestSendHandler {
                         NameChangeSender::RequestSendHandler {
 public:
 public:
-    isc::asiolink::IOService io_service_;
+    IOServicePtr io_service_;
     NameChangeSenderPtr   sender_;
     NameChangeSenderPtr   sender_;
     isc::asiolink::IntervalTimer test_timer_;
     isc::asiolink::IntervalTimer test_timer_;
     D2QueueMgrPtr queue_mgr_;
     D2QueueMgrPtr queue_mgr_;
@@ -215,7 +219,8 @@ public:
     std::vector<NameChangeRequestPtr> sent_ncrs_;
     std::vector<NameChangeRequestPtr> sent_ncrs_;
     std::vector<NameChangeRequestPtr> received_ncrs_;
     std::vector<NameChangeRequestPtr> received_ncrs_;
 
 
-    QueueMgrUDPTest() : io_service_(), test_timer_(io_service_) {
+    QueueMgrUDPTest() : io_service_(new isc::asiolink::IOService()),
+        test_timer_(*io_service_) {
         isc::asiolink::IOAddress addr(TEST_ADDRESS);
         isc::asiolink::IOAddress addr(TEST_ADDRESS);
         // Create our sender instance. Note that reuse_address is true.
         // Create our sender instance. Note that reuse_address is true.
         sender_.reset(new NameChangeUDPSender(addr, SENDER_PORT,
         sender_.reset(new NameChangeUDPSender(addr, SENDER_PORT,
@@ -245,7 +250,7 @@ public:
     ///
     ///
     /// This callback stops all running (hanging) tasks on IO service.
     /// This callback stops all running (hanging) tasks on IO service.
     void testTimeoutHandler() {
     void testTimeoutHandler() {
-        io_service_.stop();
+        io_service_->stop();
         FAIL() << "Test timeout hit.";
         FAIL() << "Test timeout hit.";
     }
     }
 };
 };
@@ -296,7 +301,7 @@ TEST_F (QueueMgrUDPTest, stateModel) {
 
 
     // Stopping requires IO cancel, which result in a callback.
     // Stopping requires IO cancel, which result in a callback.
     // So process one event and verify we are STOPPED.
     // So process one event and verify we are STOPPED.
-    io_service_.run_one();
+    io_service_->run_one();
     EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr_->getMgrState());
     EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr_->getMgrState());
 
 
     // Verify that we can re-enter the RUNNING from STOPPED by starting the
     // Verify that we can re-enter the RUNNING from STOPPED by starting the
@@ -313,7 +318,7 @@ TEST_F (QueueMgrUDPTest, stateModel) {
 
 
     // Stopping requires IO cancel, which result in a callback.
     // Stopping requires IO cancel, which result in a callback.
     // So process one event and verify we are STOPPED.
     // So process one event and verify we are STOPPED.
-    io_service_.run_one();
+    io_service_->run_one();
     EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr_->getMgrState());
     EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr_->getMgrState());
 
 
     // Verify that we can remove the listener in the STOPPED state and
     // Verify that we can remove the listener in the STOPPED state and
@@ -355,7 +360,7 @@ TEST_F (QueueMgrUDPTest, liveFeed) {
     ASSERT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
     ASSERT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
 
 
     // Place the sender into sending state.
     // Place the sender into sending state.
-    ASSERT_NO_THROW(sender_->startSending(io_service_));
+    ASSERT_NO_THROW(sender_->startSending(*io_service_));
     ASSERT_TRUE(sender_->amSending());
     ASSERT_TRUE(sender_->amSending());
 
 
     // Iterate over the list of requests sending and receiving
     // Iterate over the list of requests sending and receiving
@@ -366,8 +371,8 @@ TEST_F (QueueMgrUDPTest, liveFeed) {
         ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
         ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
 
 
         // running two should do the send then the receive
         // running two should do the send then the receive
-        io_service_.run_one();
-        io_service_.run_one();
+        io_service_->run_one();
+        io_service_->run_one();
 
 
         // Verify that the request can be added to the queue and queue
         // Verify that the request can be added to the queue and queue
         // size increments accordingly.
         // size increments accordingly.
@@ -390,8 +395,8 @@ TEST_F (QueueMgrUDPTest, liveFeed) {
         ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
         ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
 
 
         // running two should do the send then the receive
         // running two should do the send then the receive
-        EXPECT_NO_THROW(io_service_.run_one());
-        EXPECT_NO_THROW(io_service_.run_one());
+        EXPECT_NO_THROW(io_service_->run_one());
+        EXPECT_NO_THROW(io_service_->run_one());
         EXPECT_EQ(i+1, queue_mgr_->getQueueSize());
         EXPECT_EQ(i+1, queue_mgr_->getQueueSize());
     }
     }
 
 
@@ -400,11 +405,11 @@ TEST_F (QueueMgrUDPTest, liveFeed) {
 
 
     // Send another. The send should succeed.
     // Send another. The send should succeed.
     ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
     ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
-    EXPECT_NO_THROW(io_service_.run_one());
+    EXPECT_NO_THROW(io_service_->run_one());
 
 
     // Now execute the receive which should not throw but should move us
     // Now execute the receive which should not throw but should move us
     // to STOPPED_QUEUE_FULL state.
     // to STOPPED_QUEUE_FULL state.
-    EXPECT_NO_THROW(io_service_.run_one());
+    EXPECT_NO_THROW(io_service_->run_one());
     EXPECT_EQ(D2QueueMgr::STOPPED_QUEUE_FULL, queue_mgr_->getMgrState());
     EXPECT_EQ(D2QueueMgr::STOPPED_QUEUE_FULL, queue_mgr_->getMgrState());
 
 
     // Verify queue size did not increase beyond max.
     // Verify queue size did not increase beyond max.
@@ -430,10 +435,10 @@ TEST_F (QueueMgrUDPTest, liveFeed) {
     // Verify that we can again receive requests.
     // Verify that we can again receive requests.
     // Send should be fine.
     // Send should be fine.
     ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
     ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
-    EXPECT_NO_THROW(io_service_.run_one());
+    EXPECT_NO_THROW(io_service_->run_one());
 
 
     // Receive should succeed.
     // Receive should succeed.
-    EXPECT_NO_THROW(io_service_.run_one());
+    EXPECT_NO_THROW(io_service_->run_one());
     EXPECT_EQ(1, queue_mgr_->getQueueSize());
     EXPECT_EQ(1, queue_mgr_->getQueueSize());
 }
 }
 
 

+ 11 - 4
src/bin/d2/tests/d2_update_mgr_unittests.cc

@@ -12,7 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
-#include <asiolink/interval_timer.h>
+#include <d2/d2_asio.h>
 #include <d2/d2_update_mgr.h>
 #include <d2/d2_update_mgr.h>
 #include <util/time_utilities.h>
 #include <util/time_utilities.h>
 #include <d_test_stubs.h>
 #include <d_test_stubs.h>
@@ -41,7 +41,7 @@ public:
     ///
     ///
     /// Parameters match those needed by D2UpdateMgr.
     /// Parameters match those needed by D2UpdateMgr.
     D2UpdateMgrWrapper(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
     D2UpdateMgrWrapper(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
-                       isc::asiolink::IOService& io_service,
+                       IOServicePtr& io_service,
                        const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT)
                        const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT)
         : D2UpdateMgr(queue_mgr, cfg_mgr, io_service, max_transactions) {
         : D2UpdateMgr(queue_mgr, cfg_mgr, io_service, max_transactions) {
     }
     }
@@ -68,7 +68,7 @@ typedef boost::shared_ptr<D2UpdateMgrWrapper> D2UpdateMgrWrapperPtr;
 /// functions.
 /// functions.
 class D2UpdateMgrTest : public ConfigParseTest {
 class D2UpdateMgrTest : public ConfigParseTest {
 public:
 public:
-    isc::asiolink::IOService io_service_;
+    IOServicePtr io_service_;
     D2QueueMgrPtr queue_mgr_;
     D2QueueMgrPtr queue_mgr_;
     D2CfgMgrPtr cfg_mgr_;
     D2CfgMgrPtr cfg_mgr_;
     //D2UpdateMgrPtr update_mgr_;
     //D2UpdateMgrPtr update_mgr_;
@@ -77,6 +77,7 @@ public:
     size_t canned_count_;
     size_t canned_count_;
 
 
     D2UpdateMgrTest() {
     D2UpdateMgrTest() {
+        io_service_.reset(new isc::asiolink::IOService());
         queue_mgr_.reset(new D2QueueMgr(io_service_));
         queue_mgr_.reset(new D2QueueMgr(io_service_));
         cfg_mgr_.reset(new D2CfgMgr());
         cfg_mgr_.reset(new D2CfgMgr());
         update_mgr_.reset(new D2UpdateMgrWrapper(queue_mgr_, cfg_mgr_,
         update_mgr_.reset(new D2UpdateMgrWrapper(queue_mgr_, cfg_mgr_,
@@ -162,7 +163,7 @@ public:
 /// 4. Default construction works and max transactions is defaulted properly
 /// 4. Default construction works and max transactions is defaulted properly
 /// 5. Construction with custom max transactions works properly
 /// 5. Construction with custom max transactions works properly
 TEST(D2UpdateMgr, construction) {
 TEST(D2UpdateMgr, construction) {
-    isc::asiolink::IOService io_service;
+    IOServicePtr io_service(new isc::asiolink::IOService());
     D2QueueMgrPtr queue_mgr;
     D2QueueMgrPtr queue_mgr;
     D2CfgMgrPtr cfg_mgr;
     D2CfgMgrPtr cfg_mgr;
     D2UpdateMgrPtr update_mgr;
     D2UpdateMgrPtr update_mgr;
@@ -180,6 +181,12 @@ TEST(D2UpdateMgr, construction) {
 
 
     ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr()));
     ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr()));
 
 
+    // Verify that constructor fails with invalid io_service.
+    io_service.reset();
+    EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service),
+                 D2UpdateMgrError);
+    io_service.reset(new isc::asiolink::IOService());
+
     // Verify that max transactions cannot be zero.
     // Verify that max transactions cannot be zero.
     EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service, 0),
     EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service, 0),
                  D2UpdateMgrError);
                  D2UpdateMgrError);

+ 1 - 1
src/bin/d2/tests/d_test_stubs.h

@@ -15,11 +15,11 @@
 #ifndef D_TEST_STUBS_H
 #ifndef D_TEST_STUBS_H
 #define D_TEST_STUBS_H
 #define D_TEST_STUBS_H
 
 
-#include <asiolink/asiolink.h>
 #include <cc/data.h>
 #include <cc/data.h>
 #include <cc/session.h>
 #include <cc/session.h>
 #include <config/ccsession.h>
 #include <config/ccsession.h>
 
 
+#include <d2/d2_asio.h>
 #include <d2/d_controller.h>
 #include <d2/d_controller.h>
 #include <d2/d_cfg_mgr.h>
 #include <d2/d_cfg_mgr.h>
 
 

+ 1 - 1
src/bin/d2/tests/dns_client_unittests.cc

@@ -261,7 +261,7 @@ public:
 
 
         // Set the response wait time to 0 so as our test is not hanging. This
         // Set the response wait time to 0 so as our test is not hanging. This
         // should cause instant timeout.
         // should cause instant timeout.
-        const int timeout = 0;
+        const int timeout = 500;
         // The doUpdate() function starts asynchronous message exchange with DNS
         // The doUpdate() function starts asynchronous message exchange with DNS
         // server. When message exchange is done or timeout occurs, the
         // server. When message exchange is done or timeout occurs, the
         // completion callback will be triggered. The doUpdate function returns
         // completion callback will be triggered. The doUpdate function returns

File diff suppressed because it is too large
+ 1449 - 0
src/bin/d2/tests/nc_add_unittests.cc


+ 543 - 20
src/bin/d2/tests/nc_trans_unittests.cc

@@ -12,7 +12,18 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
+#include <asiolink/interval_timer.h>
 #include <d2/nc_trans.h>
 #include <d2/nc_trans.h>
+#include <dns/opcode.h>
+#include <dns/messagerenderer.h>
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <util/buffer.h>
+
+#include <asio/ip/udp.hpp>
+#include <asio/socket_base.hpp>
+#include <asio.hpp>
+#include <asio/error_code.hpp>
 
 
 #include <boost/function.hpp>
 #include <boost/function.hpp>
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
@@ -24,6 +35,8 @@ using namespace isc::d2;
 
 
 namespace {
 namespace {
 
 
+const size_t MAX_MSG_SIZE = 1024;
+
 /// @brief Test derivation of NameChangeTransaction for exercising state
 /// @brief Test derivation of NameChangeTransaction for exercising state
 /// model mechanics.
 /// model mechanics.
 ///
 ///
@@ -40,21 +53,57 @@ public:
     // NameChangeStub events
     // NameChangeStub events
     static const int SEND_UPDATE_EVT = NCT_DERIVED_EVENT_MIN + 2;
     static const int SEND_UPDATE_EVT = NCT_DERIVED_EVENT_MIN + 2;
 
 
+    bool use_stub_callback_;
+
     /// @brief Constructor
     /// @brief Constructor
     ///
     ///
     /// Parameters match those needed by NameChangeTransaction.
     /// Parameters match those needed by NameChangeTransaction.
-    NameChangeStub(isc::asiolink::IOService& io_service,
+    NameChangeStub(IOServicePtr& io_service,
                    dhcp_ddns::NameChangeRequestPtr& ncr,
                    dhcp_ddns::NameChangeRequestPtr& ncr,
                    DdnsDomainPtr forward_domain,
                    DdnsDomainPtr forward_domain,
                    DdnsDomainPtr reverse_domain)
                    DdnsDomainPtr reverse_domain)
         : NameChangeTransaction(io_service, ncr, forward_domain,
         : NameChangeTransaction(io_service, ncr, forward_domain,
-                                reverse_domain) {
+                                reverse_domain),
+                                use_stub_callback_(false) {
     }
     }
 
 
     /// @brief Destructor
     /// @brief Destructor
     virtual ~NameChangeStub() {
     virtual ~NameChangeStub() {
     }
     }
 
 
+    /// @brief DNSClient callback
+    /// Overrides the callback in NameChangeTranscation to allow testing
+    /// sendUpdate without incorporating exectution of the state model
+    /// into the test.
+    /// It sets the DNS status update and posts IO_COMPLETED_EVT as does
+    /// the normal callback, but rather than invoking runModel it stops
+    /// the IO service.  This allows tests to be constructed that consisted
+    /// of generating a DNS request and invoking sendUpdate to post it and
+    /// wait for response.
+    virtual void operator()(DNSClient::Status status) {
+        if (use_stub_callback_) {
+            setDnsUpdateStatus(status);
+            postNextEvent(IO_COMPLETED_EVT);
+            getIOService()->stop();
+        } else {
+            // For tests which need to use the real callback.
+            NameChangeTransaction::operator()(status);
+        }
+    }
+
+    /// @brief Some tests require a server to be selected.  This method
+    /// selects the first server in the forward domain without involving
+    /// state model execution to do so.
+    bool selectFwdServer() {
+        if (getForwardDomain()) {
+            initServerSelection(getForwardDomain());
+            selectNextServer();
+            return (getCurrentServer());
+        }
+
+        return (false);
+    }
+
     /// @brief Empty handler used to statisfy map verification.
     /// @brief Empty handler used to statisfy map verification.
     void dummyHandler() {
     void dummyHandler() {
         isc_throw(NameChangeTransactionError,
         isc_throw(NameChangeTransactionError,
@@ -182,23 +231,179 @@ public:
         // Invoke the base call implementation first.
         // Invoke the base call implementation first.
         NameChangeTransaction::verifyStates();
         NameChangeTransaction::verifyStates();
 
 
-        // Define our states.
+        // Check our states.
         getState(DOING_UPDATE_ST);
         getState(DOING_UPDATE_ST);
     }
     }
 
 
     // Expose the protected methods to be tested.
     // Expose the protected methods to be tested.
     using StateModel::runModel;
     using StateModel::runModel;
+    using StateModel::postNextEvent;
+    using StateModel::setState;
+    using StateModel::initDictionaries;
     using NameChangeTransaction::initServerSelection;
     using NameChangeTransaction::initServerSelection;
     using NameChangeTransaction::selectNextServer;
     using NameChangeTransaction::selectNextServer;
     using NameChangeTransaction::getCurrentServer;
     using NameChangeTransaction::getCurrentServer;
     using NameChangeTransaction::getDNSClient;
     using NameChangeTransaction::getDNSClient;
     using NameChangeTransaction::setNcrStatus;
     using NameChangeTransaction::setNcrStatus;
+    using NameChangeTransaction::setDnsUpdateRequest;
+    using NameChangeTransaction::clearDnsUpdateRequest;
     using NameChangeTransaction::setDnsUpdateStatus;
     using NameChangeTransaction::setDnsUpdateStatus;
     using NameChangeTransaction::getDnsUpdateResponse;
     using NameChangeTransaction::getDnsUpdateResponse;
+    using NameChangeTransaction::setDnsUpdateResponse;
+    using NameChangeTransaction::clearDnsUpdateResponse;
     using NameChangeTransaction::getForwardChangeCompleted;
     using NameChangeTransaction::getForwardChangeCompleted;
     using NameChangeTransaction::getReverseChangeCompleted;
     using NameChangeTransaction::getReverseChangeCompleted;
     using NameChangeTransaction::setForwardChangeCompleted;
     using NameChangeTransaction::setForwardChangeCompleted;
     using NameChangeTransaction::setReverseChangeCompleted;
     using NameChangeTransaction::setReverseChangeCompleted;
+    using NameChangeTransaction::setUpdateAttempts;
+    using NameChangeTransaction::transition;
+    using NameChangeTransaction::retryTransition;
+    using NameChangeTransaction::sendUpdate;
+};
+
+// Declare them so Gtest can see them.
+const int NameChangeStub::DOING_UPDATE_ST;
+const int NameChangeStub::SEND_UPDATE_EVT;
+
+typedef boost::shared_ptr<asio::ip::udp::socket> SocketPtr;
+
+/// @brief This class simulates a DNS server.  It is capable of performing
+/// an asynchronous read, governed by an IOService, and responding to received
+/// requests in a given manner.
+class FauxServer {
+public:
+    enum  ResponseMode {
+        USE_RCODE,   // Generate a response with a given RCODE
+        CORRUPT_RESP  // Generate a corrupt response
+    };
+
+    asiolink::IOService& io_service_;
+    asiolink::IOAddress& address_;
+    size_t port_;
+    SocketPtr server_socket_;
+    asio::ip::udp::endpoint remote_;
+    uint8_t receive_buffer_[MAX_MSG_SIZE];
+
+    /// @brief Constructor
+    ///
+    /// @param io_service IOService to be used for socket IO.
+    /// @param address  IP address at which the server should listen.
+    /// @param port Port number at which the server should listen.
+    FauxServer(asiolink::IOService& io_service, asiolink::IOAddress& address,
+               size_t port)
+        : io_service_(io_service), address_(address), port_(port),
+          server_socket_() {
+        server_socket_.reset(new asio::ip::udp::
+                             socket(io_service_.get_io_service(),
+                                    asio::ip::udp::v4()));
+        server_socket_->set_option(asio::socket_base::reuse_address(true));
+        server_socket_->bind(asio::ip::udp::
+                             endpoint(address_.getAddress(), port_));
+    }
+
+    /// @brief Destructor
+    virtual ~FauxServer() {
+    }
+
+    /// @brief Initiates an asyncrhonrous receive
+    ///
+    /// Starts the server listening for requests.  Upon completion of the
+    /// the listen, the callback method, requestHandler, is invoked.
+    ///
+    /// @param response_mode Selects how the server responds to a request
+    /// @param response_rcode The Rcode value set in the response. Not used
+    /// for all modes.
+    void receive (const ResponseMode& response_mode,
+                  const dns::Rcode& response_rcode=dns::Rcode::NOERROR()) {
+
+        server_socket_->async_receive_from(asio::buffer(receive_buffer_,
+                                                   sizeof(receive_buffer_)),
+                                      remote_,
+                                      boost::bind(&FauxServer::requestHandler,
+                                                  this, _1, _2,
+                                                  response_mode,
+                                                  response_rcode));
+    }
+
+    /// @brief Socket IO Completion callback
+    ///
+    /// This method servers as the Server's UDP socket receive callback handler.
+    /// When the receive completes the handler is invoked with the
+    /// @param error result code of the recieve (determined by asio layer)
+    /// @param bytes_recvd number of bytes received, if any
+    /// @param response_mode type of response the handler should produce
+    /// @param response_rcode value of Rcode in the response constructed by
+    /// handler
+    void requestHandler(const asio::error_code& error,
+                        std::size_t bytes_recvd,
+                        const ResponseMode& response_mode,
+                        const dns::Rcode& response_rcode) {
+
+        // If we encountered an error or received no data then fail.
+        // We expect the client to send good requests.
+        if (error.value() != 0 || bytes_recvd < 1) {
+            ADD_FAILURE() << "FauxServer receive failed" << error.message();
+            return;
+        }
+
+        // We have a successfully received data. We need to turn it into
+        // a request in order to build a proper response.
+        // Note D2UpdateMessage is geared towards making requests and
+        // reading responses.  This is the opposite perspective so we have
+        // to a bit of roll-your-own here.
+        dns::Message request(dns::Message::PARSE);
+        util::InputBuffer request_buf(receive_buffer_, bytes_recvd);
+        try {
+            request.fromWire(request_buf);
+        } catch (const std::exception& ex) {
+            // If the request cannot be parsed, then fail the test.
+            // We expect the client to send good requests.
+            ADD_FAILURE() << "FauxServer request is corrupt:" << ex.what();
+            return;
+        }
+
+        // The request parsed ok, so let's build a response.
+        // We must use the QID we received in the response or IOFetch will
+        // toss the response out, resulting in eventual timeout.
+        // We fill in the zone with data we know is from the "server".
+        dns::Message response(dns::Message::RENDER);
+        response.setQid(request.getQid());
+        dns::Question question(dns::Name("response.example.com"),
+                               dns::RRClass::ANY(), dns::RRType::SOA());
+        response.addQuestion(question);
+        response.setOpcode(dns::Opcode(dns::Opcode::UPDATE_CODE));
+        response.setHeaderFlag(dns::Message::HEADERFLAG_QR, true);
+
+        // Set the response Rcode to value passed in, default is NOERROR.
+        response.setRcode(response_rcode);
+
+        // Render the response to a buffer.
+        dns::MessageRenderer renderer;
+        util::OutputBuffer response_buf(MAX_MSG_SIZE);
+        renderer.setBuffer(&response_buf);
+        response.toWire(renderer);
+
+        // If mode is to ship garbage, then stomp on part of the rendered
+        // message.
+        if (response_mode == CORRUPT_RESP) {
+            response_buf.writeUint16At(0xFFFF, 2);
+        }
+
+        // Ship the reponse via synchronous send.
+        try {
+            int cnt = server_socket_->send_to(asio::
+                                              buffer(response_buf.getData(),
+                                                     response_buf.getLength()),
+                                              remote_);
+            // Make sure we sent what we expect to send.
+            if (cnt != response_buf.getLength()) {
+                ADD_FAILURE() << "FauxServer sent: " << cnt << " expected: "
+                          << response_buf.getLength();
+            }
+        } catch (const std::exception& ex) {
+            ADD_FAILURE() << "FauxServer send failed: " << ex.what();
+        }
+    }
 };
 };
 
 
 /// @brief Defines a pointer to a NameChangeStubPtr instance.
 /// @brief Defines a pointer to a NameChangeStubPtr instance.
@@ -210,16 +415,48 @@ typedef boost::shared_ptr<NameChangeStub> NameChangeStubPtr;
 /// aspects of NameChangeTransaction.
 /// aspects of NameChangeTransaction.
 class NameChangeTransactionTest : public ::testing::Test {
 class NameChangeTransactionTest : public ::testing::Test {
 public:
 public:
-    isc::asiolink::IOService io_service_;
+    IOServicePtr io_service_;
     DdnsDomainPtr forward_domain_;
     DdnsDomainPtr forward_domain_;
     DdnsDomainPtr reverse_domain_;
     DdnsDomainPtr reverse_domain_;
+    asiolink::IntervalTimer timer_;
+    int run_time_;
+
+    NameChangeTransactionTest()
+        : io_service_(new isc::asiolink::IOService()), timer_(*io_service_),
+         run_time_(0) {
+    }
 
 
     virtual ~NameChangeTransactionTest() {
     virtual ~NameChangeTransactionTest() {
     }
     }
 
 
-    /// @brief Instantiates a NameChangeStub built around a canned
-    /// NameChangeRequest.
+    /// @brief Run the IO service for no more than a given amount of time.
+    ///
+    /// Uses an IntervalTimer to interrupt the invocation of IOService run(),
+    /// after the given number of milliseconds elapse.  The timer executes
+    /// the timesUp() method if it expires.
+    ///
+    /// @param run_time amount of time in milliseconds to allow run to execute.
+    void runTimedIO(int run_time) {
+        run_time_ = run_time;
+        timer_.setup(boost::bind(&NameChangeTransactionTest::timesUp, this),
+                     run_time_);
+        io_service_->run();
+    }
+
+    /// @brief IO Timer expiration handler
+    ///
+    /// Stops the IOSerivce and FAILs the current test.
+    void timesUp() {
+        io_service_->stop();
+        FAIL() << "Test Time: " << run_time_ << " expired";
+    }
+
+    /// @brief  Instantiates a NameChangeStub test transaction
+    /// The transaction is constructed around a predefined (i.e "canned")
+    /// NameChangeRequest. The request has both forward and reverse DNS
+    /// changes requested, and both forward and reverse domains are populated.
     NameChangeStubPtr makeCannedTransaction() {
     NameChangeStubPtr makeCannedTransaction() {
+        // NCR in JSON form.
         const char* msg_str =
         const char* msg_str =
             "{"
             "{"
             " \"change_type\" : 0 , "
             " \"change_type\" : 0 , "
@@ -232,28 +469,36 @@ public:
             " \"lease_length\" : 1300 "
             " \"lease_length\" : 1300 "
             "}";
             "}";
 
 
+        // Create the request from JSON.
         dhcp_ddns::NameChangeRequestPtr ncr;
         dhcp_ddns::NameChangeRequestPtr ncr;
-
         DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
         DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
         DnsServerInfoPtr server;
         DnsServerInfoPtr server;
-
         ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str);
         ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str);
 
 
-        // make forward server list
+        // Make forward DdnsDomain with 2 forward servers.
         server.reset(new DnsServerInfo("forward.example.com",
         server.reset(new DnsServerInfo("forward.example.com",
-                                       isc::asiolink::IOAddress("1.1.1.1")));
+                                       isc::asiolink::IOAddress("127.0.0.1"),
+                                       5301));
         servers->push_back(server);
         servers->push_back(server);
-        forward_domain_.reset(new DdnsDomain("*", "", servers));
+        server.reset(new DnsServerInfo("forward2.example.com",
+                                       isc::asiolink::IOAddress("127.0.0.1"),
+                                       5302));
 
 
-        // make reverse server list
-        servers->clear();
+        servers->push_back(server);
+        forward_domain_.reset(new DdnsDomain("example.com.", "", servers));
+
+        // Make reverse DdnsDomain with one reverse server.
+        servers.reset(new DnsServerInfoStorage());
         server.reset(new DnsServerInfo("reverse.example.com",
         server.reset(new DnsServerInfo("reverse.example.com",
-                                       isc::asiolink::IOAddress("2.2.2.2")));
+                                       isc::asiolink::IOAddress("127.0.0.1"),
+                                       5301));
         servers->push_back(server);
         servers->push_back(server);
-        reverse_domain_.reset(new DdnsDomain("*", "", servers));
+        reverse_domain_.reset(new DdnsDomain("2.168.192.in.addr.arpa.",
+                                             "", servers));
+
+        // Instantiate the transaction as would be done by update manager.
         return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr,
         return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr,
                                   forward_domain_, reverse_domain_)));
                                   forward_domain_, reverse_domain_)));
-
     }
     }
 
 
 };
 };
@@ -267,7 +512,7 @@ public:
 /// requires reverse change.
 /// requires reverse change.
 /// 4. Valid construction functions properly
 /// 4. Valid construction functions properly
 TEST(NameChangeTransaction, construction) {
 TEST(NameChangeTransaction, construction) {
-    isc::asiolink::IOService io_service;
+    IOServicePtr io_service(new isc::asiolink::IOService());
 
 
     const char* msg_str =
     const char* msg_str =
         "{"
         "{"
@@ -293,6 +538,13 @@ TEST(NameChangeTransaction, construction) {
     ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", "", servers)));
     ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", "", servers)));
     ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", "", servers)));
     ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", "", servers)));
 
 
+    // Verify that construction with a null IOServicePtr fails.
+    // @todo Subject to change if multi-threading is implemenated.
+    IOServicePtr empty;
+    EXPECT_THROW(NameChangeTransaction(empty, ncr,
+                                       forward_domain, reverse_domain),
+                                       NameChangeTransactionError);
+
     // Verify that construction with an empty NameChangeRequest throws.
     // Verify that construction with an empty NameChangeRequest throws.
     EXPECT_THROW(NameChangeTransaction(io_service, empty_ncr,
     EXPECT_THROW(NameChangeTransaction(io_service, empty_ncr,
                                        forward_domain, reverse_domain),
                                        forward_domain, reverse_domain),
@@ -363,9 +615,6 @@ TEST_F(NameChangeTransactionTest, accessors) {
     EXPECT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
     EXPECT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
     EXPECT_EQ(DNSClient::TIMEOUT, name_change->getDnsUpdateStatus());
     EXPECT_EQ(DNSClient::TIMEOUT, name_change->getDnsUpdateStatus());
 
 
-    // Verify that the DNS update response can be retrieved.
-    EXPECT_FALSE(name_change->getDnsUpdateResponse());
-
     // Verify that the forward change complete flag can be set and fetched.
     // Verify that the forward change complete flag can be set and fetched.
     EXPECT_NO_THROW(name_change->setForwardChangeCompleted(true));
     EXPECT_NO_THROW(name_change->setForwardChangeCompleted(true));
     EXPECT_TRUE(name_change->getForwardChangeCompleted());
     EXPECT_TRUE(name_change->getForwardChangeCompleted());
@@ -375,6 +624,59 @@ TEST_F(NameChangeTransactionTest, accessors) {
     EXPECT_TRUE(name_change->getReverseChangeCompleted());
     EXPECT_TRUE(name_change->getReverseChangeCompleted());
 }
 }
 
 
+/// @brief Tests DNS update request accessor methods.
+TEST_F(NameChangeTransactionTest, dnsUpdateRequestAccessors) {
+    // Create a transction.
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // Post transaction construction, there should not be an update request.
+    EXPECT_FALSE(name_change->getDnsUpdateRequest());
+
+    // Create a request.
+    D2UpdateMessagePtr req;
+    ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
+
+    // Use the setter and then verify we can fetch the request.
+    ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
+
+    // Post set, we should be able to fetch it.
+    ASSERT_TRUE(name_change->getDnsUpdateRequest());
+
+    // Should be able to clear it.
+    ASSERT_NO_THROW(name_change->clearDnsUpdateRequest());
+
+    // Should be empty again.
+    EXPECT_FALSE(name_change->getDnsUpdateRequest());
+}
+
+/// @brief Tests DNS update request accessor methods.
+TEST_F(NameChangeTransactionTest, dnsUpdateResponseAccessors) {
+    // Create a transction.
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // Post transaction construction, there should not be an update response.
+    EXPECT_FALSE(name_change->getDnsUpdateResponse());
+
+    // Create a response.
+    D2UpdateMessagePtr resp;
+    ASSERT_NO_THROW(resp.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND)));
+
+    // Use the setter and then verify we can fetch the response.
+    ASSERT_NO_THROW(name_change->setDnsUpdateResponse(resp));
+
+    // Post set, we should be able to fetch it.
+    EXPECT_TRUE(name_change->getDnsUpdateResponse());
+
+    // Should be able to clear it.
+    ASSERT_NO_THROW(name_change->clearDnsUpdateResponse());
+
+    // Should be empty again.
+    EXPECT_FALSE(name_change->getDnsUpdateResponse());
+}
+
+
 /// @brief Tests event and state dictionary construction and verification.
 /// @brief Tests event and state dictionary construction and verification.
 TEST_F(NameChangeTransactionTest, dictionaryCheck) {
 TEST_F(NameChangeTransactionTest, dictionaryCheck) {
     NameChangeStubPtr name_change;
     NameChangeStubPtr name_change;
@@ -596,4 +898,225 @@ TEST_F(NameChangeTransactionTest, failedUpdateTest) {
     EXPECT_FALSE(name_change->getForwardChangeCompleted());
     EXPECT_FALSE(name_change->getForwardChangeCompleted());
 }
 }
 
 
+/// @brief Tests update attempt accessors.
+TEST_F(NameChangeTransactionTest, updateAttempts) {
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // Post transaction construction, update attempts should be 0.
+    EXPECT_EQ(0, name_change->getUpdateAttempts());
+
+    // Set it to a known value.
+    name_change->setUpdateAttempts(5);
+
+    // Verify that the value is as expected.
+    EXPECT_EQ(5, name_change->getUpdateAttempts());
+}
+
+/// @brief Tests retryTransition method
+///
+/// Verifes that while the maximum number of update attempts has not
+/// been exceeded, the method will leave the state unchanged but post a
+/// SERVER_SELECTED_EVT.  Once the maximum is exceeded, the method should
+/// transition to the state given with a next event of SERVER_IO_ERROR_EVT.
+TEST_F(NameChangeTransactionTest, retryTransition) {
+    // Create the transaction.
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // Define dictionaries.
+    ASSERT_NO_THROW(name_change->initDictionaries());
+
+    // Transition to a known spot.
+    ASSERT_NO_THROW(name_change->transition(
+                    NameChangeStub::DOING_UPDATE_ST,
+                    NameChangeStub::SEND_UPDATE_EVT));
+
+    // Verify we are at the known spot.
+    ASSERT_EQ(NameChangeStub::DOING_UPDATE_ST,
+              name_change->getCurrState());
+    ASSERT_EQ(NameChangeStub::SEND_UPDATE_EVT,
+              name_change->getNextEvent());
+
+    // Verify that we have not exceeded maximum number of attempts.
+    ASSERT_LT(name_change->getUpdateAttempts(),
+              NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER);
+
+    // Call retryTransition.
+    ASSERT_NO_THROW(name_change->retryTransition(
+                    NameChangeTransaction::PROCESS_TRANS_FAILED_ST));
+
+    // Since the number of udpate attempts is less than the maximum allowed
+    // we should remain in our current state but with next event of
+    // SERVER_SELECTED_EVT posted.
+    ASSERT_EQ(NameChangeStub::DOING_UPDATE_ST,
+              name_change->getCurrState());
+    ASSERT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+              name_change->getNextEvent());
+
+    // Now set the number of attempts to the maximum.
+    name_change->setUpdateAttempts(NameChangeTransaction::
+                                   MAX_UPDATE_TRIES_PER_SERVER);
+    // Call retryTransition.
+    ASSERT_NO_THROW(name_change->retryTransition(
+                    NameChangeTransaction::PROCESS_TRANS_FAILED_ST));
+
+    // Since we have exceeded maximum attempts, we should tranisition to
+    // PROCESS_UPDATE_FAILD_ST with a next event of SERVER_IO_ERROR_EVT.
+    ASSERT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+              name_change->getCurrState());
+    ASSERT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+              name_change->getNextEvent());
+}
+
+/// @brief Tests sendUpdate method when underlying doUpdate throws.
+///
+/// DNSClient::doUpdate can throw for a variety of reasons. This tests
+/// sendUpdate handling of such a throw by passing doUpdate a request
+/// that will not render.
+TEST_F(NameChangeTransactionTest, sendUpdateDoUpdateFailure) {
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+    ASSERT_NO_THROW(name_change->initDictionaries());
+    ASSERT_TRUE(name_change->selectFwdServer());
+
+    // Set the transaction's request to an empty DNS update.
+    D2UpdateMessagePtr req;
+    ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
+    ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
+
+    // Verify that sendUpdate does not throw, but it should fail because
+    // the requset won't render.
+    ASSERT_NO_THROW(name_change->sendUpdate());
+
+    // Verify that we transition to failed state and event.
+    ASSERT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+              name_change->getCurrState());
+    ASSERT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+              name_change->getNextEvent());
+}
+
+/// @brief Tests sendUpdate method when underlying doUpdate times out.
+TEST_F(NameChangeTransactionTest, sendUpdateTimeout) {
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+    ASSERT_NO_THROW(name_change->initDictionaries());
+    ASSERT_TRUE(name_change->selectFwdServer());
+
+    // Create a valid request.
+    D2UpdateMessagePtr req;
+    ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
+    ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
+    req->setZone(dns::Name("request.example.com"), dns::RRClass::ANY());
+    req->setRcode(dns::Rcode(dns::Rcode::NOERROR_CODE));
+
+    // Set the flag to use the NameChangeStub's DNSClient callback.
+    name_change->use_stub_callback_ = true;
+
+    // Invoke sendUdpate.
+    ASSERT_NO_THROW(name_change->sendUpdate());
+
+    // Update attempt count should be 1, next event should be NOP_EVT.
+    EXPECT_EQ(1, name_change->getUpdateAttempts());
+    ASSERT_EQ(NameChangeTransaction::NOP_EVT,
+              name_change->getNextEvent());
+
+    // Run IO a bit longer than maximum allowed to permit timeout logic to
+    // execute.
+    runTimedIO(NameChangeTransaction::DNS_UPDATE_DEFAULT_TIMEOUT + 100);
+
+    // Verify that next event is IO_COMPLETED_EVT and DNS status is TIMEOUT.
+    ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+              name_change->getNextEvent());
+    ASSERT_EQ(DNSClient::TIMEOUT, name_change->getDnsUpdateStatus());
+}
+
+/// @brief Tests sendUpdate method when it receives a corrupt response from
+/// the server.
+TEST_F(NameChangeTransactionTest, sendUpdateCorruptResponse) {
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+    ASSERT_NO_THROW(name_change->initDictionaries());
+    ASSERT_TRUE(name_change->selectFwdServer());
+
+    // Create a server and start it listening.
+    asiolink::IOAddress address("127.0.0.1");
+    FauxServer server(*io_service_, address, 5301);
+    server.receive(FauxServer::CORRUPT_RESP);
+
+    // Create a valid request for the transaction.
+    D2UpdateMessagePtr req;
+    ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
+    ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
+    req->setZone(dns::Name("request.example.com"), dns::RRClass::ANY());
+    req->setRcode(dns::Rcode(dns::Rcode::NOERROR_CODE));
+
+    // Set the flag to use the NameChangeStub's DNSClient callback.
+    name_change->use_stub_callback_ = true;
+
+    // Invoke sendUdpate.
+    ASSERT_NO_THROW(name_change->sendUpdate());
+
+    // Update attempt count should be 1, next event should be NOP_EVT.
+    EXPECT_EQ(1, name_change->getUpdateAttempts());
+    ASSERT_EQ(NameChangeTransaction::NOP_EVT,
+              name_change->getNextEvent());
+
+    // Run the IO for 500 ms.  This should be more than enough time.
+    runTimedIO(500);
+
+    // Verify that next event is IO_COMPLETED_EVT and DNS status is INVALID.
+    ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+              name_change->getNextEvent());
+    ASSERT_EQ(DNSClient::INVALID_RESPONSE, name_change->getDnsUpdateStatus());
+}
+
+/// @brief Tests sendUpdate method when the exchange succeeds.
+TEST_F(NameChangeTransactionTest, sendUpdate) {
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+    ASSERT_NO_THROW(name_change->initDictionaries());
+    ASSERT_TRUE(name_change->selectFwdServer());
+
+    // Create a server and start it listening.
+    asiolink::IOAddress address("127.0.0.1");
+    FauxServer server(*io_service_, address, 5301);
+    server.receive (FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+    // Create a valid request for the transaction.
+    D2UpdateMessagePtr req;
+    ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
+    ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
+    req->setZone(dns::Name("request.example.com"), dns::RRClass::ANY());
+    req->setRcode(dns::Rcode(dns::Rcode::NOERROR_CODE));
+
+    // Set the flag to use the NameChangeStub's DNSClient callback.
+    name_change->use_stub_callback_ = true;
+
+    // Invoke sendUdpate.
+    ASSERT_NO_THROW(name_change->sendUpdate());
+
+    // Update attempt count should be 1, next event should be NOP_EVT.
+    EXPECT_EQ(1, name_change->getUpdateAttempts());
+    ASSERT_EQ(NameChangeTransaction::NOP_EVT,
+              name_change->getNextEvent());
+
+    // Run the IO for 500 ms.  This should be more than enough time.
+    runTimedIO(500);
+
+    // Verify that next event is IO_COMPLETED_EVT and DNS status is SUCCESS.
+    ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+              name_change->getNextEvent());
+    ASSERT_EQ(DNSClient::SUCCESS, name_change->getDnsUpdateStatus());
+
+    // Verify that we have a response and it's Rcode is NOERROR,
+    // and the zone is as expected.
+    D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+    ASSERT_TRUE(response);
+    ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode());
+    D2ZonePtr zone = response->getZone();
+    EXPECT_TRUE(zone);
+    EXPECT_EQ("response.example.com.", zone->getName().toText());
+}
+
 }
 }