BIND 10 master, updated. 8f99da735a9f39d514c40d0a295f751dc8edfbcd Merge branch 'trac3087'

BIND 10 source code commits bind10-changes at lists.isc.org
Tue Dec 3 18:02:47 UTC 2013


The branch, master has been updated
       via  8f99da735a9f39d514c40d0a295f751dc8edfbcd (commit)
       via  33a7569f381acf79c29b57a20329a8ed94319aa1 (commit)
       via  4c74dff22bd0cd130120d98847c16152ddf39bc6 (commit)
       via  6782bcbb2c658aecf68902af6f50f07cf04cf2f2 (commit)
       via  fe99fae754429d42ff01dc01d79fe7b5495beaad (commit)
       via  e6df9bca72feedd92ec08dc43b53dfec632f228c (commit)
       via  4c16336d81f8af698f6791c3f2a637106211e72d (commit)
       via  4d07784b1a5773e5773e1ccb683171e0225a098e (commit)
      from  608638ff330a5d144516d8d556779aad5e6e8cb3 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 8f99da735a9f39d514c40d0a295f751dc8edfbcd
Merge: 608638f 33a7569
Author: Thomas Markwalder <tmark at isc.org>
Date:   Tue Dec 3 10:56:06 2013 -0500

    Merge branch 'trac3087'

-----------------------------------------------------------------------

Summary of changes:
 src/bin/d2/Makefile.am                      |    2 +
 src/bin/d2/{d2_log.h => d2_asio.h}          |   21 +-
 src/bin/d2/d2_cfg_mgr.h                     |    2 +-
 src/bin/d2/d2_config.h                      |    2 +-
 src/bin/d2/d2_messages.mes                  |   68 +-
 src/bin/d2/d2_process.cc                    |    4 +-
 src/bin/d2/d2_queue_mgr.cc                  |    9 +-
 src/bin/d2/d2_queue_mgr.h                   |    7 +-
 src/bin/d2/d2_update_mgr.cc                 |    6 +-
 src/bin/d2/d2_update_mgr.h                  |    6 +-
 src/bin/d2/d_controller.h                   |    2 +-
 src/bin/d2/d_process.h                      |    4 +-
 src/bin/d2/dns_client.cc                    |    7 +-
 src/bin/d2/nc_add.cc                        |  539 ++++++++++
 src/bin/d2/nc_add.h                         |  409 ++++++++
 src/bin/d2/nc_trans.cc                      |   97 +-
 src/bin/d2/nc_trans.h                       |   82 +-
 src/bin/d2/state_model.cc                   |   43 +-
 src/bin/d2/state_model.h                    |   17 +-
 src/bin/d2/tests/Makefile.am                |    5 +-
 src/bin/d2/tests/d2_queue_mgr_unittests.cc  |   43 +-
 src/bin/d2/tests/d2_update_mgr_unittests.cc |   15 +-
 src/bin/d2/tests/d_test_stubs.h             |    2 +-
 src/bin/d2/tests/dns_client_unittests.cc    |    2 +-
 src/bin/d2/tests/nc_add_unittests.cc        | 1449 +++++++++++++++++++++++++++
 src/bin/d2/tests/nc_trans_unittests.cc      |  563 ++++++++++-
 26 files changed, 3295 insertions(+), 111 deletions(-)
 copy src/bin/d2/{d2_log.h => d2_asio.h} (74%)
 create mode 100644 src/bin/d2/nc_add.cc
 create mode 100644 src/bin/d2/nc_add.h
 create mode 100644 src/bin/d2/tests/nc_add_unittests.cc

-----------------------------------------------------------------------
diff --git a/src/bin/d2/Makefile.am b/src/bin/d2/Makefile.am
index 5606ef2..43f3e39 100644
--- a/src/bin/d2/Makefile.am
+++ b/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
 
 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_process.cc d2_process.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 += dns_client.cc dns_client.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 += state_model.cc state_model.h
 
diff --git a/src/bin/d2/d2_asio.h b/src/bin/d2/d2_asio.h
new file mode 100644
index 0000000..c9458f6
--- /dev/null
+++ b/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
diff --git a/src/bin/d2/d2_cfg_mgr.h b/src/bin/d2/d2_cfg_mgr.h
index d95a890..ec19d4c 100644
--- a/src/bin/d2/d2_cfg_mgr.h
+++ b/src/bin/d2/d2_cfg_mgr.h
@@ -15,9 +15,9 @@
 #ifndef D2_CFG_MGR_H
 #define D2_CFG_MGR_H
 
-#include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <exceptions/exceptions.h>
+#include <d2/d2_asio.h>
 #include <d2/d_cfg_mgr.h>
 #include <d2/d2_config.h>
 
diff --git a/src/bin/d2/d2_config.h b/src/bin/d2/d2_config.h
index 8e3bd34..6eb975e 100644
--- a/src/bin/d2/d2_config.h
+++ b/src/bin/d2/d2_config.h
@@ -15,8 +15,8 @@
 #ifndef D2_CONFIG_H
 #define D2_CONFIG_H
 
-#include <asiolink/io_address.h>
 #include <cc/data.h>
+#include <d2/d2_asio.h>
 #include <d2/d_cfg_mgr.h>
 #include <dhcpsrv/dhcp_parsers.h>
 #include <exceptions/exceptions.h>
diff --git a/src/bin/d2/d2_messages.mes b/src/bin/d2/d2_messages.mes
index e25c122..6ecadcd 100644
--- a/src/bin/d2/d2_messages.mes
+++ b/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
 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
 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
@@ -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
 of this update did not succeed. This is a programmatic error and should be
 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.
diff --git a/src/bin/d2/d2_process.cc b/src/bin/d2/d2_process.cc
index 63bd6bd..187bfe1 100644
--- a/src/bin/d2/d2_process.cc
+++ b/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,
     // D2 will neither receive nor process NameChangeRequests.
     // Pass in IOService for NCR IO event processing.
-    queue_mgr_.reset(new D2QueueMgr(*getIoService()));
+    queue_mgr_.reset(new D2QueueMgr(getIoService()));
 
     // Instantiate update manager.
     // Pass in both queue manager and configuration manager.
     // Pass in IOService for DNS update transaction IO event processing.
     D2CfgMgrPtr tmp = getD2CfgMgr();
-    update_mgr_.reset(new D2UpdateMgr(queue_mgr_,  tmp,  *getIoService()));
+    update_mgr_.reset(new D2UpdateMgr(queue_mgr_,  tmp,  getIoService()));
 };
 
 void
diff --git a/src/bin/d2/d2_queue_mgr.cc b/src/bin/d2/d2_queue_mgr.cc
index 4de9c42..041c2e8 100644
--- a/src/bin/d2/d2_queue_mgr.cc
+++ b/src/bin/d2/d2_queue_mgr.cc
@@ -22,10 +22,13 @@ namespace d2 {
 // Makes constant visible to Google test macros.
 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),
       mgr_state_(NOT_INITTED), target_stop_state_(NOT_INITTED) {
+    if (!io_service_) {
+        isc_throw(D2QueueMgrError, "IOServicePtr cannot be null");
+    }
+
     // Use setter to do validation.
     setMaxQueueSize(max_queue_size);
 }
@@ -129,7 +132,7 @@ D2QueueMgr::startListening() {
 
     // Instruct the listener to start listening and set state accordingly.
     try {
-        listener_->startListening(io_service_);
+        listener_->startListening(*io_service_);
         mgr_state_ = RUNNING;
     } catch (const isc::Exception& ex) {
         isc_throw(D2QueueMgrError, "D2QueueMgr listener start failed: "
diff --git a/src/bin/d2/d2_queue_mgr.h b/src/bin/d2/d2_queue_mgr.h
index 8fe078b..c9b0298 100644
--- a/src/bin/d2/d2_queue_mgr.h
+++ b/src/bin/d2/d2_queue_mgr.h
@@ -17,9 +17,8 @@
 
 /// @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 <d2/d2_asio.h>
 #include <dhcp_ddns/ncr_msg.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.
     ///
     /// @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);
 
     /// @brief Destructor
@@ -328,7 +327,7 @@ public:
     void updateStopState();
 
     /// @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.
     size_t max_queue_size_;
diff --git a/src/bin/d2/d2_update_mgr.cc b/src/bin/d2/d2_update_mgr.cc
index b88b415..4439cc2 100644
--- a/src/bin/d2/d2_update_mgr.cc
+++ b/src/bin/d2/d2_update_mgr.cc
@@ -24,7 +24,7 @@ namespace d2 {
 const size_t D2UpdateMgr::MAX_TRANSACTIONS_DEFAULT;
 
 D2UpdateMgr::D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
-                         isc::asiolink::IOService& io_service,
+                         IOServicePtr& io_service,
                          const size_t max_transactions)
     :queue_mgr_(queue_mgr), cfg_mgr_(cfg_mgr), io_service_(io_service) {
     if (!queue_mgr_) {
@@ -36,6 +36,10 @@ D2UpdateMgr::D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
                   "D2UpdateMgr configuration manager cannot be null");
     }
 
+    if (!io_service_) {
+        isc_throw(D2UpdateMgrError, "IOServicePtr cannot be null");
+    }
+
     // Use setter to do validation.
     setMaxTransactions(max_transactions);
 }
diff --git a/src/bin/d2/d2_update_mgr.h b/src/bin/d2/d2_update_mgr.h
index 9653253..422049f 100644
--- a/src/bin/d2/d2_update_mgr.h
+++ b/src/bin/d2/d2_update_mgr.h
@@ -17,8 +17,8 @@
 
 /// @file d2_update_mgr.h This file defines the class D2UpdateMgr.
 
-#include <asiolink/io_service.h>
 #include <exceptions/exceptions.h>
+#include <d2/d2_asio.h>
 #include <d2/d2_log.h>
 #include <d2/d2_queue_mgr.h>
 #include <d2/d2_cfg_mgr.h>
@@ -100,7 +100,7 @@ public:
     /// @throw D2UpdateMgrError if either the queue manager or configuration
     /// managers are NULL, or max transactions is less than one.
     D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
-                isc::asiolink::IOService& io_service,
+                IOServicePtr& io_service,
                 const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT);
 
     /// @brief Destructor
@@ -228,7 +228,7 @@ private:
     /// passed into transactions to manager their IO events.
     /// (For future reference, multi-threaded transactions would each use their
     /// own IOService instance.)
-    isc::asiolink::IOService& io_service_;
+    IOServicePtr io_service_;
 
     /// @brief Maximum number of concurrent transactions.
     size_t max_transactions_;
diff --git a/src/bin/d2/d_controller.h b/src/bin/d2/d_controller.h
index 100eb44..917ed64 100644
--- a/src/bin/d2/d_controller.h
+++ b/src/bin/d2/d_controller.h
@@ -15,10 +15,10 @@
 #ifndef D_CONTROLLER_H
 #define D_CONTROLLER_H
 
-#include <asiolink/asiolink.h>
 #include <cc/data.h>
 #include <cc/session.h>
 #include <config/ccsession.h>
+#include <d2/d2_asio.h>
 #include <d2/d2_log.h>
 #include <d2/d_process.h>
 #include <exceptions/exceptions.h>
diff --git a/src/bin/d2/d_process.h b/src/bin/d2/d_process.h
index ce86118..7ba74f9 100644
--- a/src/bin/d2/d_process.h
+++ b/src/bin/d2/d_process.h
@@ -15,16 +15,14 @@
 #ifndef D_PROCESS_H
 #define D_PROCESS_H
 
-#include <asiolink/asiolink.h>
 #include <cc/data.h>
+#include <d2/d2_asio.h>
 #include <d2/d_cfg_mgr.h>
 
 #include <boost/shared_ptr.hpp>
 
 #include <exceptions/exceptions.h>
 
-typedef boost::shared_ptr<isc::asiolink::IOService> IOServicePtr;
-
 namespace isc {
 namespace d2 {
 
diff --git a/src/bin/d2/dns_client.cc b/src/bin/d2/dns_client.cc
index a3bff81..a391c28 100644
--- a/src/bin/d2/dns_client.cc
+++ b/src/bin/d2/dns_client.cc
@@ -162,7 +162,7 @@ DNSClientImpl::getStatus(const asiodns::IOFetch::Result result) {
 }
 
 void
-DNSClientImpl::doUpdate(IOService& io_service,
+DNSClientImpl::doUpdate(asiolink::IOService& io_service,
                         const IOAddress& ns_addr,
                         const uint16_t ns_port,
                         D2UpdateMessage& update,
@@ -191,6 +191,7 @@ DNSClientImpl::doUpdate(IOService& io_service,
     // caller that the unsigned timeout value will fit into int.
     IOFetch io_fetch(IOFetch::UDP, io_service, msg_buf, ns_addr, ns_port,
                      in_buf_, this, static_cast<int>(wait));
+
     // Post the task to the task queue in the IO service. Caller will actually
     // run these tasks by executing IOService::run.
     io_service.post(io_fetch);
@@ -213,7 +214,7 @@ DNSClient::getMaxTimeout() {
 }
 
 void
-DNSClient::doUpdate(IOService&,
+DNSClient::doUpdate(asiolink::IOService&,
                     const IOAddress&,
                     const uint16_t,
                     D2UpdateMessage&,
@@ -224,7 +225,7 @@ DNSClient::doUpdate(IOService&,
 }
 
 void
-DNSClient::doUpdate(IOService& io_service,
+DNSClient::doUpdate(asiolink::IOService& io_service,
                     const IOAddress& ns_addr,
                     const uint16_t ns_port,
                     D2UpdateMessage& update,
diff --git a/src/bin/d2/nc_add.cc b/src/bin/d2/nc_add.cc
new file mode 100644
index 0000000..1c35ad0
--- /dev/null
+++ b/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
diff --git a/src/bin/d2/nc_add.h b/src/bin/d2/nc_add.h
new file mode 100644
index 0000000..ce8a1c5
--- /dev/null
+++ b/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
diff --git a/src/bin/d2/nc_trans.cc b/src/bin/d2/nc_trans.cc
index 50741fe..f7a2d43 100644
--- a/src/bin/d2/nc_trans.cc
+++ b/src/bin/d2/nc_trans.cc
@@ -38,18 +38,29 @@ const int NameChangeTransaction::UPDATE_FAILED_EVT;
 
 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(isc::asiolink::IOService& io_service,
+NameChangeTransaction(IOServicePtr& io_service,
                       dhcp_ddns::NameChangeRequestPtr& ncr,
                       DdnsDomainPtr& forward_domain,
                       DdnsDomainPtr& reverse_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_(),
      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_) {
-        isc_throw(NameChangeTransactionError, "NameChangeRequest cannot null");
+        isc_throw(NameChangeTransactionError,
+                  "NameChangeRequest cannot be null");
     }
 
     if (ncr_->isForwardChange() && !(forward_domain_)) {
@@ -82,6 +93,36 @@ NameChangeTransaction::operator()(DNSClient::Status status) {
 }
 
 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() {
     // Call superclass impl first.
     StateModel::defineEvents();
@@ -140,11 +181,43 @@ NameChangeTransaction::onModelFailure(const std::string& explanation) {
 }
 
 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) {
     dns_update_status_ = status;
 }
 
 void
+NameChangeTransaction::setDnsUpdateResponse(D2UpdateMessagePtr& response) {
+    dns_update_response_ = response;
+}
+
+void
+NameChangeTransaction::clearDnsUpdateResponse() {
+    dns_update_response_.reset();
+}
+
+void
 NameChangeTransaction::setForwardChangeCompleted(const bool value) {
     forward_change_completed_ = value;
 }
@@ -154,6 +227,11 @@ NameChangeTransaction::setReverseChangeCompleted(const bool value) {
     reverse_change_completed_ = value;
 }
 
+void
+NameChangeTransaction::setUpdateAttempts(const size_t value) {
+    update_attempts_ = value;
+}
+
 const dhcp_ddns::NameChangeRequestPtr&
 NameChangeTransaction::getNcr() const {
     return (ncr_);
@@ -220,12 +298,16 @@ NameChangeTransaction::getCurrentServer() const {
     return (current_server_);
 }
 
-
 void
 NameChangeTransaction::setNcrStatus(const dhcp_ddns::NameChangeStatus& status) {
     return (ncr_->setStatus(status));
 }
 
+const D2UpdateMessagePtr&
+NameChangeTransaction::getDnsUpdateRequest() const {
+    return (dns_update_request_);
+}
+
 DNSClient::Status
 NameChangeTransaction::getDnsUpdateStatus() const {
     return (dns_update_status_);
@@ -246,5 +328,10 @@ NameChangeTransaction::getReverseChangeCompleted() const {
     return (reverse_change_completed_);
 }
 
+size_t
+NameChangeTransaction::getUpdateAttempts() const {
+    return (update_attempts_);
+}
+
 } // namespace isc::d2
 } // namespace isc
diff --git a/src/bin/d2/nc_trans.h b/src/bin/d2/nc_trans.h
index d30dff7..dbff034 100644
--- a/src/bin/d2/nc_trans.h
+++ b/src/bin/d2/nc_trans.h
@@ -17,8 +17,8 @@
 
 /// @file nc_trans.h This file defines the class NameChangeTransaction.
 
-#include <asiolink/io_service.h>
 #include <exceptions/exceptions.h>
+#include <d2/d2_asio.h>
 #include <d2/d2_config.h>
 #include <d2/dns_client.h>
 #include <d2/state_model.h>
@@ -151,6 +151,12 @@ public:
     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
     ///
     /// Instantiates a transaction that is ready to be started.
@@ -163,7 +169,7 @@ public:
     /// @throw NameChangeTransactionError if given an null request,
     /// if forward change is enabled but forward domain is null, if
     /// reverse change is enabled but reverse domain is null.
-    NameChangeTransaction(isc::asiolink::IOService& io_service,
+    NameChangeTransaction(IOServicePtr& io_service,
                           dhcp_ddns::NameChangeRequestPtr& ncr,
                           DdnsDomainPtr& forward_domain,
                           DdnsDomainPtr& reverse_domain);
@@ -191,6 +197,20 @@ public:
     virtual void operator()(DNSClient::Status status);
 
 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.
     ///
     /// This method adds the events common to NCR transaction processing to
@@ -248,6 +268,28 @@ protected:
     /// @param explanation is text detailing the error
     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.
     ///
     /// @param status is the new value for the update status.
@@ -258,6 +300,9 @@ protected:
     /// @param response is the new response packet to assign.
     void setDnsUpdateResponse(D2UpdateMessagePtr& response);
 
+    /// @brief Destroys the current update response packet.
+    void clearDnsUpdateResponse();
+
     /// @brief Sets the forward change completion flag to the given value.
     ///
     /// @param value is the new value to assign to the flag.
@@ -307,6 +352,19 @@ protected:
     /// @return A const pointer reference to the DNSClient
     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:
     /// @brief Fetches the NameChangeRequest for this transaction.
     ///
@@ -344,6 +402,12 @@ public:
     /// the request does not include a reverse change, the pointer will empty.
     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.
     ///
     /// @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.
     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:
     /// @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.
     dhcp_ddns::NameChangeRequestPtr ncr_;
@@ -398,6 +468,9 @@ private:
     /// @brief The DNSClient instance that will carry out DNS packet exchanges.
     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.
     DNSClient::Status dns_update_status_;
 
@@ -421,6 +494,9 @@ private:
     /// This value is always the position of the next selection in the server
     /// list, which may be beyond the end of the list.
     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.
diff --git a/src/bin/d2/state_model.cc b/src/bin/d2/state_model.cc
index 6786e43..c5a0209 100644
--- a/src/bin/d2/state_model.cc
+++ b/src/bin/d2/state_model.cc
@@ -92,27 +92,13 @@ StateModel::~StateModel(){
 
 void
 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.
     setState(start_state);
+
+    // Start running the model.
     runModel(START_EVT);
 }
 
@@ -149,6 +135,27 @@ void
 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
 StateModel::defineEvent(unsigned int event_value, const std::string& label) {
diff --git a/src/bin/d2/state_model.h b/src/bin/d2/state_model.h
index 1596bb6..b7320eb 100644
--- a/src/bin/d2/state_model.h
+++ b/src/bin/d2/state_model.h
@@ -17,7 +17,6 @@
 
 /// @file state_model.h This file defines the class StateModel.
 
-#include <asiolink/io_service.h>
 #include <exceptions/exceptions.h>
 #include <d2/d2_config.h>
 #include <d2/dns_client.h>
@@ -276,10 +275,9 @@ public:
 
     /// @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.
     ///
@@ -324,6 +322,15 @@ public:
     void nopStateHandler();
 
 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.
     ///
     /// This method is used to construct the set of valid events. Each class
diff --git a/src/bin/d2/tests/Makefile.am b/src/bin/d2/tests/Makefile.am
index 9c6d1f2..439918b 100644
--- a/src/bin/d2/tests/Makefile.am
+++ b/src/bin/d2/tests/Makefile.am
@@ -51,7 +51,8 @@ if HAVE_GTEST
 
 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_controller.cc ../d2_controller.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 += ../dns_client.cc ../dns_client.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 += ../state_model.cc ../state_model.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 += dns_client_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 += state_model_unittests.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
diff --git a/src/bin/d2/tests/d2_queue_mgr_unittests.cc b/src/bin/d2/tests/d2_queue_mgr_unittests.cc
index 18bf95c..a49ab65 100644
--- a/src/bin/d2/tests/d2_queue_mgr_unittests.cc
+++ b/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
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <asiolink/interval_timer.h>
+#include <d2/d2_asio.h>
 #include <d2/d2_queue_mgr.h>
 #include <dhcp_ddns/ncr_udp.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.
 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.
     EXPECT_THROW(D2QueueMgr(io_service, 0), D2QueueMgrError);
 }
 
 /// @brief Tests default construction works.
 TEST(D2QueueMgrBasicTest, construction2) {
-    isc::asiolink::IOService io_service;
+    IOServicePtr io_service(new isc::asiolink::IOService());
 
     // Verify that valid constructor works.
     D2QueueMgrPtr queue_mgr;
@@ -97,7 +101,7 @@ TEST(D2QueueMgrBasicTest, construction2) {
 
 /// @brief Tests construction with custom queue size works properly
 TEST(D2QueueMgrBasicTest, construction3) {
-    isc::asiolink::IOService io_service;
+    IOServicePtr io_service(new isc::asiolink::IOService());
 
     // Verify that custom queue size constructor works.
     D2QueueMgrPtr queue_mgr;
@@ -114,7 +118,7 @@ TEST(D2QueueMgrBasicTest, construction3) {
 /// 4. Peek returns the first entry on the queue without altering queue content
 /// 5. Dequeue removes the first entry on the queue
 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
     // we'll use.
@@ -206,7 +210,7 @@ bool checkSendVsReceived(NameChangeRequestPtr sent_ncr,
 class QueueMgrUDPTest : public virtual ::testing::Test,
                         NameChangeSender::RequestSendHandler {
 public:
-    isc::asiolink::IOService io_service_;
+    IOServicePtr io_service_;
     NameChangeSenderPtr   sender_;
     isc::asiolink::IntervalTimer test_timer_;
     D2QueueMgrPtr queue_mgr_;
@@ -215,7 +219,8 @@ public:
     std::vector<NameChangeRequestPtr> sent_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);
         // Create our sender instance. Note that reuse_address is true.
         sender_.reset(new NameChangeUDPSender(addr, SENDER_PORT,
@@ -245,7 +250,7 @@ public:
     ///
     /// This callback stops all running (hanging) tasks on IO service.
     void testTimeoutHandler() {
-        io_service_.stop();
+        io_service_->stop();
         FAIL() << "Test timeout hit.";
     }
 };
@@ -296,7 +301,7 @@ TEST_F (QueueMgrUDPTest, stateModel) {
 
     // Stopping requires IO cancel, which result in a callback.
     // So process one event and verify we are STOPPED.
-    io_service_.run_one();
+    io_service_->run_one();
     EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr_->getMgrState());
 
     // 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.
     // So process one event and verify we are STOPPED.
-    io_service_.run_one();
+    io_service_->run_one();
     EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr_->getMgrState());
 
     // 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());
 
     // Place the sender into sending state.
-    ASSERT_NO_THROW(sender_->startSending(io_service_));
+    ASSERT_NO_THROW(sender_->startSending(*io_service_));
     ASSERT_TRUE(sender_->amSending());
 
     // Iterate over the list of requests sending and receiving
@@ -366,8 +371,8 @@ TEST_F (QueueMgrUDPTest, liveFeed) {
         ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
 
         // 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
         // size increments accordingly.
@@ -390,8 +395,8 @@ TEST_F (QueueMgrUDPTest, liveFeed) {
         ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
 
         // 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());
     }
 
@@ -400,11 +405,11 @@ TEST_F (QueueMgrUDPTest, liveFeed) {
 
     // Send another. The send should succeed.
     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
     // 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());
 
     // Verify queue size did not increase beyond max.
@@ -430,10 +435,10 @@ TEST_F (QueueMgrUDPTest, liveFeed) {
     // Verify that we can again receive requests.
     // Send should be fine.
     ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
-    EXPECT_NO_THROW(io_service_.run_one());
+    EXPECT_NO_THROW(io_service_->run_one());
 
     // Receive should succeed.
-    EXPECT_NO_THROW(io_service_.run_one());
+    EXPECT_NO_THROW(io_service_->run_one());
     EXPECT_EQ(1, queue_mgr_->getQueueSize());
 }
 
diff --git a/src/bin/d2/tests/d2_update_mgr_unittests.cc b/src/bin/d2/tests/d2_update_mgr_unittests.cc
index 0abed5d..e4f0c4c 100644
--- a/src/bin/d2/tests/d2_update_mgr_unittests.cc
+++ b/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
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <asiolink/interval_timer.h>
+#include <d2/d2_asio.h>
 #include <d2/d2_update_mgr.h>
 #include <util/time_utilities.h>
 #include <d_test_stubs.h>
@@ -41,7 +41,7 @@ public:
     ///
     /// Parameters match those needed by D2UpdateMgr.
     D2UpdateMgrWrapper(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
-                       isc::asiolink::IOService& io_service,
+                       IOServicePtr& io_service,
                        const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT)
         : D2UpdateMgr(queue_mgr, cfg_mgr, io_service, max_transactions) {
     }
@@ -68,7 +68,7 @@ typedef boost::shared_ptr<D2UpdateMgrWrapper> D2UpdateMgrWrapperPtr;
 /// functions.
 class D2UpdateMgrTest : public ConfigParseTest {
 public:
-    isc::asiolink::IOService io_service_;
+    IOServicePtr io_service_;
     D2QueueMgrPtr queue_mgr_;
     D2CfgMgrPtr cfg_mgr_;
     //D2UpdateMgrPtr update_mgr_;
@@ -77,6 +77,7 @@ public:
     size_t canned_count_;
 
     D2UpdateMgrTest() {
+        io_service_.reset(new isc::asiolink::IOService());
         queue_mgr_.reset(new D2QueueMgr(io_service_));
         cfg_mgr_.reset(new D2CfgMgr());
         update_mgr_.reset(new D2UpdateMgrWrapper(queue_mgr_, cfg_mgr_,
@@ -162,7 +163,7 @@ public:
 /// 4. Default construction works and max transactions is defaulted properly
 /// 5. Construction with custom max transactions works properly
 TEST(D2UpdateMgr, construction) {
-    isc::asiolink::IOService io_service;
+    IOServicePtr io_service(new isc::asiolink::IOService());
     D2QueueMgrPtr queue_mgr;
     D2CfgMgrPtr cfg_mgr;
     D2UpdateMgrPtr update_mgr;
@@ -180,6 +181,12 @@ TEST(D2UpdateMgr, construction) {
 
     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.
     EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service, 0),
                  D2UpdateMgrError);
diff --git a/src/bin/d2/tests/d_test_stubs.h b/src/bin/d2/tests/d_test_stubs.h
index 1313687..7680a98 100644
--- a/src/bin/d2/tests/d_test_stubs.h
+++ b/src/bin/d2/tests/d_test_stubs.h
@@ -15,11 +15,11 @@
 #ifndef D_TEST_STUBS_H
 #define D_TEST_STUBS_H
 
-#include <asiolink/asiolink.h>
 #include <cc/data.h>
 #include <cc/session.h>
 #include <config/ccsession.h>
 
+#include <d2/d2_asio.h>
 #include <d2/d_controller.h>
 #include <d2/d_cfg_mgr.h>
 
diff --git a/src/bin/d2/tests/dns_client_unittests.cc b/src/bin/d2/tests/dns_client_unittests.cc
index 9105ab8..db4d6b5 100644
--- a/src/bin/d2/tests/dns_client_unittests.cc
+++ b/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
         // should cause instant timeout.
-        const int timeout = 0;
+        const int timeout = 500;
         // The doUpdate() function starts asynchronous message exchange with DNS
         // server. When message exchange is done or timeout occurs, the
         // completion callback will be triggered. The doUpdate function returns
diff --git a/src/bin/d2/tests/nc_add_unittests.cc b/src/bin/d2/tests/nc_add_unittests.cc
new file mode 100644
index 0000000..8d28cc3
--- /dev/null
+++ b/src/bin/d2/tests/nc_add_unittests.cc
@@ -0,0 +1,1449 @@
+// 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/nc_add.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+
+namespace {
+
+/// @brief Test class derived from NameAddTransaction to provide visiblity
+// to protected methods.
+class NameAddStub : public NameAddTransaction {
+public:
+    NameAddStub(IOServicePtr& io_service,
+                dhcp_ddns::NameChangeRequestPtr& ncr,
+                DdnsDomainPtr& forward_domain,
+                DdnsDomainPtr& reverse_domain)
+        : NameAddTransaction(io_service, ncr, forward_domain, reverse_domain){
+    }
+
+    virtual ~NameAddStub() {
+    }
+
+    /// @brief Simulates sending update requests to the DNS server
+    /// Allows state handlers which conduct IO to be tested without a server.
+    virtual void sendUpdate(bool /* use_tsig_ = false */) {
+        setUpdateAttempts(getUpdateAttempts() + 1);
+        postNextEvent(StateModel::NOP_EVT);
+    }
+
+    void fakeResponse(const DNSClient::Status& status,
+                      const dns::Rcode& rcode) {
+        D2UpdateMessagePtr msg(new D2UpdateMessage(D2UpdateMessage::OUTBOUND));
+        setDnsUpdateStatus(status);
+        msg->setRcode(rcode);
+        setDnsUpdateResponse(msg);
+        postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+    }
+
+    bool selectFwdServer() {
+        if (getForwardDomain()) {
+            initServerSelection(getForwardDomain());
+            selectNextServer();
+            return (getCurrentServer());
+        }
+
+        return (false);
+    }
+
+    bool selectRevServer() {
+        if (getReverseDomain()) {
+            initServerSelection(getReverseDomain());
+            selectNextServer();
+            return (getCurrentServer());
+        }
+
+        return (false);
+    }
+
+
+    using StateModel::postNextEvent;
+    using StateModel::setState;
+    using StateModel::initDictionaries;
+    using NameAddTransaction::defineEvents;
+    using NameAddTransaction::verifyEvents;
+    using NameAddTransaction::defineStates;
+    using NameAddTransaction::verifyStates;
+    using NameAddTransaction::readyHandler;
+    using NameAddTransaction::selectingFwdServerHandler;
+    using NameAddTransaction::getCurrentServer;
+    using NameAddTransaction::addingFwdAddrsHandler;
+    using NameAddTransaction::setDnsUpdateStatus;
+    using NameAddTransaction::replacingFwdAddrsHandler;
+    using NameAddTransaction::selectingRevServerHandler;
+    using NameAddTransaction::replacingRevPtrsHandler;
+    using NameAddTransaction::processAddOkHandler;
+    using NameAddTransaction::processAddFailedHandler;
+};
+
+typedef boost::shared_ptr<NameAddStub> NameAddStubPtr;
+
+/// @brief Test fixture for testing NameAddTransaction
+///
+/// Note this class uses NameAddStub class to exercise non-public
+/// aspects of NameAddTransaction.
+class NameAddTransactionTest : public ::testing::Test {
+public:
+    IOServicePtr io_service_;
+    DdnsDomainPtr forward_domain_;
+    DdnsDomainPtr reverse_domain_;
+
+    NameAddTransactionTest() : io_service_(new isc::asiolink::IOService()) {
+    }
+
+    static const unsigned int FORWARD_CHG = 0x01;
+    static const unsigned int REVERSE_CHG = 0x02;
+    static const unsigned int FWD_AND_REV_CHG = REVERSE_CHG | FORWARD_CHG;
+
+    virtual ~NameAddTransactionTest() {
+    }
+
+    /// @brief  Instantiates a NameAddStub test transaction
+    /// The transaction is constructed around a predefined (i.e "canned")
+    /// NameChangeRequest. The request has both forward and reverse DNS
+    /// changes requested.  Based upon the change mask, the transaction
+    /// will have either the forward, reverse, or both domains populated.
+    ///
+    /// @param change_mask determines which change directions are requested
+    NameAddStubPtr makeCannedTransaction(int change_mask=FWD_AND_REV_CHG) {
+        const char* msg_str =
+            "{"
+            " \"change_type\" : 0 , "
+            " \"forward_change\" : true , "
+            " \"reverse_change\" : true , "
+            " \"fqdn\" : \"example.com.\" , "
+            " \"ip_address\" : \"192.168.2.1\" , "
+            " \"dhcid\" : \"0102030405060708\" , "
+            " \"lease_expires_on\" : \"20130121132405\" , "
+            " \"lease_length\" : 1300 "
+            "}";
+
+        // Create NameChangeRequest from JSON string.
+        dhcp_ddns::NameChangeRequestPtr ncr = dhcp_ddns::NameChangeRequest::
+                                              fromJSON(msg_str);
+
+        // If the change mask does not include a forward change clear the
+        // forward domain; otherise create the domain and its servers.
+        if (!(change_mask & FORWARD_CHG)) {
+            ncr->setForwardChange(false);
+            forward_domain_.reset();
+        } else {
+            // Create the forward domain and then its servers.
+            DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+            DnsServerInfoPtr server(new DnsServerInfo("forward.example.com",
+                                       isc::asiolink::IOAddress("1.1.1.1")));
+            servers->push_back(server);
+            server.reset(new DnsServerInfo("forward2.example.com",
+                                       isc::asiolink::IOAddress("1.1.1.2")));
+            servers->push_back(server);
+            forward_domain_.reset(new DdnsDomain("example.com.", "", servers));
+        }
+
+        // If the change mask does not include a reverse change clear the
+        // reverse domain; otherise create the domain and its servers.
+        if (!(change_mask & REVERSE_CHG)) {
+            ncr->setReverseChange(false);
+            reverse_domain_.reset();
+        } else {
+            // Create the reverse domain and its server.
+            DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+            DnsServerInfoPtr server(new DnsServerInfo("reverse.example.com",
+                                                      isc::asiolink::
+                                                      IOAddress("2.2.2.2")));
+            servers->push_back(server);
+            server.reset(new DnsServerInfo("reverse2.example.com",
+                                           isc::asiolink::
+                                           IOAddress("2.2.2.3")));
+            servers->push_back(server);
+            reverse_domain_.reset(new DdnsDomain("2.168.192.in.addr.arpa.",
+                                                 "", servers));
+        }
+
+        // Now create the test transaction as would occur in update manager.
+        return (NameAddStubPtr(new NameAddStub(io_service_, ncr,
+                                      forward_domain_, reverse_domain_)));
+    }
+
+    /// @brief Create a test transaction at a known point in the state model.
+    ///
+    /// Method prepares a new test transaction and sets its state and next
+    /// event values to those given.  This makes the transaction appear to
+    /// be at that point in the state model without having to transition it
+    /// through prerequiste states.   It also provides the ability to set
+    /// which change directions are requested: forward change only, reverse
+    /// change only, or both.
+    ///
+    /// @param state value to set as the current state
+    /// @param event value to post as the next event
+    /// @param change_mask determines which change directions are requested
+    NameAddStubPtr prepHandlerTest(unsigned int state, unsigned int event,
+                                   unsigned int change_mask = FWD_AND_REV_CHG) {
+        NameAddStubPtr name_add = makeCannedTransaction(change_mask);
+        name_add->initDictionaries();
+        name_add->postNextEvent(event);
+        name_add->setState(state);
+        return (name_add);
+    }
+
+};
+
+/// @brief Tests NameAddTransaction construction.
+/// This test verifies that:
+/// 1. Construction with invalid type of request
+/// 2. Valid construction functions properly
+TEST(NameAddTransaction, construction) {
+    IOServicePtr io_service(new isc::asiolink::IOService());
+
+    const char* msg_str =
+        "{"
+        " \"change_type\" : 1 , "
+        " \"forward_change\" : true , "
+        " \"reverse_change\" : true , "
+        " \"fqdn\" : \"example.com.\" , "
+        " \"ip_address\" : \"192.168.2.1\" , "
+        " \"dhcid\" : \"0102030405060708\" , "
+        " \"lease_expires_on\" : \"20130121132405\" , "
+        " \"lease_length\" : 1300 "
+        "}";
+
+    dhcp_ddns::NameChangeRequestPtr ncr;
+    DnsServerInfoStoragePtr servers;
+    DdnsDomainPtr forward_domain;
+    DdnsDomainPtr reverse_domain;
+    DdnsDomainPtr empty_domain;
+
+    ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
+    ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", "", servers)));
+    ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", "", servers)));
+
+    // Verify that construction with wrong change type fails.
+    EXPECT_THROW(NameAddTransaction(io_service, ncr,
+                                    forward_domain, reverse_domain),
+                                    NameAddTransactionError);
+
+    // Verify that a valid construction attempt works.
+    ncr->setChangeType(isc::dhcp_ddns::CHG_ADD);
+    EXPECT_NO_THROW(NameAddTransaction(io_service, ncr,
+                                       forward_domain, reverse_domain));
+}
+
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(NameAddTransactionTest, dictionaryCheck) {
+    NameAddStubPtr name_add;
+    ASSERT_NO_THROW(name_add = makeCannedTransaction());
+    // Verify that the event and state dictionary validation fails prior
+    // dictionary construction.
+    ASSERT_THROW(name_add->verifyEvents(), StateModelError);
+    ASSERT_THROW(name_add->verifyStates(), StateModelError);
+
+    // Construct both dictionaries.
+    ASSERT_NO_THROW(name_add->defineEvents());
+    ASSERT_NO_THROW(name_add->defineStates());
+
+    // Verify both event and state dictionaries now pass validation.
+    ASSERT_NO_THROW(name_add->verifyEvents());
+    ASSERT_NO_THROW(name_add->verifyStates());
+}
+
+// Tests the readyHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is START_EVT and request includes only a forward change
+// 2. Posted event is START_EVT and request includes both a forward and a
+// reverse change
+// 3. Posted event is START_EVT and request includes only a reverse change
+// 3. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, readyHandler) {
+    NameAddStubPtr name_add;
+
+    // Create a transaction which includes only a forward change.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameChangeTransaction::READY_ST,
+                                    StateModel::START_EVT, FORWARD_CHG));
+    // Run readyHandler.
+    EXPECT_NO_THROW(name_add->readyHandler());
+
+    // Verify that a request requiring only a forward change, transitions to
+    // selecting a forward server.
+    EXPECT_EQ(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+              name_add->getNextEvent());
+
+
+    // Create a transaction which includes both a forward and a reverse change.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameChangeTransaction::READY_ST,
+                                    StateModel::START_EVT, FWD_AND_REV_CHG));
+    // Run readyHandler.
+    EXPECT_NO_THROW(name_add->readyHandler());
+
+    // Verify that a request requiring both forward and reverse, starts with
+    // the forward change by transitioning to selecting a forward server.
+    EXPECT_EQ(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+              name_add->getNextEvent());
+
+
+    // Create and prep a reverse only transaction.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameChangeTransaction::READY_ST,
+                                    StateModel::START_EVT, REVERSE_CHG));
+    // Run readyHandler.
+    EXPECT_NO_THROW(name_add->readyHandler());
+
+    // Verify that a request requiring only a reverse change, transitions to
+    // selecting a reverse server.
+    EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+              name_add->getNextEvent());
+
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameChangeTransaction::READY_ST,
+                                    StateModel::NOP_EVT));
+
+    // Running the readyHandler should throw.
+    EXPECT_THROW(name_add->readyHandler(), NameAddTransactionError);
+}
+
+// Tests the selectingFwdServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, selectingFwdServerHandler) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameChangeTransaction::
+                                    SELECTING_FWD_SERVER_ST,
+                                    NameChangeTransaction::SELECT_SERVER_EVT));
+
+    // Call selectingFwdServerHandler enough times to select all of the
+    // servers in it's current domain.  The first time, it will be with
+    // next event of SELECT_SERVER_EVT.  Thereafter it will be with a next
+    // event of SERVER_IO_ERROR_EVT.
+    int num_servers = name_add->getForwardDomain()->getServers()->size();
+    for (int i = 0; i < num_servers; ++i) {
+        // Run selectingFwdServerHandler.
+        ASSERT_NO_THROW(name_add->selectingFwdServerHandler())
+                        << " num_servers: " << num_servers
+                        << " selections: " << i;
+
+        // Verify that a server was selected.
+        ASSERT_TRUE(name_add->getCurrentServer())
+                    << " num_servers: " << num_servers << " selections: " << i;
+
+        // Verify that we transitioned correctly.
+        ASSERT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+                  name_add->getCurrState())
+                  << " num_servers: " << num_servers << " selections: " << i;
+        ASSERT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+                  name_add->getNextEvent())
+                  << " num_servers: " << num_servers << " selections: " << i;
+
+        // Post a server IO error event.  This simulates an IO error occuring
+        // and a need to select the new server.
+        ASSERT_NO_THROW(name_add->postNextEvent(NameChangeTransaction::
+                                                SERVER_IO_ERROR_EVT))
+                        << " num_servers: " << num_servers
+                        << " selections: " << i;
+    }
+
+    // We should have exhausted the list of servers. Processing another
+    // SERVER_IO_ERROR_EVT should transition us to failure.
+    EXPECT_NO_THROW(name_add->selectingFwdServerHandler());
+    EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::NO_MORE_SERVERS_EVT,
+              name_add->getNextEvent());
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameChangeTransaction::
+                                    SELECTING_FWD_SERVER_ST,
+                                    StateModel::NOP_EVT));
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_add->selectingFwdServerHandler(),
+                 NameAddTransactionError);
+}
+
+// ************************ addingFwdAddrHandler Tests *****************
+
+// Tests that addingFwdAddrsHandler rejects invalid events.
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_InvalidEvent) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler but with
+    // an invalid event.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+                                    NameChangeTransaction::
+                                    StateModel::NOP_EVT));
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_add->addingFwdAddrsHandler(),
+                 NameAddTransactionError);
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+//  The request includes only a forward change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates successful update
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_FwdOnlyAddOK) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+                                    NameChangeTransaction::
+                                    SERVER_SELECTED_EVT, FORWARD_CHG));
+
+    // Should not be an update message yet.
+    D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+    ASSERT_FALSE(update_msg);
+
+    // At this point completion flags should be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Run addingFwdAddrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+    // Verify that an update message was constructed.
+    update_msg = name_add->getDnsUpdateRequest();
+    EXPECT_TRUE(update_msg);
+
+    // Verify that we are still in this state and next event is NOP_EVT.
+    // This indicates we "sent" the message and are waiting for IO completion.
+    EXPECT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::NOP_EVT,
+              name_add->getNextEvent());
+
+    // Simulate receiving a succussful update response.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+    // Run addingFwdAddrsHandler again to process the response.
+    EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+    // Forward completion should be true, reverse should be false.
+    EXPECT_TRUE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Since it is a forward only change, we should be done.
+    // Verify that we transitioned correctly.
+    EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+              name_add->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_fwdAndRevAddOK) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+                                    NameChangeTransaction::
+                                    SERVER_SELECTED_EVT, FWD_AND_REV_CHG));
+
+    // Run addingFwdAddrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+    // Simulate receiving a succussful update response.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+    // Run addingFwdAddrsHandler again  to process the response.
+    EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+    // Forward change completion should be true, reverse flag should be false.
+    EXPECT_TRUE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Since the request also includes a reverse change we should
+    // be poised to start it. Verify that we transitioned correctly.
+    EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+              name_add->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates the FQDN is in use.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_FqdnInUse) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+                                    NameChangeTransaction::
+                                    SERVER_SELECTED_EVT));
+
+    // Run addingFwdAddrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+    // Simulate receiving a FQDN in use response.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::YXDOMAIN());
+
+    // Run addingFwdAddrsHandler again to process the response.
+    EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+    // Completion flags should still be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Since the FQDN is in use, per the RFC we must attempt to replace it.
+    // Verify that we transitioned correctly.
+    EXPECT_EQ(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameAddTransaction::FQDN_IN_USE_EVT,
+              name_add->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates the update was rejected.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_OtherRcode) {
+    NameAddStubPtr name_add;
+
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+                                    NameChangeTransaction::
+                                    SERVER_SELECTED_EVT));
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectFwdServer());
+
+    // Run addingFwdAddrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+    // Simulate receiving server rejection response. Per RFC, anything other
+    // than no error or FQDN in use is failure.  Arbitrarily choosing refused.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+    // Run addingFwdAddrsHandler again to process the response.
+    EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+    // Completion flags should still be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // We should have failed the transaction. Verifiy that we transitioned
+    // correctly.
+    EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+              name_add->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_Timeout) {
+    NameAddStubPtr name_add;
+
+    // Create and prep a transaction, poised to run the handler.
+    // The log message issued when this test succeeds, displays the
+    // selected server, so we need to select a server before running this
+    // test.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+                                    NameChangeTransaction::
+                                    SERVER_SELECTED_EVT));
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectFwdServer());
+
+    // Verify that we can make maximum number of update attempts permitted
+    // and then transition to selecting a new server.
+    int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+    for (int i = 1; i <= max_tries; ++i) {
+        const D2UpdateMessagePtr prev_msg = name_add->getDnsUpdateRequest();
+
+        // Run addingFwdAddrsHandler to send the request.
+        EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+        const D2UpdateMessagePtr curr_msg = name_add->getDnsUpdateRequest();
+        if (i == 1) {
+            // First time out we should build the message.
+            EXPECT_FALSE(prev_msg);
+            EXPECT_TRUE(curr_msg);
+        } else {
+            // Subsequent passes should reuse the request. We are only
+            // looking to check that we have not replaced the pointer value
+            // with a new pointer.  This tests the on_entry() logic which
+            // clears the request ONLY upon initial entry into the state.
+            EXPECT_TRUE(prev_msg == curr_msg);
+        }
+
+        // Simulate a server IO timeout.
+        name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+        name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+        // Run addingFwdAddrsHandler again to process the response.
+        EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+        // Completion flags should be false.
+        EXPECT_FALSE(name_add->getForwardChangeCompleted());
+        EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+        if (i < max_tries) {
+            // We should be ready to try again.
+            EXPECT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+                      name_add->getCurrState());
+            EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+                    name_add->getNextEvent());
+        } else {
+            // Server retries should be exhausted, time for a new server.
+            EXPECT_EQ(NameAddTransaction::SELECTING_FWD_SERVER_ST,
+                      name_add->getCurrState());
+            EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+                    name_add->getNextEvent());
+        }
+    }
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent but a corrupt response is received, this occurs
+//  MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_InvalidResponse) {
+    NameAddStubPtr name_add;
+
+    // Create and prep a transaction, poised to run the handler.
+    // The log message issued when this test succeeds, displays the
+    // selected server, so we need to select a server before running this
+    // test.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+                                    NameChangeTransaction::
+                                    SERVER_SELECTED_EVT));
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectFwdServer());
+
+    // Verify that we can make maximum number of update attempts permitted
+    // and then transition to selecting a new server.
+    int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+    for (int i = 1; i <= max_tries; ++i) {
+        // Run addingFwdAddrsHandler to construct send the request.
+        EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+        // Simulate a server IO timeout.
+        name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+        name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+        // Run addingFwdAddrsHandler again to process the response.
+        EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+        // Completion flags should be false.
+        EXPECT_FALSE(name_add->getForwardChangeCompleted());
+        EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+        if (i < max_tries) {
+            // We should be ready to try again.
+            EXPECT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+                      name_add->getCurrState());
+            EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+                    name_add->getNextEvent());
+        } else {
+            // Server retries should be exhausted, time for a new server.
+            EXPECT_EQ(NameAddTransaction::SELECTING_FWD_SERVER_ST,
+                      name_add->getCurrState());
+            EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+                    name_add->getNextEvent());
+        }
+    }
+
+}
+
+// ************************ replacingFwdAddrHandler Tests *****************
+
+// Tests that replacingFwdAddrsHandler rejects invalid events.
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_InvalidEvent) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler but with
+    // an invalid event.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                    NameChangeTransaction::
+                                    StateModel::NOP_EVT));
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_add->replacingFwdAddrsHandler(),
+                 NameAddTransactionError);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+//  The request includes only a forward change.
+//  Initial posted event is FQDN_IN_USE_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_FwdOnlyAddOK) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                    NameAddTransaction::
+                                    FQDN_IN_USE_EVT, FORWARD_CHG));
+
+    // Should not be an update message yet.
+    D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+    ASSERT_FALSE(update_msg);
+
+    // At this point completion flags should be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Run replacingFwdAddrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Verify that an update message was constructed.
+    update_msg = name_add->getDnsUpdateRequest();
+    EXPECT_TRUE(update_msg);
+
+    // Verify that we are still in this state and next event is NOP_EVT.
+    // This indicates we "sent" the message and are waiting for IO completion.
+    EXPECT_EQ(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::NOP_EVT,
+              name_add->getNextEvent());
+
+    // Simulate receiving a succussful update response.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+    // Run replacingFwdAddrsHandler again to process the response.
+    EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Forward completion should be true, reverse should be false.
+    EXPECT_TRUE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Since it is a forward only change, we should be done.
+    // Verify that we transitioned correctly.
+    EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+              name_add->getNextEvent());
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+//  The request includes only a forward change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_FwdOnlyAddOK2) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                    NameChangeTransaction::
+                                    SERVER_SELECTED_EVT, FORWARD_CHG));
+
+    // Run replacingFwdAddrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Simulate receiving a succussful update response.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+    // Run replacingFwdAddrsHandler again to process the response.
+    EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Forward completion should be true, reverse should be false.
+    EXPECT_TRUE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Since it is a forward only change, we should be done.
+    // Verify that we transitioned correctly.
+    EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+              name_add->getNextEvent());
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  Initial posted event is FQDN_IN_USE_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_FwdAndRevAddOK) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                    NameAddTransaction::
+                                    FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+    // Run replacingFwdAddrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Simulate receiving a succussful update response.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+    // Run replacingFwdAddrsHandler again  to process the response.
+    EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Forward change completion should be true, reverse flag should be false.
+    EXPECT_TRUE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Since the request also includes a reverse change we should
+    // be poised to start it. Verify that we transitioned correctly.
+    EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+              name_add->getNextEvent());
+}
+
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  Initial posted event is FQDN_IN_USE_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates the FQDN is NOT in use.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_FqdnNotInUse) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                    NameAddTransaction::
+                                    FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+    // Run replacingFwdAddrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Simulate receiving a FQDN not in use response.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXDOMAIN());
+
+    // Run replacingFwdAddrsHandler again to process the response.
+    EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Completion flags should still be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Since the FQDN is no longer in use, per the RFC, try to add it.
+    // Verify that we transitioned correctly.
+    EXPECT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+              name_add->getNextEvent());
+}
+
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  The update request is sent without error.
+//  A server response is received which indicates the update was rejected.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_OtherRcode) {
+    NameAddStubPtr name_add;
+    // Create the transaction.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                    NameAddTransaction::
+                                    FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectFwdServer());
+
+    // Run replacingFwdAddrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Simulate receiving server rejection response. Per RFC, anything other
+    // than no error or FQDN in use is failure.  Arbitrarily choosing refused.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+    // Run replacingFwdAddrsHandler again to process the response.
+    EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Completion flags should still be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // We should have failed the transaction. Verifiy that we transitioned
+    // correctly.
+    EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+              name_add->getNextEvent());
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  Initial posted event is FQDN_IN_USE_EVT.
+//  The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_Timeout) {
+    NameAddStubPtr name_add;
+
+    // Create the transaction.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                    NameAddTransaction::
+                                    FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectFwdServer());
+
+
+    // Verify that we can make maximum number of update attempts permitted
+    // and then transition to selecting a new server.
+    int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+    for (int i = 1; i <= max_tries; ++i) {
+        const D2UpdateMessagePtr prev_msg = name_add->getDnsUpdateRequest();
+
+        // Run replacingFwdAddrsHandler to send the request.
+        EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+        const D2UpdateMessagePtr curr_msg = name_add->getDnsUpdateRequest();
+        if (i == 1) {
+            // First time out we should build the message.
+            EXPECT_FALSE(prev_msg);
+            EXPECT_TRUE(curr_msg);
+        } else {
+            // Subsequent passes should reuse the request. We are only
+            // looking to check that we have not replaced the pointer value
+            // with a new pointer.  This tests the on_entry() logic which
+            // clears the request ONLY upon initial entry into the state.
+            EXPECT_TRUE(prev_msg == curr_msg);
+        }
+
+        // Simulate a server IO timeout.
+        name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+        name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+        // Run replacingFwdAddrsHandler again to process the response.
+        EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+        // Completion flags should be false.
+        EXPECT_FALSE(name_add->getForwardChangeCompleted());
+        EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+        if (i < max_tries) {
+            // We should be ready to try again.
+            EXPECT_EQ(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+                      name_add->getCurrState());
+            EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+                    name_add->getNextEvent());
+        } else {
+            // Server retries should be exhausted, time for a new server.
+            EXPECT_EQ(NameAddTransaction::SELECTING_FWD_SERVER_ST,
+                      name_add->getCurrState());
+            EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+                    name_add->getNextEvent());
+        }
+    }
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  Initial posted event is FQDN_IN_USE_EVT.
+//  The update request is sent but a corrupt response is received, this occurs
+//  MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_CorruptResponse) {
+    NameAddStubPtr name_add;
+
+    // Create the transaction.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                    NameAddTransaction::
+                                    FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectFwdServer());
+
+    // Verify that we can make maximum number of update attempts permitted
+    // and then transition to selecting a new server.
+    int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+    for (int i = 1; i <= max_tries; ++i) {
+        const D2UpdateMessagePtr prev_msg = name_add->getDnsUpdateRequest();
+
+        // Run replacingFwdAddrsHandler to send the request.
+        EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+        const D2UpdateMessagePtr curr_msg = name_add->getDnsUpdateRequest();
+        if (i == 1) {
+            // First time out we should build the message.
+            EXPECT_FALSE(prev_msg);
+            EXPECT_TRUE(curr_msg);
+        } else {
+            // Subsequent passes should reuse the request. We are only
+            // looking to check that we have not replaced the pointer value
+            // with a new pointer.  This tests the on_entry() logic which
+            // clears the request ONLY upon initial entry into the state.
+            EXPECT_TRUE(prev_msg == curr_msg);
+        }
+
+        // Simulate a server corrupt response.
+        name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+        name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+        // Run replacingFwdAddrsHandler again to process the response.
+        EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+        // Completion flags should be false.
+        EXPECT_FALSE(name_add->getForwardChangeCompleted());
+        EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+        if (i < max_tries) {
+            // We should be ready to try again.
+            EXPECT_EQ(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+                      name_add->getCurrState());
+            EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+                    name_add->getNextEvent());
+        } else {
+            // Server retries should be exhausted, time for a new server.
+            EXPECT_EQ(NameAddTransaction::SELECTING_FWD_SERVER_ST,
+                      name_add->getCurrState());
+            EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+                    name_add->getNextEvent());
+        }
+    }
+}
+
+// Tests the selectingRevServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, selectingRevServerHandler) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameChangeTransaction::
+                                    SELECTING_REV_SERVER_ST,
+                                    NameChangeTransaction::SELECT_SERVER_EVT));
+
+    // Call selectingRevServerHandler enough times to select all of the
+    // servers in it's current domain.  The first time, it will be with
+    // next event of SELECT_SERVER_EVT.  Thereafter it will be with a next
+    // event of SERVER_IO_ERROR_EVT.
+    int num_servers = name_add->getReverseDomain()->getServers()->size();
+    for (int i = 0; i < num_servers; ++i) {
+        // Run selectingRevServerHandler.
+        ASSERT_NO_THROW(name_add->selectingRevServerHandler())
+                        << " num_servers: " << num_servers
+                        << " selections: " << i;
+
+        // Verify that a server was selected.
+        ASSERT_TRUE(name_add->getCurrentServer())
+                    << " num_servers: " << num_servers
+                    << " selections: " << i;
+
+        // Verify that we transitioned correctly.
+        ASSERT_EQ(NameAddTransaction::REPLACING_REV_PTRS_ST,
+                  name_add->getCurrState())
+                  << " num_servers: " << num_servers << " selections: " << i;
+        ASSERT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+                  name_add->getNextEvent())
+                  << " num_servers: " << num_servers << " selections: " << i;
+
+        // Post a server IO error event.  This simulates an IO error occuring
+        // and a need to select the new server.
+        ASSERT_NO_THROW(name_add->postNextEvent(NameChangeTransaction::
+                                                SERVER_IO_ERROR_EVT))
+                        << " num_servers: " << num_servers
+                        << " selections: " << i;
+    }
+
+    // We should have exhausted the list of servers. Processing another
+    // SERVER_IO_ERROR_EVT should transition us to failure.
+    EXPECT_NO_THROW(name_add->selectingRevServerHandler());
+    EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::NO_MORE_SERVERS_EVT,
+              name_add->getNextEvent());
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameChangeTransaction::
+                                    SELECTING_REV_SERVER_ST,
+                                    StateModel::NOP_EVT));
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_add->selectingRevServerHandler(),
+                 NameAddTransactionError);
+}
+
+//************************** replacingRevPtrsHandler tests *****************
+
+// Tests that replacingRevPtrsHandler rejects invalid events.
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_InvalidEvent) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler but with
+    // an invalid event.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+                                    NameChangeTransaction::
+                                    StateModel::NOP_EVT));
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_add->replacingRevPtrsHandler(),
+                 NameAddTransactionError);
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_FwdOnlyAddOK) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+                                    NameAddTransaction::
+                                    SERVER_SELECTED_EVT, REVERSE_CHG));
+
+    // Should not be an update message yet.
+    D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+    ASSERT_FALSE(update_msg);
+
+    // At this point completion flags should be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Run replacingRevPtrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+    // Verify that an update message was constructed.
+    update_msg = name_add->getDnsUpdateRequest();
+    EXPECT_TRUE(update_msg);
+
+    // Verify that we are still in this state and next event is NOP_EVT.
+    // This indicates we "sent" the message and are waiting for IO completion.
+    EXPECT_EQ(NameAddTransaction::REPLACING_REV_PTRS_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::NOP_EVT,
+              name_add->getNextEvent());
+
+    // Simulate receiving a succussful update response.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+    // Run replacingRevPtrsHandler again to process the response.
+    EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+    // Forward completion should be false, reverse should be true.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_TRUE(name_add->getReverseChangeCompleted());
+
+    // Since it is a reverse change, we should be done.
+    // Verify that we transitioned correctly.
+    EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+              name_add->getNextEvent());
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates the update was rejected.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_OtherRcode) {
+    NameAddStubPtr name_add;
+    // Create the transaction.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+                                    NameAddTransaction::
+                                    SERVER_SELECTED_EVT, REVERSE_CHG));
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectRevServer());
+
+    // Run replacingRevPtrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+    // Simulate receiving server rejection response. Per RFC, anything other
+    // than no error is failure.  Arbitrarily choosing refused.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+    // Run replacingRevPtrsHandler again to process the response.
+    //EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+    (name_add->replacingRevPtrsHandler());
+
+    // Completion flags should still be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // We should have failed the transaction. Verifiy that we transitioned
+    // correctly.
+    EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+              name_add->getCurrState());
+    EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+              name_add->getNextEvent());
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_Timeout) {
+    NameAddStubPtr name_add;
+    // Create the transaction.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+                                    NameAddTransaction::
+                                    SERVER_SELECTED_EVT, REVERSE_CHG));
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectRevServer());
+
+    // Verify that we can make maximum number of update attempts permitted
+    // and then transition to selecting a new server.
+    int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+    for (int i = 1; i <= max_tries; ++i) {
+        const D2UpdateMessagePtr prev_msg = name_add->getDnsUpdateRequest();
+
+        // Run replacingRevPtrsHandler to send the request.
+        EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+        const D2UpdateMessagePtr curr_msg = name_add->getDnsUpdateRequest();
+        if (i == 1) {
+            // First time out we should build the message.
+            EXPECT_FALSE(prev_msg);
+            EXPECT_TRUE(curr_msg);
+        } else {
+            // Subsequent passes should reuse the request. We are only
+            // looking to check that we have not replaced the pointer value
+            // with a new pointer.  This tests the on_entry() logic which
+            // clears the request ONLY upon initial entry into the state.
+            EXPECT_TRUE(prev_msg == curr_msg);
+        }
+
+        // Simulate a server IO timeout.
+        name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+        name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+        // Run replacingRevPtrsHandler again to process the response.
+        EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+        // Completion flags should be false.
+        EXPECT_FALSE(name_add->getForwardChangeCompleted());
+        EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+        if (i < max_tries) {
+            // We should be ready to try again.
+            EXPECT_EQ(NameAddTransaction::REPLACING_REV_PTRS_ST,
+                      name_add->getCurrState());
+            EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+                    name_add->getNextEvent());
+        } else {
+            // Server retries should be exhausted, time for a new server.
+            EXPECT_EQ(NameAddTransaction::SELECTING_REV_SERVER_ST,
+                      name_add->getCurrState());
+            EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+                    name_add->getNextEvent());
+        }
+    }
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent but a corrupt response is received, this occurs
+//  MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_CorruptResponse) {
+    NameAddStubPtr name_add;
+    // Create the transaction.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+                                    NameAddTransaction::
+                                    SERVER_SELECTED_EVT, REVERSE_CHG));
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectRevServer());
+
+    // Verify that we can make maximum number of update attempts permitted
+    // and then transition to selecting a new server.
+    int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+    for (int i = 1; i <= max_tries; ++i) {
+        const D2UpdateMessagePtr prev_msg = name_add->getDnsUpdateRequest();
+
+        // Run replacingRevPtrsHandler to send the request.
+        EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+        const D2UpdateMessagePtr curr_msg = name_add->getDnsUpdateRequest();
+        if (i == 1) {
+            // First time out we should build the message.
+            EXPECT_FALSE(prev_msg);
+            EXPECT_TRUE(curr_msg);
+        } else {
+            // Subsequent passes should reuse the request. We are only
+            // looking to check that we have not replaced the pointer value
+            // with a new pointer.  This tests the on_entry() logic which
+            // clears the request ONLY upon initial entry into the state.
+            EXPECT_TRUE(prev_msg == curr_msg);
+        }
+
+        // Simulate a server corrupt response.
+        name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+        name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+        // Run replacingRevPtrsHandler again to process the response.
+        EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+        // Completion flags should be false.
+        EXPECT_FALSE(name_add->getForwardChangeCompleted());
+        EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+        if (i < max_tries) {
+            // We should be ready to try again.
+            EXPECT_EQ(NameAddTransaction::REPLACING_REV_PTRS_ST,
+                      name_add->getCurrState());
+            EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+                    name_add->getNextEvent());
+        } else {
+            // Server retries should be exhausted, time for a new server.
+            EXPECT_EQ(NameAddTransaction::SELECTING_REV_SERVER_ST,
+                      name_add->getCurrState());
+            EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+                    name_add->getNextEvent());
+        }
+    }
+}
+
+// Tests the processAddOkHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_OK_EVT
+// 2. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, processAddOkHandler) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+                                    NameChangeTransaction::UPDATE_OK_EVT));
+    // Run processAddOkHandler.
+    EXPECT_NO_THROW(name_add->processAddOkHandler());
+
+    // Verify that a server was selected.
+    EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_add->getNcrStatus());
+
+    // Verify that the model has ended.
+    EXPECT_EQ(StateModel::END_ST, name_add->getCurrState());
+    EXPECT_EQ(StateModel::END_EVT, name_add->getNextEvent());
+
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+                                    StateModel::NOP_EVT));
+    // Running the handler should throw.
+    EXPECT_THROW(name_add->processAddOkHandler(), NameAddTransactionError);
+}
+
+// Tests the processAddFailedHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_FAILED_EVT
+// 2. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, processAddFailedHandler) {
+    NameAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameChangeTransaction::
+                                    PROCESS_TRANS_FAILED_ST,
+                                    NameChangeTransaction::UPDATE_FAILED_EVT));
+    // Run processAddFailedHandler.
+    EXPECT_NO_THROW(name_add->processAddFailedHandler());
+
+    // Verify that a server was selected.
+    EXPECT_EQ(dhcp_ddns::ST_FAILED, name_add->getNcrStatus());
+
+    // Verify that the model has ended. (Remember, the transaction failed NOT
+    // the model.  The model should have ended normally.)
+    EXPECT_EQ(StateModel::END_ST, name_add->getCurrState());
+    EXPECT_EQ(StateModel::END_EVT, name_add->getNextEvent());
+
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(name_add =
+                    prepHandlerTest(NameChangeTransaction::
+                                    PROCESS_TRANS_FAILED_ST,
+                                    StateModel::NOP_EVT));
+    // Running the handler should throw.
+    EXPECT_THROW(name_add->processAddFailedHandler(), NameAddTransactionError);
+}
+
+
+}
diff --git a/src/bin/d2/tests/nc_trans_unittests.cc b/src/bin/d2/tests/nc_trans_unittests.cc
index 31785a4..ce80758 100644
--- a/src/bin/d2/tests/nc_trans_unittests.cc
+++ b/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
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <asiolink/interval_timer.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/bind.hpp>
@@ -24,6 +35,8 @@ using namespace isc::d2;
 
 namespace {
 
+const size_t MAX_MSG_SIZE = 1024;
+
 /// @brief Test derivation of NameChangeTransaction for exercising state
 /// model mechanics.
 ///
@@ -40,21 +53,57 @@ public:
     // NameChangeStub events
     static const int SEND_UPDATE_EVT = NCT_DERIVED_EVENT_MIN + 2;
 
+    bool use_stub_callback_;
+
     /// @brief Constructor
     ///
     /// Parameters match those needed by NameChangeTransaction.
-    NameChangeStub(isc::asiolink::IOService& io_service,
+    NameChangeStub(IOServicePtr& io_service,
                    dhcp_ddns::NameChangeRequestPtr& ncr,
                    DdnsDomainPtr forward_domain,
                    DdnsDomainPtr reverse_domain)
         : NameChangeTransaction(io_service, ncr, forward_domain,
-                                reverse_domain) {
+                                reverse_domain),
+                                use_stub_callback_(false) {
     }
 
     /// @brief Destructor
     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.
     void dummyHandler() {
         isc_throw(NameChangeTransactionError,
@@ -182,23 +231,179 @@ public:
         // Invoke the base call implementation first.
         NameChangeTransaction::verifyStates();
 
-        // Define our states.
+        // Check our states.
         getState(DOING_UPDATE_ST);
     }
 
     // Expose the protected methods to be tested.
     using StateModel::runModel;
+    using StateModel::postNextEvent;
+    using StateModel::setState;
+    using StateModel::initDictionaries;
     using NameChangeTransaction::initServerSelection;
     using NameChangeTransaction::selectNextServer;
     using NameChangeTransaction::getCurrentServer;
     using NameChangeTransaction::getDNSClient;
     using NameChangeTransaction::setNcrStatus;
+    using NameChangeTransaction::setDnsUpdateRequest;
+    using NameChangeTransaction::clearDnsUpdateRequest;
     using NameChangeTransaction::setDnsUpdateStatus;
     using NameChangeTransaction::getDnsUpdateResponse;
+    using NameChangeTransaction::setDnsUpdateResponse;
+    using NameChangeTransaction::clearDnsUpdateResponse;
     using NameChangeTransaction::getForwardChangeCompleted;
     using NameChangeTransaction::getReverseChangeCompleted;
     using NameChangeTransaction::setForwardChangeCompleted;
     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.
@@ -210,16 +415,48 @@ typedef boost::shared_ptr<NameChangeStub> NameChangeStubPtr;
 /// aspects of NameChangeTransaction.
 class NameChangeTransactionTest : public ::testing::Test {
 public:
-    isc::asiolink::IOService io_service_;
+    IOServicePtr io_service_;
     DdnsDomainPtr forward_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() {
     }
 
-    /// @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() {
+        // NCR in JSON form.
         const char* msg_str =
             "{"
             " \"change_type\" : 0 , "
@@ -232,28 +469,36 @@ public:
             " \"lease_length\" : 1300 "
             "}";
 
+        // Create the request from JSON.
         dhcp_ddns::NameChangeRequestPtr ncr;
-
         DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
         DnsServerInfoPtr server;
-
         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",
-                                       isc::asiolink::IOAddress("1.1.1.1")));
+                                       isc::asiolink::IOAddress("127.0.0.1"),
+                                       5301));
         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",
-                                       isc::asiolink::IOAddress("2.2.2.2")));
+                                       isc::asiolink::IOAddress("127.0.0.1"),
+                                       5301));
         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,
                                   forward_domain_, reverse_domain_)));
-
     }
 
 };
@@ -267,7 +512,7 @@ public:
 /// requires reverse change.
 /// 4. Valid construction functions properly
 TEST(NameChangeTransaction, construction) {
-    isc::asiolink::IOService io_service;
+    IOServicePtr io_service(new isc::asiolink::IOService());
 
     const char* msg_str =
         "{"
@@ -293,6 +538,13 @@ TEST(NameChangeTransaction, construction) {
     ASSERT_NO_THROW(forward_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.
     EXPECT_THROW(NameChangeTransaction(io_service, empty_ncr,
                                        forward_domain, reverse_domain),
@@ -363,9 +615,6 @@ TEST_F(NameChangeTransactionTest, accessors) {
     EXPECT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
     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.
     EXPECT_NO_THROW(name_change->setForwardChangeCompleted(true));
     EXPECT_TRUE(name_change->getForwardChangeCompleted());
@@ -375,6 +624,59 @@ TEST_F(NameChangeTransactionTest, accessors) {
     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.
 TEST_F(NameChangeTransactionTest, dictionaryCheck) {
     NameChangeStubPtr name_change;
@@ -596,4 +898,225 @@ TEST_F(NameChangeTransactionTest, failedUpdateTest) {
     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());
+}
+
 }



More information about the bind10-changes mailing list