Parcourir la source

Merge branch 'trac3087'

Thomas Markwalder il y a 11 ans
Parent
commit
8f99da735a

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

@@ -50,6 +50,7 @@ BUILT_SOURCES = spec_config.h d2_messages.h d2_messages.cc
 pkglibexec_PROGRAMS = b10-dhcp-ddns
 
 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
 

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

@@ -0,0 +1,31 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_ASIO_H
+#define D2_ASIO_H
+
+#include <asiolink/asiolink.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines a smart pointer to an IOService instance.
+typedef boost::shared_ptr<isc::asiolink::IOService> IOServicePtr;
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif

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

@@ -15,9 +15,9 @@
 #ifndef D2_CFG_MGR_H
 #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>
 

+ 1 - 1
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>

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

@@ -190,7 +190,7 @@ indicate a network connectivity or system resource issue.
 
 % DHCP_DDNS_QUEUE_MGR_RESUME_ERROR application could not restart the queue manager, reason: %1
 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.

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

@@ -35,13 +35,13 @@ D2Process::D2Process(const char* name, IOServicePtr io_service)
     // been received.  This means that until we receive the configuration,
     // 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

+ 6 - 3
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: "

+ 3 - 4
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_;

+ 5 - 1
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);
 }

+ 3 - 3
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_;

+ 1 - 1
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>

+ 1 - 3
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 {
 

+ 4 - 3
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,

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

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

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

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

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

@@ -38,18 +38,29 @@ const int NameChangeTransaction::UPDATE_FAILED_EVT;
 
 const int NameChangeTransaction::NCT_DERIVED_EVENT_MIN;
 
+const 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

+ 79 - 3
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.

+ 25 - 18
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) {

+ 12 - 5
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

+ 4 - 1
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

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

@@ -12,7 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // 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());
 }
 

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

@@ -12,7 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // 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);

+ 1 - 1
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>
 

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

@@ -261,7 +261,7 @@ public:
 
         // Set the response wait time to 0 so as our test is not hanging. This
         // 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

Fichier diff supprimé car celui-ci est trop grand
+ 1449 - 0
src/bin/d2/tests/nc_add_unittests.cc


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

@@ -12,7 +12,18 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // 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());
+}
+
 }